package plugin

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

	"gogobee/internal/db"

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

// StreaksPlugin tracks daily activity streaks and first-poster records.
type StreaksPlugin struct {
	Base
}

// NewStreaksPlugin creates a new streaks plugin.
func NewStreaksPlugin(client *mautrix.Client) *StreaksPlugin {
	return &StreaksPlugin{
		Base: NewBase(client),
	}
}

func (p *StreaksPlugin) Name() string { return "streaks" }

func (p *StreaksPlugin) Commands() []CommandDef {
	return []CommandDef{
		{Name: "streak", Description: "Show your current and longest activity streak", Usage: "!streak", Category: "Leveling & Stats"},
		{Name: "firstboard", Description: "Show top first-posters of the day", Usage: "!firstboard", Category: "Leveling & Stats"},
	}
}

func (p *StreaksPlugin) Init() error { return nil }

func (p *StreaksPlugin) OnReaction(_ ReactionContext) error { return nil }

func (p *StreaksPlugin) OnMessage(ctx MessageContext) error {
	if p.IsCommand(ctx.Body, "streak") {
		return p.handleStreak(ctx)
	}
	if p.IsCommand(ctx.Body, "firstboard") {
		return p.handleFirstboard(ctx)
	}

	// Skip tracking for bot commands
	if ctx.IsCommand {
		return nil
	}

	// Passive: track daily activity and first poster
	p.trackDailyActivity(ctx)
	p.trackFirstPoster(ctx)

	return nil
}

func (p *StreaksPlugin) trackDailyActivity(ctx MessageContext) {
	today := time.Now().UTC().Format("2006-01-02")

	db.Exec("streaks: track daily activity",
		`INSERT INTO daily_activity (user_id, date, message_count) VALUES (?, ?, 1)
		 ON CONFLICT(user_id, date) DO UPDATE SET message_count = message_count + 1`,
		string(ctx.Sender), today,
	)
}

func (p *StreaksPlugin) trackFirstPoster(ctx MessageContext) {
	today := time.Now().UTC().Format("2006-01-02")
	now := time.Now().UTC().Unix()

	// INSERT OR IGNORE — only the first poster for this room+date wins
	db.Exec("streaks: track first poster",
		`INSERT OR IGNORE INTO daily_first (room_id, date, user_id, timestamp) VALUES (?, ?, ?, ?)`,
		string(ctx.RoomID), today, string(ctx.Sender), now,
	)
}

func (p *StreaksPlugin) handleStreak(ctx MessageContext) error {
	target := ctx.Sender
	args := p.GetArgs(ctx.Body, "streak")
	if args != "" {
		if resolved, ok := p.ResolveUser(args, ctx.RoomID); ok {
			target = resolved
		}
	}

	d := db.Get()

	// Get all active dates for this user, sorted descending
	rows, err := d.Query(
		`SELECT date FROM daily_activity WHERE user_id = ? ORDER BY date DESC`,
		string(target),
	)
	if err != nil {
		slog.Error("streaks: query", "err", err)
		return p.SendReply(ctx.RoomID, ctx.EventID, "Failed to look up streak.")
	}
	defer rows.Close()

	var dates []string
	for rows.Next() {
		var date string
		if err := rows.Scan(&date); err != nil {
			continue
		}
		dates = append(dates, date)
	}

	if len(dates) == 0 {
		return p.SendReply(ctx.RoomID, ctx.EventID, fmt.Sprintf("%s has no activity recorded yet.", string(target)))
	}

	currentStreak, longestStreak := calculateStreaks(dates)

	msg := fmt.Sprintf(
		"🔥 %s — Streak Report\nCurrent streak: %s days\nLongest streak: %s days\nTotal active days: %s",
		string(target),
		formatNumber(currentStreak),
		formatNumber(longestStreak),
		formatNumber(len(dates)),
	)
	return p.SendReply(ctx.RoomID, ctx.EventID, msg)
}

// calculateStreaks computes current and longest streaks from descending-sorted date strings.
func calculateStreaks(dates []string) (current int, longest int) {
	if len(dates) == 0 {
		return 0, 0
	}

	today := time.Now().UTC().Format("2006-01-02")

	// Parse first date — current streak only counts if it includes today or yesterday
	firstDate, err := time.Parse("2006-01-02", dates[0])
	if err != nil {
		return 0, 0
	}

	todayDate, _ := time.Parse("2006-01-02", today)
	daysSinceFirst := int(todayDate.Sub(firstDate).Hours() / 24)
	if daysSinceFirst > 1 {
		// Streak is broken — current streak is 0
		// Still calculate longest
		current = 0
	} else {
		// Count current streak
		current = 1
		for i := 1; i < len(dates); i++ {
			prev, err1 := time.Parse("2006-01-02", dates[i-1])
			curr, err2 := time.Parse("2006-01-02", dates[i])
			if err1 != nil || err2 != nil {
				break
			}
			diff := int(prev.Sub(curr).Hours() / 24)
			if diff == 1 {
				current++
			} else {
				break
			}
		}
	}

	// Calculate longest streak (walk all dates)
	streak := 1
	longest = 1
	for i := 1; i < len(dates); i++ {
		prev, err1 := time.Parse("2006-01-02", dates[i-1])
		curr, err2 := time.Parse("2006-01-02", dates[i])
		if err1 != nil || err2 != nil {
			streak = 1
			continue
		}
		diff := int(prev.Sub(curr).Hours() / 24)
		if diff == 1 {
			streak++
		} else {
			streak = 1
		}
		if streak > longest {
			longest = streak
		}
	}

	if current > longest {
		longest = current
	}

	return current, longest
}

func (p *StreaksPlugin) handleFirstboard(ctx MessageContext) error {
	members := p.RoomMembers(ctx.RoomID)

	d := db.Get()

	rows, err := d.Query(
		`SELECT user_id, COUNT(*) as wins
		 FROM daily_first
		 GROUP BY user_id
		 ORDER BY wins DESC`,
	)
	if err != nil {
		slog.Error("streaks: firstboard query", "err", err)
		return p.SendReply(ctx.RoomID, ctx.EventID, "Failed to load first-poster board.")
	}
	defer rows.Close()

	var sb strings.Builder
	sb.WriteString("🌅 First Poster Board — Top 10\n\n")

	medals := []string{"🥇", "🥈", "🥉"}
	i := 0
	for rows.Next() && i < 10 {
		var userID string
		var wins int
		if err := rows.Scan(&userID, &wins); err != nil {
			continue
		}
		if members != nil && !members[id.UserID(userID)] {
			continue
		}
		prefix := fmt.Sprintf("#%d", i+1)
		if i < len(medals) {
			prefix = medals[i]
		}
		sb.WriteString(fmt.Sprintf("%s %s — %s first posts\n", prefix, userID, formatNumber(wins)))
		i++
	}

	if i == 0 {
		return p.SendReply(ctx.RoomID, ctx.EventID, "No first-poster data yet.")
	}

	// Also show today's first poster
	today := time.Now().UTC().Format("2006-01-02")
	var todayFirst sql.NullString
	_ = d.QueryRow(
		`SELECT user_id FROM daily_first WHERE room_id = ? AND date = ?`,
		string(ctx.RoomID), today,
	).Scan(&todayFirst)

	if todayFirst.Valid {
		sb.WriteString(fmt.Sprintf("\nToday's first poster in this room: %s", todayFirst.String))
	}

	return p.SendReply(ctx.RoomID, ctx.EventID, sb.String())
}
