package main

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"
)

func (a *App) readState(includeAllProofs bool) (StateResponse, error) {
	players, err := a.readPlayers()
	if err != nil {
		return StateResponse{}, err
	}

	if err := a.ensureScoreRows(players); err != nil {
		return StateResponse{}, err
	}

	settings, err := a.readSettings()
	if err != nil {
		return StateResponse{}, err
	}

	scoreMap, err := a.readScores(players)
	if err != nil {
		return StateResponse{}, err
	}

	includeScoreProofs := includeAllProofs || settings.ViewProofInView
	var scoreProofs map[string]map[int]string
	if includeScoreProofs {
		scoreProofs, err = a.readScoreProofs(players)
		if err != nil {
			return StateResponse{}, err
		}
	}

	derived := computeDerived(players, scoreMap)

	response := StateResponse{
		Competition: CompetitionMeta{
			TitleAr: "بطولة دويتوايلر للرماية",
			TitleEn: "Datwyler Shooting Event",
		},
		Players:    players,
		Scores:     scoreMapToJSON(scoreMap),
		Settings:   settings,
		Derived:    derived,
		ServerTime: time.Now().UTC().Format(time.RFC3339),
	}
	if includeScoreProofs {
		response.ScoreProofs = scoreProofMapToJSON(scoreProofs)
	}

	return response, nil
}

func (a *App) readPlayers() ([]Player, error) {
	rows, err := a.db.Query(`
SELECT id, name_ar, name_en, group_code, image_data
FROM players
ORDER BY id ASC
`)
	if err != nil {
		return nil, fmt.Errorf("query players: %w", err)
	}
	defer rows.Close()

	players := []Player{}
	for rows.Next() {
		var p Player
		if err := rows.Scan(&p.ID, &p.NameAr, &p.NameEn, &p.GroupCode, &p.ImageData); err != nil {
			return nil, fmt.Errorf("scan player: %w", err)
		}
		players = append(players, p)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate players: %w", err)
	}
	return players, nil
}

