package tldr

import (
	"fmt"
	"strings"
	"time"

	"github.com/velour/catbase/bot"
	"github.com/velour/catbase/bot/msg"

	"github.com/rs/zerolog/log"

	"github.com/james-bowman/nlp"
)

type TLDRPlugin struct {
	bot         bot.Bot
	history     []history
	index       int
	lastRequest time.Time
}

type history struct {
	timestamp time.Time
	user      string
	body      string
}

func New(b bot.Bot) *TLDRPlugin {
	plugin := &TLDRPlugin{
		bot:         b,
		history:     []history{},
		index:       0,
		lastRequest: time.Now().Add(-24 * time.Hour),
	}
	b.Register(plugin, bot.Message, plugin.message)
	b.Register(plugin, bot.Help, plugin.help)
	return plugin
}

func (p *TLDRPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...any) bool {
	timeLimit := time.Duration(p.bot.Config().GetInt("TLDR.HourLimit", 1))
	lowercaseMessage := strings.ToLower(message.Body)
	if lowercaseMessage == "tl;dr" && p.lastRequest.After(time.Now().Add(-timeLimit*time.Hour)) {
		p.bot.Send(c, bot.Message, message.Channel, "Slow down, cowboy. Read that tiny backlog.")
		return true
	} else if lowercaseMessage == "tl;dr" {
		p.lastRequest = time.Now()
		nTopics := p.bot.Config().GetInt("TLDR.Topics", 5)

		stopWordSlice := p.bot.Config().GetArray("TLDR.StopWords", []string{})
		if len(stopWordSlice) == 0 {
			stopWordSlice = THESE_ARE_NOT_THE_WORDS_YOU_ARE_LOOKING_FOR
			p.bot.Config().SetArray("TLDR.StopWords", stopWordSlice)
		}

		vectoriser := nlp.NewCountVectoriser(stopWordSlice...)
		lda := nlp.NewLatentDirichletAllocation(nTopics)
		pipeline := nlp.NewPipeline(vectoriser, lda)
		docsOverTopics, err := pipeline.FitTransform(p.getTopics()...)

		if err != nil {
			log.Error().Err(err)
			return false
		}

		bestScores := make([][]float64, nTopics)
		bestDocs := make([][]history, nTopics)

		supportingDocs := p.bot.Config().GetInt("TLDR.Support", 3)
		for i := 0; i < nTopics; i++ {
			bestScores[i] = make([]float64, supportingDocs)
			bestDocs[i] = make([]history, supportingDocs)
		}

		dr, dc := docsOverTopics.Dims()
		for topic := 0; topic < dr; topic++ {
			minScore, minIndex := min(bestScores[topic])

			for doc := 0; doc < dc; doc++ {
				score := docsOverTopics.At(topic, doc)
				if score > minScore {
					bestScores[topic][minIndex] = score
					bestDocs[topic][minIndex] = p.history[doc]
					minScore, minIndex = min(bestScores[topic])
				}
			}
		}

		topicsOverWords := lda.Components()
		tr, tc := topicsOverWords.Dims()

		vocab := make([]string, len(vectoriser.Vocabulary))
		for k, v := range vectoriser.Vocabulary {
			vocab[v] = k
		}

		response := "Here you go captain 'too good to read backlog':\n"

		for topic := 0; topic < tr; topic++ {
			bestScore := -1.
			bestTopic := ""
			for word := 0; word < tc; word++ {
				score := topicsOverWords.At(topic, word)
				if score > bestScore {
					bestScore = score
					bestTopic = vocab[word]
				}
			}
			response += fmt.Sprintf("\n*Topic #%d: %s*\n", topic, bestTopic)
			for i := range bestDocs[topic] {
				response += fmt.Sprintf("<%s>%s\n", bestDocs[topic][i].user, bestDocs[topic][i].body)
			}

		}

		p.bot.Send(c, bot.Message, message.Channel, response)

		return true
	}

	hist := history{
		body:      lowercaseMessage,
		user:      message.User.Name,
		timestamp: time.Now(),
	}
	p.addHistory(hist)

	return false
}

func (p *TLDRPlugin) addHistory(hist history) {
	p.history = append(p.history, hist)
	sz := len(p.history)
	max := p.bot.Config().GetInt("TLDR.HistorySize", 1000)
	keepHrs := time.Duration(p.bot.Config().GetInt("TLDR.KeepHours", 24))
	// Clamp the size of the history
	if sz > max {
		p.history = p.history[len(p.history)-max:]
	}
	// Remove old entries
	yesterday := time.Now().Add(-keepHrs * time.Hour)
	begin := 0
	for i, m := range p.history {
		if !m.timestamp.Before(yesterday) {
			begin = i - 1 // should keep this message
			if begin < 0 {
				begin = 0
			}
			break
		}
	}
	p.history = p.history[begin:]
}

func (p *TLDRPlugin) getTopics() []string {
	hist := []string{}
	for _, h := range p.history {
		hist = append(hist, h.body)
	}
	return hist
}

// Help responds to help requests. Every plugin must implement a help function.
func (p *TLDRPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...any) bool {
	p.bot.Send(c, bot.Message, message.Channel, "tl;dr")
	return true
}

func min(slice []float64) (float64, int) {
	minVal := 1.
	minIndex := -1
	for index, val := range slice {
		if val < minVal {
			minVal = val
			minIndex = index
		}
	}
	return minVal, minIndex
}