package plugin

import (
	"fmt"
	"log/slog"
	"strings"
	"time"

	"gogobee/internal/db"

	"maunium.net/go/mautrix/id"
)

// ── Prize Tiers (fixed payouts) ─────────────────────────────────────────────

var lotteryFixedPrizes = map[int]int{
	4: 5000,
	3: 500,
}

// ── Draw Ticker ─────────────────────────────────────────────────────────────

func (p *LotteryPlugin) drawTicker() {
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop()

	for range ticker.C {
		now := time.Now().UTC()
		if now.Weekday() != time.Friday || now.Hour() != 23 || now.Minute() != 59 {
			continue
		}

		weekKey := lotteryCurrentWeekStart()
		jobName := "lottery_draw"
		if db.JobCompleted(jobName, weekKey) {
			continue
		}

		slog.Info("lottery: executing weekly draw")
		p.executeDraw(weekKey)
		db.MarkJobCompleted(jobName, weekKey)
	}
}

// ── Reminder Ticker ─────────────────────────────────────────────────────────

func (p *LotteryPlugin) reminderTicker() {
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop()

	for range ticker.C {
		now := time.Now().UTC()
		if now.Weekday() != time.Thursday || now.Hour() != 20 || now.Minute() != 0 {
			continue
		}

		weekKey := lotteryCurrentWeekStart()
		jobName := "lottery_reminder"
		if db.JobCompleted(jobName, weekKey) {
			continue
		}

		gr := gamesRoom()
		if gr != "" {
			pot := communityPotBalance()
			p.SendMessage(gr, fmt.Sprintf(
				"🎟️ Lottery draw tomorrow. 23:59 UTC. Current pot: %s. "+
					"Tickets €1 each. Max 100 per player. `!lottery buy [N]` to enter.",
				fmtEuro(pot)))
		}

		db.MarkJobCompleted(jobName, weekKey)
	}
}

// ── Draw Execution ──────────────────────────────────────────────────────────

func (p *LotteryPlugin) executeDraw(weekStart string) {
	winning := generateLotteryNumbers()

	tickets, err := lotteryLoadAllWeekTickets(weekStart)
	if err != nil {
		slog.Error("lottery: failed to load tickets for draw", "err", err)
		return
	}

	if len(tickets) == 0 {
		gr := gamesRoom()
		if gr != "" {
			p.SendMessage(gr, "🎟️ **LOTTERY DRAW** — No tickets were sold this week. The pot rolls over.")
		}
		return
	}

	// Score all tickets.
	matchBuckets := make(map[int][]lotteryTicket) // matchCount -> tickets
	for i := range tickets {
		mc := countMatches(tickets[i].Numbers, winning)
		tickets[i].MatchCount = &mc

		prize := 0
		if mc >= 3 && mc <= 4 {
			prize = lotteryFixedPrizes[mc]
		}
		tickets[i].Prize = &prize

		lotteryUpdateTicketResult(tickets[i].ID, mc, prize)
		matchBuckets[mc] = append(matchBuckets[mc], tickets[i])
	}

	pot := communityPotBalance()
	initialPot := pot

	// Calculate fixed tier payouts.
	fixedTotal := 0
	for tier := 4; tier >= 3; tier-- {
		count := len(matchBuckets[tier])
		fixedTotal += count * lotteryFixedPrizes[tier]
	}

	// If pot can't cover all fixed payouts, prorate top-down.
	actualFixed := fixedTotal
	if actualFixed > pot {
		actualFixed = pot
	}

	// Debit fixed payouts from pot.
	if actualFixed > 0 {
		if !communityPotDebit(actualFixed) {
			slog.Error("lottery: failed to debit fixed payouts from pot", "amount", actualFixed)
			actualFixed = 0
		}
		pot -= actualFixed
	}

	// Credit fixed tier winners.
	prorateRatio := 1.0
	if fixedTotal > 0 && actualFixed < fixedTotal {
		prorateRatio = float64(actualFixed) / float64(fixedTotal)
	}

	if actualFixed > 0 {
		for tier := 4; tier >= 3; tier-- {
			for _, t := range matchBuckets[tier] {
				amount := int(float64(lotteryFixedPrizes[tier]) * prorateRatio)
				if amount > 0 {
					p.euro.Credit(t.UserID, float64(amount), fmt.Sprintf("lottery_%dmatch", tier))
				}
				// Update ticket with actual prorated prize (not nominal)
				lotteryUpdateTicketResult(t.ID, *t.MatchCount, amount)
			}
		}
	}

	// Jackpot.
	jackpotWinners := matchBuckets[5]
	jackpotAmount := 0
	rolledOver := 0

	if len(jackpotWinners) > 0 && pot >= 500 {
		// Split remaining pot among jackpot winners.
		perWinner := pot / len(jackpotWinners)
		remainder := pot - (perWinner * len(jackpotWinners))
		jackpotAmount = perWinner

		totalJackpotDebit := perWinner * len(jackpotWinners)
		if !communityPotDebit(totalJackpotDebit) {
			slog.Error("lottery: failed to debit jackpot from pot", "amount", totalJackpotDebit)
			// Jackpot rolls over — don't credit winners.
			jackpotAmount = 0
			rolledOver = pot
		} else {
			for _, t := range jackpotWinners {
				p.euro.Credit(t.UserID, float64(perWinner), "lottery_jackpot")
				lotteryUpdateTicketResult(t.ID, 5, perWinner)
			}
			rolledOver = remainder
		}
	} else {
		// Jackpot rolls over.
		rolledOver = pot
	}

	// Insert history.
	h := &lotteryHistoryRow{
		DrawDate:       time.Now().UTC().Format("2006-01-02"),
		WinningNumbers: winning,
		JackpotWinners: len(jackpotWinners),
		JackpotAmount:  jackpotAmount,
		Match4Winners:  len(matchBuckets[4]),
		Match3Winners:  len(matchBuckets[3]),
		Match2Winners:  0,
		Match1Winners:  0,
		PotTotal:       initialPot,
		RolledOver:     rolledOver,
	}
	lotteryInsertHistory(h)

	// Room announcement.
	gr := gamesRoom()
	if gr != "" {
		announcement := p.buildDrawAnnouncement(winning, h, jackpotWinners)
		p.SendMessage(gr, announcement)
	}

	// DM each winner with their tickets and results.
	p.dmWinners(winning, tickets)

	// Cleanup old tickets.
	lotteryCleanupOldTickets()

	slog.Info("lottery: draw completed",
		"winning", winning,
		"tickets", len(tickets),
		"pot", initialPot,
		"jackpot_winners", len(jackpotWinners))
}

