package timers

import (
	"context"
	"encoding/json"
	"log/slog"
	"math/rand"
	"time"

	"git.sr.ht/~ashkeel/strimertul/log"

	"git.sr.ht/~ashkeel/containers/sync"
	"git.sr.ht/~ashkeel/strimertul/database"
	"git.sr.ht/~ashkeel/strimertul/twitch/chat"
)

const AverageMessageWindow = 5

type Module struct {
	Config Config

	lastTrigger *sync.Map[string, time.Time]
	messages    *sync.Slice[int]

	logger *slog.Logger
	db     database.Database
	ctx    context.Context
}

func Setup(ctx context.Context, db database.Database, logger *slog.Logger) *Module {
	mod := &Module{
		lastTrigger: sync.NewMap[string, time.Time](),
		messages:    sync.NewSlice[int](),
		db:          db,
		ctx:         ctx,
		logger:      logger,
	}

	// Fill messages with zero values
	// (This can probably be done faster)
	for i := 0; i < AverageMessageWindow; i++ {
		mod.messages.Push(0)
	}

	// Load config from database
	if err := db.GetJSON(ConfigKey, &mod.Config); err != nil {
		logger.Debug("Config load error", log.Error(err))
		mod.Config = Config{
			Timers: make(map[string]ChatTimer),
		}
		// Save empty config
		err = db.PutJSON(ConfigKey, mod.Config)
		if err != nil {
			logger.Warn("Could not save default config for bot timers", log.Error(err))
		}
	}

	if err := db.SubscribeKeyContext(ctx, ConfigKey, func(value string) {
		if err := json.Unmarshal([]byte(value), &mod.Config); err != nil {
			logger.Warn("Error reloading timer config", log.Error(err))
			return
		}
		logger.Info("Reloaded timer config")
	}); err != nil {
		logger.Error("Could not set-up timer reload subscription", log.Error(err))
	}

	logger.Debug("Loaded timers", slog.Int("timers", len(mod.Config.Timers)))

	// Start goroutine for clearing message counters and running timers
	go mod.runTimers()

	return mod
}

func (m *Module) runTimers() {
	for {
		// Wait until next tick (remainder until next minute, as close to 0 seconds as possible)
		currentTime := time.Now()
		nextTick := currentTime.Round(time.Minute).Add(time.Minute)
		timeUntilNextTick := nextTick.Sub(currentTime)
		time.Sleep(timeUntilNextTick)

		err := m.db.PutJSON(chat.ActivityKey, m.messages.Get())
		if err != nil {
			m.logger.Warn("Error saving chat activity", log.Error(err))
		}

		// Calculate activity
		activity := m.currentChatActivity()

		// Reset timer
		index := time.Now().Minute() % AverageMessageWindow
		messages := m.messages.Get()
		messages[index] = 0
		m.messages.Set(messages)

		// Run timers
		for name, timer := range m.Config.Timers {
			m.ProcessTimer(name, timer, activity)
		}
	}
}

func (m *Module) ProcessTimer(name string, timer ChatTimer, activity int) {
	// Must be enabled
	if !timer.Enabled {
		return
	}

	// Check if enough time has passed
	lastTriggeredTime, ok := m.lastTrigger.GetKey(name)
	if !ok {
		// If it's the first time we're checking it, start the cooldown
		lastTriggeredTime = time.Now()
		m.lastTrigger.SetKey(name, lastTriggeredTime)
	}

	minDelay := timer.MinimumDelay
	if minDelay < 60 {
		minDelay = 60
	}

	now := time.Now()
	if now.Sub(lastTriggeredTime) < time.Duration(minDelay)*time.Second {
		return
	}

	// Make sure chat activity is high enough
	if activity < timer.MinimumChatActivity {
		return
	}

	// Pick a random message
	message := timer.Messages[rand.Intn(len(timer.Messages))]

	// Write message to chat
	chat.WriteMessage(m.db, m.logger, chat.WriteMessageRequest{
		Message:  message,
		Announce: timer.Announce,
	})

	// Update last trigger
	m.lastTrigger.SetKey(name, now)
}

func (m *Module) currentChatActivity() int {
	total := 0
	for _, v := range m.messages.Get() {
		total += v
	}
	return total
}

func (m *Module) OnMessage() {
	index := time.Now().Minute() % AverageMessageWindow
	m.messages.SetIndex(index, 1)
}