func (a *App) readScoreProofs(players []Player) (map[string]map[int]string, error) {
	proofs := map[string]map[int]string{}
	for _, stage := range scoreStages {
		proofs[stage] = map[int]string{}
	}
	for _, p := range players {
		for _, stage := range scoreStages {
			proofs[stage][p.ID] = ""
		}
	}

	rows, err := a.db.Query(`SELECT stage, player_id, image_data FROM score_attachments`)
	if err != nil {
		return nil, fmt.Errorf("query score attachments: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var stage string
		var playerID int
		var imageData string
		if err := rows.Scan(&stage, &playerID, &imageData); err != nil {
			return nil, fmt.Errorf("scan score attachment: %w", err)
		}
		stage = strings.ToLower(strings.TrimSpace(stage))
		stageMap, ok := proofs[stage]
		if !ok {
			continue
		}
		stageMap[playerID] = imageData
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate score attachments: %w", err)
	}
	return proofs, nil
}

func scoreProofMapToJSON(proofMap map[string]map[int]string) map[string]map[string]string {
	out := map[string]map[string]string{}
	for stage, stageMap := range proofMap {
		out[stage] = map[string]string{}
		for playerID, imageData := range stageMap {
			if strings.TrimSpace(imageData) == "" {
				continue
			}
			out[stage][strconv.Itoa(playerID)] = imageData
		}
	}
	return out
}

func (a *App) ensureScoreRows(players []Player) error {
	if len(players) == 0 {
		return nil
	}
	tx, err := a.db.Begin()
	if err != nil {
		return fmt.Errorf("begin score row ensure tx: %w", err)
	}
	defer tx.Rollback()

	for _, p := range players {
		for _, stage := range scoreStages {
			if _, err := tx.Exec(`INSERT OR IGNORE INTO scores(stage, player_id, score) VALUES(?, ?, 0)`, stage, p.ID); err != nil {
				return fmt.Errorf("ensure score row (%s,%d): %w", stage, p.ID, err)
			}
		}
	}

	if err := tx.Commit(); err != nil {
		return fmt.Errorf("commit score row ensure tx: %w", err)
	}
	return nil
}

func (a *App) readScores(players []Player) (map[string]map[int]int, error) {
	scores := map[string]map[int]int{}
	for _, stage := range scoreStages {
		scores[stage] = map[int]int{}
	}
	for _, p := range players {
		for _, stage := range scoreStages {
			scores[stage][p.ID] = 0
		}
	}

	rows, err := a.db.Query(`SELECT stage, player_id, score FROM scores`)
	if err != nil {
		return nil, fmt.Errorf("query scores: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var stage string
		var playerID int
		var score int
		if err := rows.Scan(&stage, &playerID, &score); err != nil {
			return nil, fmt.Errorf("scan score: %w", err)
		}
		stage = strings.ToLower(stage)
		if _, ok := scores[stage]; !ok {
			continue
		}
		scores[stage][playerID] = score
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate scores: %w", err)
	}

	return scores, nil
}

func scoreMapToJSON(scoreMap map[string]map[int]int) map[string]map[string]int {
	out := map[string]map[string]int{}
	for stage, stageMap := range scoreMap {
		out[stage] = map[string]int{}
		for playerID, value := range stageMap {
			out[stage][strconv.Itoa(playerID)] = value
		}
	}
	return out
}

func computeDerived(players []Player, scores map[string]map[int]int) DerivedState {
	playerByID := map[int]Player{}
	for _, p := range players {
		playerByID[p.ID] = p
	}

	preRows := []RankingRow{}
	for _, p := range players {
		preRows = append(preRows, RankingRow{
			PlayerID:  p.ID,
			NameAr:    p.NameAr,
			NameEn:    p.NameEn,
			GroupCode: p.GroupCode,
			ImageData: p.ImageData,
			Score:     scoreTotal(scores, p.ID, preliminaryRoundStages),
			TieBreak:  scores["prelim_tiebreak"][p.ID],
		})
	}

	sort.SliceStable(preRows, func(i, j int) bool {
		if preRows[i].Score != preRows[j].Score {
			return preRows[i].Score > preRows[j].Score
		}
		return preRows[i].PlayerID < preRows[j].PlayerID
	})
	assignDenseRankByScore(preRows)

	preTie := TieBreakInfo{Required: false, Resolved: true, Slots: 0, PlayerIDs: []int{}}
	finalists := []RankingRow{}

	if len(preRows) <= 12 {
		for i := range preRows {
			row := preRows[i]
			row.Seed = i + 1
			finalists = append(finalists, row)
		}
	} else {
		cutoff := preRows[11].Score
		above := []RankingRow{}
		atCutoff := []RankingRow{}
		for _, row := range preRows {
			if row.Score > cutoff {
				above = append(above, row)
			} else if row.Score == cutoff {
				atCutoff = append(atCutoff, row)
			}
		}
		slots := 12 - len(above)
		if slots < 0 {
			slots = 0
		}

		if len(atCutoff) <= slots {
			finalists = append(finalists, above...)
			finalists = append(finalists, atCutoff...)
		} else {
			preTie.Required = true
			preTie.Slots = slots
			preTie.Resolved = true
			for _, row := range atCutoff {
				preTie.PlayerIDs = append(preTie.PlayerIDs, row.PlayerID)
			}
			sort.Ints(preTie.PlayerIDs)

			sort.SliceStable(atCutoff, func(i, j int) bool {
				if atCutoff[i].TieBreak != atCutoff[j].TieBreak {
					return atCutoff[i].TieBreak > atCutoff[j].TieBreak
				}
				return atCutoff[i].PlayerID < atCutoff[j].PlayerID
			})

			if slots > 0 {
				boundary := atCutoff[slots-1].TieBreak
				greater := 0
				equal := 0
				for _, row := range atCutoff {
					if row.TieBreak > boundary {
						greater++
					} else if row.TieBreak == boundary {
						equal++
					}
				}
				if greater < slots && greater+equal > slots {
					preTie.Resolved = false
				}
			}

			finalists = append(finalists, above...)
			if slots > len(atCutoff) {
				slots = len(atCutoff)
			}
			finalists = append(finalists, atCutoff[:slots]...)
		}

		sort.SliceStable(finalists, func(i, j int) bool {
			if finalists[i].Score != finalists[j].Score {
				return finalists[i].Score > finalists[j].Score
			}
			if preTie.Required {
				if finalists[i].TieBreak != finalists[j].TieBreak {
					return finalists[i].TieBreak > finalists[j].TieBreak
				}
			}
			return finalists[i].PlayerID < finalists[j].PlayerID
		})
		assignDenseRankBy(finalists, func(a, b RankingRow) bool {
			if a.Score != b.Score {
				return false
			}
			if preTie.Required {
				return a.TieBreak == b.TieBreak
			}
			return true
		})
	}

	for i := range finalists {
		finalists[i].Seed = i + 1
		if i < 6 {
			finalists[i].FinalGroup = 1
		} else {
			finalists[i].FinalGroup = 2
		}
	}

	finalGroup1 := []RankingRow{}
	finalGroup2 := []RankingRow{}
	for _, row := range finalists {
		if row.FinalGroup == 1 {
			finalGroup1 = append(finalGroup1, row)
		} else {
			finalGroup2 = append(finalGroup2, row)
		}
	}

	finalRows := []RankingRow{}
	for _, finalist := range finalists {
		p := playerByID[finalist.PlayerID]
		finalRows = append(finalRows, RankingRow{
			PlayerID:   finalist.PlayerID,
			NameAr:     p.NameAr,
			NameEn:     p.NameEn,
			GroupCode:  p.GroupCode,
			ImageData:  p.ImageData,
			Score:      scoreTotal(scores, finalist.PlayerID, finalRoundStages),
			TieBreak:   scores["final_tiebreak"][finalist.PlayerID],
			Seed:       finalist.Seed,
			FinalGroup: finalist.FinalGroup,
		})
	}

	sort.SliceStable(finalRows, func(i, j int) bool {
		if finalRows[i].Score != finalRows[j].Score {
			return finalRows[i].Score > finalRows[j].Score
		}
		return finalRows[i].Seed < finalRows[j].Seed
	})

	finalTie := TieBreakInfo{Required: false, Resolved: true, Slots: 0, PlayerIDs: []int{}}
	tiedTop := map[int]bool{}
	if len(finalRows) > 0 {
		i := 0
		for i < len(finalRows) {
			j := i + 1
			for j < len(finalRows) && finalRows[j].Score == finalRows[i].Score {
				j++
			}
			if j-i > 1 && finalRows[i].Score > 0 {
				startPos := i + 1
				endPos := j
				if startPos <= 3 || endPos <= 3 || (startPos < 3 && endPos > 3) {
					for k := i; k < j; k++ {
						tiedTop[finalRows[k].PlayerID] = true
					}
				}
			}
			i = j
		}

		if len(tiedTop) > 0 {
			finalTie.Required = true
			for id := range tiedTop {
				finalTie.PlayerIDs = append(finalTie.PlayerIDs, id)
			}
			sort.Ints(finalTie.PlayerIDs)

			sort.SliceStable(finalRows, func(i, j int) bool {
				if finalRows[i].Score != finalRows[j].Score {
					return finalRows[i].Score > finalRows[j].Score
				}
				iti := tiedTop[finalRows[i].PlayerID]
				itj := tiedTop[finalRows[j].PlayerID]
				if iti && itj && finalRows[i].TieBreak != finalRows[j].TieBreak {
					return finalRows[i].TieBreak > finalRows[j].TieBreak
				}
				return finalRows[i].Seed < finalRows[j].Seed
			})
		}
	}

	if finalTie.Required {
		assignDenseRankBy(finalRows, func(a, b RankingRow) bool {
			if a.Score != b.Score {
				return false
			}
			if tiedTop[a.PlayerID] && tiedTop[b.PlayerID] {
				return a.TieBreak == b.TieBreak
			}
			return true
		})
		if len(finalRows) >= 3 {
			third := finalRows[2]
			greater := 0
			equal := 0
			for _, row := range finalRows {
				if row.Score > third.Score {
					greater++
					continue
				}
				if row.Score == third.Score {
					itied := tiedTop[row.PlayerID]
					ttied := tiedTop[third.PlayerID]
					if itied && ttied {
						if row.TieBreak > third.TieBreak {
							greater++
						} else if row.TieBreak == third.TieBreak {
							equal++
						}
					} else {
						equal++
					}
				}
			}
			if greater < 3 && greater+equal > 3 {
				finalTie.Resolved = false
			}
		}
	} else {
		assignDenseRankByScore(finalRows)
	}

	podium := []RankingRow{}
	for i := 0; i < len(finalRows) && i < 3; i++ {
		podium = append(podium, finalRows[i])
	}

	return DerivedState{
		PreliminaryRanking: RankingBundle{Rows: preRows, TieBreak: preTie, Unresolved: preTie.Required && !preTie.Resolved},
		Finalists:          finalists,
		FinalGroups:        FinalGroups{Group1: finalGroup1, Group2: finalGroup2},
		FinalRanking:       RankingBundle{Rows: finalRows, TieBreak: finalTie, Unresolved: finalTie.Required && !finalTie.Resolved},
		Podium:             podium,
	}
}

func scoreTotal(scores map[string]map[int]int, playerID int, stages []string) int {
	total := 0
	for _, stage := range stages {
		stageMap, ok := scores[stage]
		if !ok {
			continue
		}
		total += stageMap[playerID]
	}
	return total
}

func assignDenseRankByScore(rows []RankingRow) {
	assignDenseRankBy(rows, func(a, b RankingRow) bool {
		return a.Score == b.Score
	})
}

func assignDenseRankBy(rows []RankingRow, isEqual func(a, b RankingRow) bool) {
	if len(rows) == 0 {
		return
	}

	currentRank := 1
	rows[0].Rank = currentRank
	for i := 1; i < len(rows); i++ {
		if !isEqual(rows[i], rows[i-1]) {
			currentRank = i + 1
		}
		rows[i].Rank = currentRank
	}
}
