package services

import (
	"context"
	"log"
	"sort"
	"time"

	"github.com/google/uuid"
	"github.com/tapper/backend/database"
	"github.com/tapper/backend/models"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

const matchmakingInterval = 5 * time.Second

var (
	playersPerMatch = 10
	playersPerTeam  = 5
	team1Indices    = []int{0, 3, 4, 7, 8}
	team2Indices    = []int{1, 2, 5, 6, 9}
)

func ConfigureMatchmaker(demoMode bool) {
	if demoMode {
		playersPerMatch = 2
		playersPerTeam = 1
		team1Indices = []int{0}
		team2Indices = []int{1}
		log.Println("[matchmaker] DEMO_MODE: matches require 2 players (1v1)")
	}
}

func StartMatchmaker(ctx context.Context) {
	log.Println("[matchmaker] Starting background matchmaking loop")
	ticker := time.NewTicker(matchmakingInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			log.Println("[matchmaker] Shutting down")
			return
		case <-ticker.C:
			if err := runMatchmakingCycle(); err != nil {
				log.Printf("[matchmaker] Cycle error: %v", err)
			}
		}
	}
}

func runMatchmakingCycle() error {
	for _, mode := range []string{"competitive", "casual"} {
		if err := matchmakeForMode(mode); err != nil {
			log.Printf("[matchmaker] Error for mode %s: %v", mode, err)
		}
	}
	return nil
}

func matchmakeForMode(gameMode string) error {
	return database.DB.Transaction(func(tx *gorm.DB) error {
		// SKIP LOCKED so multiple API instances can run matchmaking concurrently
		// without picking the same queue rows.
		var entries []struct {
			models.QueueEntry
			UserMMR      int     `gorm:"column:user_mmr"`
			UserTier     string  `gorm:"column:user_tier"`
			WeightedWait float64 `gorm:"column:weighted_wait"`
		}

		err := tx.Raw(`
			SELECT qe.*, u.mmr AS user_mmr, u.tier AS user_tier,
				EXTRACT(EPOCH FROM (NOW() - qe.joined_at)) *
				CASE u.tier WHEN 'vip' THEN 2.0 WHEN 'premium' THEN 1.5 ELSE 1.0 END
				AS weighted_wait
			FROM queue_entries qe
			JOIN users u ON qe.user_id = u.id
			WHERE qe.game_mode = ?
			ORDER BY weighted_wait DESC
			FOR UPDATE OF qe SKIP LOCKED
		`, gameMode).Scan(&entries).Error
		if err != nil {
			return err
		}

		if len(entries) < playersPerMatch {
			return nil
		}

		selected := entries[:playersPerMatch]

		sort.Slice(selected, func(i, j int) bool {
			return selected[i].UserMMR > selected[j].UserMMR
		})

		// Snake draft for team balance: in full mode T1 picks 1,4,5,8,9 / T2 picks 2,3,6,7,10.
		// In demo mode it is just 1v1.

		maps := []string{"de_dust2", "de_mirage", "de_inferno", "de_nuke", "de_anubis", "de_vertigo", "de_ancient"}
		selectedMap := maps[time.Now().UnixNano()%int64(len(maps))]

		match := models.Match{
			ID:        uuid.New(),
			GameMode:  gameMode,
			Map:       selectedMap,
			Status:    models.MatchStatusMatched,
			Team1Name: "Team Alpha",
			Team2Name: "Team Beta",
		}
		if err := tx.Create(&match).Error; err != nil {
			return err
		}

		var matchPlayers []models.MatchPlayer
		for _, idx := range team1Indices {
			matchPlayers = append(matchPlayers, models.MatchPlayer{
				MatchID: match.ID,
				UserID:  selected[idx].UserID,
				Team:    1,
			})
		}
		for _, idx := range team2Indices {
			matchPlayers = append(matchPlayers, models.MatchPlayer{
				MatchID: match.ID,
				UserID:  selected[idx].UserID,
				Team:    2,
			})
		}
		if err := tx.Create(&matchPlayers).Error; err != nil {
			return err
		}

		selectedUserIDs := make([]uuid.UUID, playersPerMatch)
		for i, s := range selected {
			selectedUserIDs[i] = s.UserID
		}
		if err := tx.Where("user_id IN ?", selectedUserIDs).
			Clauses(clause.Locking{Strength: "UPDATE"}).
			Delete(&models.QueueEntry{}).Error; err != nil {
			return err
		}

		notifyPayload := map[string]interface{}{
			"match_id": match.ID.String(),
			"event":    "match_found",
			"status":   match.Status,
		}
		PublishEvent("match_events", notifyPayload)

		log.Printf("[matchmaker] Created match %s (%s on %s) with %d players",
			match.ID.String()[:8], gameMode, selectedMap, playersPerMatch)

		return nil
	})
}
