package storage

import (
	"encoding/json"
	"fmt"
	"math/rand/v2"
	"time"

	"pete/internal/games/trivia"
)

// The trivia bank.
//
// Questions are pulled from OpenTDB in the background (internal/opentdb) and
// drawn from here when a ladder is built. Nothing in a player's round ever
// touches the network.

// ErrBankEmpty means the bank hasn't got enough questions of that difficulty to
// build a ladder. It is a real state, not a bug: a fresh database has an empty
// bank until the refill loop has been round a few times.
var ErrBankEmpty = fmt.Errorf("trivia: the bank is short of questions")

// AddTriviaQuestions files a fetched batch. Questions already in the bank are
// ignored rather than replaced — OpenTDB hands back overlapping batches, and the
// UNIQUE on the text is what stops the bank becoming forty questions deep.
// Returns how many were actually new, which is what the refill loop logs.
func AddTriviaQuestions(difficulty string, qs []trivia.Question) (int, error) {
	if len(qs) == 0 {
		return 0, nil
	}
	tx, err := Get().Begin()
	if err != nil {
		return 0, fmt.Errorf("trivia: begin: %w", err)
	}
	defer tx.Rollback() //nolint:errcheck // no-op once committed

	stmt, err := tx.Prepare(
		`INSERT OR IGNORE INTO trivia_questions
		     (difficulty, category, question, correct, incorrect, fetched_at)
		 VALUES (?, ?, ?, ?, ?, ?)`)
	if err != nil {
		return 0, fmt.Errorf("trivia: prepare: %w", err)
	}
	defer stmt.Close()

	now := time.Now().Unix()
	added := 0
	for _, q := range qs {
		if len(q.Answers) < 2 || q.Correct < 0 || q.Correct >= len(q.Answers) {
			continue
		}
		correct := q.Answers[q.Correct]
		wrong := make([]string, 0, len(q.Answers)-1)
		for i, a := range q.Answers {
			if i != q.Correct {
				wrong = append(wrong, a)
			}
		}
		blob, err := json.Marshal(wrong)
		if err != nil {
			continue
		}
		res, err := stmt.Exec(difficulty, q.Category, q.Text, correct, string(blob), now)
		if err != nil {
			return added, fmt.Errorf("trivia: insert: %w", err)
		}
		if n, err := res.RowsAffected(); err == nil {
			added += int(n)
		}
	}
	if err := tx.Commit(); err != nil {
		return 0, fmt.Errorf("trivia: commit: %w", err)
	}
	return added, nil
}

// CountTrivia is how many questions of a difficulty the bank holds. The refill
// loop reads it to decide whether to bother.
func CountTrivia(difficulty string) (int, error) {
	var n int
	if err := Get().QueryRow(
		`SELECT COUNT(*) FROM trivia_questions WHERE difficulty = ?`, difficulty,
	).Scan(&n); err != nil {
		return 0, fmt.Errorf("trivia: count: %w", err)
	}
	return n, nil
}

// DrawTrivia deals a ladder: n distinct questions of one difficulty, chosen with
// the game's own rng.
//
// The choice is made in Go rather than with ORDER BY RANDOM() so that the seed
// in the audit log means something: the same seed against the same bank deals
// the same ladder, which is what lets a disputed game be replayed. It reads the
// ids first and picks from them, so a bank of a few thousand questions costs one
// small scan rather than a sort of the whole table.
func DrawTrivia(difficulty string, n int, rng *rand.Rand) ([]trivia.Question, error) {
	if n <= 0 {
		return nil, nil
	}
	rows, err := Get().Query(
		`SELECT id FROM trivia_questions WHERE difficulty = ? ORDER BY id`, difficulty)
	if err != nil {
		return nil, fmt.Errorf("trivia: draw ids: %w", err)
	}
	var ids []int64
	for rows.Next() {
		var id int64
		if err := rows.Scan(&id); err != nil {
			rows.Close()
			return nil, fmt.Errorf("trivia: scan id: %w", err)
		}
		ids = append(ids, id)
	}
	rows.Close()
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("trivia: draw ids: %w", err)
	}
	if len(ids) < n {
		return nil, ErrBankEmpty
	}

	rng.Shuffle(len(ids), func(i, j int) { ids[i], ids[j] = ids[j], ids[i] })
	pick := ids[:n]

	out := make([]trivia.Question, 0, n)
	for _, id := range pick {
		var q trivia.Question
		var correct, blob string
		if err := Get().QueryRow(
			`SELECT category, question, correct, incorrect FROM trivia_questions WHERE id = ?`, id,
		).Scan(&q.Category, &q.Text, &correct, &blob); err != nil {
			return nil, fmt.Errorf("trivia: load question: %w", err)
		}
		var wrong []string
		if err := json.Unmarshal([]byte(blob), &wrong); err != nil {
			return nil, fmt.Errorf("trivia: unreadable answers: %w", err)
		}
		// Correct: 0 is a convention the engine immediately destroys — New()
		// reshuffles every question against the game's seed. Nothing that reaches a
		// player depends on the order they come out of the table in.
		q.Answers = append([]string{correct}, wrong...)
		q.Correct = 0
		out = append(out, q)
	}
	return out, nil
}
