package alerts

import (
	"encoding/json"
	"math/rand"
	"text/template"

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

	"git.sr.ht/~ashkeel/strimertul/twitch/chat"
	"github.com/nicklaw5/helix/v2"
)

func (m *Module) onEventSubEvent(_ string, value string) {
	var ev eventSubNotification
	if err := json.Unmarshal([]byte(value), &ev); err != nil {
		m.logger.Warn("Error parsing webhook payload", log.Error(err))
		return
	}
	switch ev.Subscription.Type {
	case helix.EventSubTypeChannelFollow:
		// Only process if we care about follows
		if !m.Config.Follow.Enabled {
			return
		}
		// Parse as a follow event
		var followEv helix.EventSubChannelFollowEvent
		if err := json.Unmarshal(ev.Event, &followEv); err != nil {
			m.logger.Warn("Error parsing follow event", log.Error(err))
			return
		}
		// Pick a random message
		messageID := rand.Intn(len(m.Config.Follow.Messages))
		// Pick compiled template or fallback to plain text
		tpl, ok := m.templates[templateTypeFollow][m.Config.Follow.Messages[messageID]]
		if !ok {
			chat.WriteMessage(m.db, m.logger, chat.WriteMessageRequest{
				Message:  m.Config.Follow.Messages[messageID],
				Announce: m.Config.Follow.Announce,
			})
			return
		}
		m.writeTemplate(tpl, &followEv, m.Config.Follow.Announce)
		// Compile template and send
	case helix.EventSubTypeChannelRaid:
		// Only process if we care about raids
		if !m.Config.Raid.Enabled {
			return
		}
		// Parse as raid event
		var raidEv helix.EventSubChannelRaidEvent

		if err := json.Unmarshal(ev.Event, &raidEv); err != nil {
			m.logger.Warn("Error parsing raid event", log.Error(err))
			return
		}
		// Pick a random message from base set
		messageID := rand.Intn(len(m.Config.Raid.Messages))
		tpl, ok := m.templates[templateTypeRaid][m.Config.Raid.Messages[messageID]]
		if !ok {
			// Broken template!
			chat.WriteMessage(m.db, m.logger, chat.WriteMessageRequest{
				Message:  m.Config.Raid.Messages[messageID],
				Announce: m.Config.Raid.Announce,
			})
			return
		}
		// If we have variations, get the available variations and pick the one with the highest minimum viewers that are met
		if len(m.Config.Raid.Variations) > 0 {
			variation := getBestValidVariation(m.Config.Raid.Variations, func(variation raidVariation) int {
				if variation.MinViewers != nil && raidEv.Viewers >= *variation.MinViewers {
					return *variation.MinViewers
				}
				return 0
			})
			tpl = m.replaceWithVariation(tpl, templateTypeRaid, variation.Messages)
		}
		// Compile template and send
		m.writeTemplate(tpl, &raidEv, m.Config.Raid.Announce)
	case helix.EventSubTypeChannelCheer:
		// Only process if we care about bits
		if !m.Config.Cheer.Enabled {
			return
		}
		// Parse as cheer event
		var cheerEv helix.EventSubChannelCheerEvent
		if err := json.Unmarshal(ev.Event, &cheerEv); err != nil {
			m.logger.Warn("Error parsing cheer event", log.Error(err))
			return
		}
		// Pick a random message from base set
		messageID := rand.Intn(len(m.Config.Cheer.Messages))
		tpl, ok := m.templates[templateTypeCheer][m.Config.Cheer.Messages[messageID]]
		if !ok {
			// Broken template!
			chat.WriteMessage(m.db, m.logger, chat.WriteMessageRequest{
				Message:  m.Config.Cheer.Messages[messageID],
				Announce: m.Config.Cheer.Announce,
			})
			return
		}
		// If we have variations, get the available variations and pick the one with the highest minimum amount that is met
		if len(m.Config.Cheer.Variations) > 0 {
			variation := getBestValidVariation(m.Config.Cheer.Variations, func(variation cheerVariation) int {
				if variation.MinAmount != nil && cheerEv.Bits >= *variation.MinAmount {
					return *variation.MinAmount
				}
				return 0
			})
			tpl = m.replaceWithVariation(tpl, templateTypeCheer, variation.Messages)
		}
		// Compile template and send
		m.writeTemplate(tpl, &cheerEv, m.Config.Cheer.Announce)
	case helix.EventSubTypeChannelSubscription:
		// Only process if we care about subscriptions
		if !m.Config.Subscription.Enabled {
			return
		}
		// Parse as subscription event
		var subEv helix.EventSubChannelSubscribeEvent
		if err := json.Unmarshal(ev.Event, &subEv); err != nil {
			m.logger.Warn("Error parsing new subscription event", log.Error(err))
			return
		}
		m.addMixedEvent(subEv)
	case helix.EventSubTypeChannelSubscriptionMessage:
		// Only process if we care about subscriptions
		if !m.Config.Subscription.Enabled {
			return
		}
		// Parse as subscription event
		var subEv helix.EventSubChannelSubscriptionMessageEvent
		err := json.Unmarshal(ev.Event, &subEv)
		if err != nil {
			m.logger.Warn("Error parsing returning subscription event", log.Error(err))
			return
		}
		m.addMixedEvent(subEv)
	case helix.EventSubTypeChannelSubscriptionGift:
		// Only process if we care about gifted subs
		if !m.Config.GiftSub.Enabled {
			return
		}
		// Parse as gift event
		var giftEv helix.EventSubChannelSubscriptionGiftEvent
		if err := json.Unmarshal(ev.Event, &giftEv); err != nil {
			m.logger.Warn("Error parsing subscription gifted event", log.Error(err))
			return
		}
		// Pick a random message from base set
		messageID := rand.Intn(len(m.Config.GiftSub.Messages))
		tpl, ok := m.templates[templateTypeGift][m.Config.GiftSub.Messages[messageID]]
		if !ok {
			// Broken template!
			chat.WriteMessage(m.db, m.logger, chat.WriteMessageRequest{
				Message:  m.Config.GiftSub.Messages[messageID],
				Announce: m.Config.GiftSub.Announce,
			})
			return
		}
		// If we have variations, loop through all the available variations and pick the one with the highest minimum cumulative total that are met
		if len(m.Config.GiftSub.Variations) > 0 {
			if giftEv.IsAnonymous {
				variation := getBestValidVariation(m.Config.GiftSub.Variations, func(variation giftSubVariation) int {
					if variation.IsAnonymous != nil && *variation.IsAnonymous {
						return 1
					}
					return 0
				})
				tpl = m.replaceWithVariation(tpl, templateTypeGift, variation.Messages)
			} else if giftEv.CumulativeTotal > 0 {
				variation := getBestValidVariation(m.Config.GiftSub.Variations, func(variation giftSubVariation) int {
					if variation.MinCumulative != nil && *variation.MinCumulative > giftEv.CumulativeTotal {
						return *variation.MinCumulative
					}
					return 0
				})
				tpl = m.replaceWithVariation(tpl, templateTypeGift, variation.Messages)
			}
		}
		// Compile template and send
		m.writeTemplate(tpl, &giftEv, m.Config.GiftSub.Announce)
	}
}

func (m *Module) replaceWithVariation(tpl *template.Template, templateType templateType, messages []string) *template.Template {
	if messages != nil {
		messageID := rand.Intn(len(messages))
		// Make sure the template is valid
		if temp, ok := m.templates[templateType][messages[messageID]]; ok {
			return temp
		}
	}
	return tpl
}

// For variations, some variations are better than others, this function returns the best one
// by using a provided score function. The score is 0 or less if the variation is not valid,
// and 1 or more if it is valid. The variation with the highest score is returned.
func getBestValidVariation[T any](variations []T, filterFunc func(T) int) T {
	var best T
	var bestScore int
	for _, variation := range variations {
		score := filterFunc(variation)
		if score > bestScore {
			best = variation
			bestScore = score
		}
	}
	return best
}
