package game

import (
	"context"
	"fmt"
	"strings"

	"github.com/google/uuid"
)

// A move's "duration" is the think time from the previous move's commit (the moment
// the turn started) to this move's commit. Only play/pass/exchange moves count;
// timeouts and resignations are not think time. The very first move of a game has no
// previous move, so its baseline is the game's creation time. The figures are derived
// from the move journal (game_moves.created_at), so no schema change is needed.
//
// timedMovesCTE is the shared subquery yielding (account, game, ordinal, seconds) for
// every timed move; the two reports aggregate it differently.
const timedMovesCTE = `
  SELECT gp.account_id AS aid,
         m.game_id AS gid,
         ROW_NUMBER() OVER (PARTITION BY m.game_id ORDER BY m.seq) AS ord,
         EXTRACT(EPOCH FROM (m.created_at - COALESCE(prev.created_at, g.created_at))) AS secs
  FROM backend.game_moves m
  JOIN backend.games g ON g.game_id = m.game_id
  LEFT JOIN backend.game_moves prev ON prev.game_id = m.game_id AND prev.seq = m.seq - 1
  JOIN backend.game_players gp ON gp.game_id = m.game_id AND gp.seat = m.seat
  WHERE m.action IN ('play', 'pass', 'exchange')`

// MoveDurationStat is the min, max and mean per-move think time (in seconds) for an
// account across all its games, with the number of timed moves counted.
type MoveDurationStat struct {
	MinSecs float64
	MaxSecs float64
	AvgSecs float64
	Moves   int
}

// MoveDurationStats returns the move-duration summary for each of accountIDs that has
// at least one timed move; accounts with none are absent from the map. It powers the
// admin user-list columns. The scan over the journal is acceptable for the low-traffic
// console; per-human analysis is the authoritative use (the live metric aggregates all
// seats including robots).
func (s *Store) MoveDurationStats(ctx context.Context, accountIDs []uuid.UUID) (map[uuid.UUID]MoveDurationStat, error) {
	if len(accountIDs) == 0 {
		return map[uuid.UUID]MoveDurationStat{}, nil
	}
	q := `WITH d AS (` + timedMovesCTE + `)
SELECT aid, MIN(secs), MAX(secs), AVG(secs), COUNT(*) FROM d WHERE aid = ANY($1::uuid[]) GROUP BY aid`
	rows, err := s.db.QueryContext(ctx, q, uuidArrayLiteral(accountIDs))
	if err != nil {
		return nil, fmt.Errorf("game: move-duration stats: %w", err)
	}
	defer rows.Close()
	out := make(map[uuid.UUID]MoveDurationStat, len(accountIDs))
	for rows.Next() {
		var id uuid.UUID
		var st MoveDurationStat
		if err := rows.Scan(&id, &st.MinSecs, &st.MaxSecs, &st.AvgSecs, &st.Moves); err != nil {
			return nil, fmt.Errorf("game: scan move-duration stat: %w", err)
		}
		out[id] = st
	}
	return out, rows.Err()
}

// OrdinalDuration is the min/max/mean think time (seconds) at an account's k-th move
// (Ordinal) across all its games.
type OrdinalDuration struct {
	Ordinal int
	MinSecs float64
	MaxSecs float64
	AvgSecs float64
}

// MoveDurationByOrdinal returns the account's per-move-number think-time summary,
// ordered by move number, for the admin user-detail chart. The ordinal counts the
// account's own moves within each game (its 1st, 2nd, … move).
func (s *Store) MoveDurationByOrdinal(ctx context.Context, accountID uuid.UUID) ([]OrdinalDuration, error) {
	q := `WITH d AS (` + timedMovesCTE + ` AND gp.account_id = $1)
SELECT ord, MIN(secs), MAX(secs), AVG(secs) FROM d GROUP BY ord ORDER BY ord`
	rows, err := s.db.QueryContext(ctx, q, accountID)
	if err != nil {
		return nil, fmt.Errorf("game: move-duration by ordinal: %w", err)
	}
	defer rows.Close()
	var out []OrdinalDuration
	for rows.Next() {
		var od OrdinalDuration
		if err := rows.Scan(&od.Ordinal, &od.MinSecs, &od.MaxSecs, &od.AvgSecs); err != nil {
			return nil, fmt.Errorf("game: scan ordinal duration: %w", err)
		}
		out = append(out, od)
	}
	return out, rows.Err()
}

// uuidArrayLiteral renders ids as a Postgres array literal ("{u1,u2,…}") for an
// ANY($1::uuid[]) parameter. UUIDs are fixed-format, so the literal is injection-safe.
func uuidArrayLiteral(ids []uuid.UUID) string {
	ss := make([]string, len(ids))
	for i, id := range ids {
		ss[i] = id.String()
	}
	return "{" + strings.Join(ss, ",") + "}"
}

// MoveDurationStats exposes the store report to the admin console handlers.
func (svc *Service) MoveDurationStats(ctx context.Context, accountIDs []uuid.UUID) (map[uuid.UUID]MoveDurationStat, error) {
	return svc.store.MoveDurationStats(ctx, accountIDs)
}

// MoveDurationByOrdinal exposes the per-move-number report to the admin console.
func (svc *Service) MoveDurationByOrdinal(ctx context.Context, accountID uuid.UUID) ([]OrdinalDuration, error) {
	return svc.store.MoveDurationByOrdinal(ctx, accountID)
}