func (p *LotteryPlugin) dmWinners(winning []int, tickets []lotteryTicket) {
	// Group winning tickets by user.
	byUser := make(map[id.UserID][]lotteryTicket)
	for _, t := range tickets {
		if t.MatchCount == nil || *t.MatchCount < 3 {
			continue
		}
		byUser[t.UserID] = append(byUser[t.UserID], t)
	}

	for uid, userTickets := range byUser {
		var sb strings.Builder
		sb.WriteString(fmt.Sprintf("🎟️ **Lottery Results** — Winning numbers: **%s**\n\n", formatLotteryNumbers(winning)))

		totalWon := 0
		for _, t := range userTickets {
			matches := *t.MatchCount
			prize := 0
			if t.Prize != nil {
				prize = *t.Prize
			}
			totalWon += prize

			// Show ticket numbers with matches highlighted
			sb.WriteString(fmt.Sprintf("🎫 %s — **%d match** → %s\n",
				formatTicketWithMatches(t.Numbers, winning), matches, fmtEuro(prize)))
		}

		sb.WriteString(fmt.Sprintf("\n💰 **Total: %s**", fmtEuro(totalWon)))

		if err := p.SendDM(uid, sb.String()); err != nil {
			slog.Error("lottery: failed to DM winner", "user", uid, "err", err)
		}
	}
}

func formatTicketWithMatches(ticket, winning []int) string {
	winSet := make(map[int]bool, len(winning))
	for _, n := range winning {
		winSet[n] = true
	}
	parts := make([]string, len(ticket))
	for i, n := range ticket {
		if winSet[n] {
			parts[i] = fmt.Sprintf("**%d**", n)
		} else {
			parts[i] = fmt.Sprintf("%d", n)
		}
	}
	return strings.Join(parts, " · ")
}

// ── Announcement Builder ────────────────────────────────────────────────────

func (p *LotteryPlugin) buildDrawAnnouncement(winning []int, h *lotteryHistoryRow, jackpotWinners []lotteryTicket) string {
	var sb strings.Builder

	sb.WriteString("🎟️ **LOTTERY DRAW** — Friday 23:59 UTC\n\n")
	sb.WriteString(fmt.Sprintf("This week's numbers: **%s**\n\n", formatLotteryNumbers(winning)))

	// Jackpot line.
	if len(jackpotWinners) > 0 && h.JackpotAmount > 0 {
		names := p.resolveWinnerNames(jackpotWinners)
		if len(jackpotWinners) == 1 {
			sb.WriteString(fmt.Sprintf("Jackpot (5 match): **%s** — %s 🎉\n", names[0], fmtEuro(h.JackpotAmount)))
		} else {
			sb.WriteString(fmt.Sprintf("Jackpot (5 match): Split between %s. %s each. 🎉\n",
				joinNames(names), fmtEuro(h.JackpotAmount)))
		}
	} else if len(jackpotWinners) > 0 && h.JackpotAmount == 0 {
		sb.WriteString("Jackpot withheld — pot insufficient. Rolls to next week. Fixed tiers paid as normal.\n")
	} else {
		sb.WriteString("Jackpot (5 match): No winner this week. Pot rolls over.\n")
	}

	// Fixed tiers.
	sb.WriteString(fmt.Sprintf("4 match: %d winner(s) — €5,000 each\n", h.Match4Winners))
	sb.WriteString(fmt.Sprintf("3 match: %d winner(s) — €500 each\n", h.Match3Winners))

	distributed := h.PotTotal - h.RolledOver
	sb.WriteString(fmt.Sprintf("\nPot distributed: %s. Next draw: Friday. Tickets on sale now.", fmtEuro(distributed)))

	return sb.String()
}

func (p *LotteryPlugin) resolveWinnerNames(winners []lotteryTicket) []string {
	// Deduplicate by user ID (multiple tickets from same player).
	seen := make(map[id.UserID]bool)
	var names []string
	for _, t := range winners {
		if seen[t.UserID] {
			continue
		}
		seen[t.UserID] = true
		// Try to get display name from DM room.
		name := p.DisplayName(t.UserID)
		names = append(names, name)
	}
	return names
}

func joinNames(names []string) string {
	if len(names) <= 2 {
		return strings.Join(names, " and ")
	}
	return strings.Join(names[:len(names)-1], ", ") + ", and " + names[len(names)-1]
}
