package economy

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"strconv"
	"time"
)

// امتیازِ هر رویداد در تورنومنت.
const (
	tourPointsWin  = 100 // بردِ یک بازی
	tourPointsLoss = 25  // شرکت در یک بازی (باخت)
)

var (
	ErrTournamentNotFound = errors.New("tournament not found")
	ErrTournamentClosed   = errors.New("tournament is closed")
	ErrAlreadyJoined      = errors.New("already joined")
)

// Tournament یک تورنومنتِ امتیازی.
type Tournament struct {
	ID          int64     `json:"id"`
	Title       string    `json:"title"`
	Description string    `json:"description"`
	EntryFee    int64     `json:"entry_fee"`
	Prizes      []int64   `json:"prizes"`
	StartsAt    time.Time `json:"-"`
	EndsAt      time.Time `json:"-"`
	Settled     bool      `json:"-"`
}

// TournamentView نمای عمومیِ تورنومنت برای کلاینت (وضعیت، جایزه‌ی کل، عضویتِ کاربر).
type TournamentView struct {
	ID          int64   `json:"id"`
	Title       string  `json:"title"`
	Description string  `json:"description"`
	EntryFee    int64   `json:"entry_fee"`
	Prizes      []int64 `json:"prizes"`
	PrizePool   int64   `json:"prize_pool"` // مجموعِ جوایز
	Status      string  `json:"status"`     // upcoming | active | ended
	StartsAt    string  `json:"starts_at"`  // RFC3339 UTC (ماشینی)
	EndsAt      string  `json:"ends_at"`
	StartsLocal string  `json:"starts_local"` // به وقتِ تهران (نمایشی)
	EndsLocal   string  `json:"ends_local"`
	Players     int     `json:"players"`
	Joined      bool    `json:"joined"`
	MyPoints    int     `json:"my_points"`
	MyRank      int     `json:"my_rank"` // ۰ یعنی رتبه‌ای ندارد
}

// TournamentStanding یک ردیفِ جدولِ رده‌بندی.
type TournamentStanding struct {
	Rank   int    `json:"rank"`
	UserID int64  `json:"user_id"`
	Name   string `json:"name"`
	Avatar string `json:"avatar"`
	Points int    `json:"points"`
	Wins   int    `json:"wins"`
	Games  int    `json:"games"`
	Prize  int64  `json:"prize"`
}

// iranZone وقتِ ایران (UTC+3:30، بدونِ DST از ۲۰۲۲). ثابت است تا به tzdata
// روی سرور وابسته نباشیم (باینریِ CGO-off).
var iranZone = time.FixedZone("IRST", 12600)

func tournamentStatus(now, starts, ends time.Time) string {
	switch {
	case now.Before(starts):
		return "upcoming"
	case now.After(ends):
		return "ended"
	default:
		return "active"
	}
}

func sumPrizes(p []int64) int64 {
	var s int64
	for _, x := range p {
		s += x
	}
	return s
}

// scanTournament یک ردیفِ tournaments را می‌خواند.
func scanTournament(sc interface{ Scan(...any) error }) (*Tournament, error) {
	var t Tournament
	var prizes, starts, ends string
	var settled int
	if err := sc.Scan(&t.ID, &t.Title, &t.Description, &t.EntryFee, &prizes, &starts, &ends, &settled); err != nil {
		return nil, err
	}
	_ = json.Unmarshal([]byte(prizes), &t.Prizes)
	t.StartsAt = parseTS(starts)
	t.EndsAt = parseTS(ends)
	t.Settled = settled != 0
	return &t, nil
}

const tournamentCols = `id, title, description, entry_fee, prizes, starts_at, ends_at, settled`

// ListTournaments تورنومنت‌ها را (به‌همراه وضعیتِ عضویتِ کاربر) برمی‌گرداند.
// پیش از خواندن، تورنومنت‌های پایان‌یافته‌ی تسویه‌نشده را تسویه می‌کند.
//
// نکته‌ی مهم: چون pool فقط یک اتصال دارد (SetMaxOpenConns(1))، نباید حین باز بودنِ
// یک rows کوئریِ دیگری زد (self-deadlock). پس ابتدا همه‌ی ردیف‌ها را می‌خوانیم و
// rows را می‌بندیم، سپس زیرکوئری‌های هر تورنومنت را اجرا می‌کنیم.
func (s *Service) ListTournaments(ctx context.Context, userID int64) ([]TournamentView, error) {
	s.SettleTournaments(ctx)

	rows, err := s.db.QueryContext(ctx,
		`SELECT `+tournamentCols+` FROM tournaments ORDER BY (ends_at < strftime('%Y-%m-%d %H:%M:%S','now')), starts_at`)
	if err != nil {
		return nil, err
	}
	var ts []*Tournament
	for rows.Next() {
		t, err := scanTournament(rows)
		if err != nil {
			rows.Close()
			return nil, err
		}
		ts = append(ts, t)
	}
	rows.Close()
	if err := rows.Err(); err != nil {
		return nil, err
	}

	now := nowUTC()
	out := make([]TournamentView, 0, len(ts))
	for _, t := range ts {
		v := TournamentView{
			ID: t.ID, Title: t.Title, Description: t.Description,
			EntryFee: t.EntryFee, Prizes: t.Prizes, PrizePool: sumPrizes(t.Prizes),
			Status:      tournamentStatus(now, t.StartsAt, t.EndsAt),
			StartsAt:    t.StartsAt.Format(time.RFC3339),
			EndsAt:      t.EndsAt.Format(time.RFC3339),
			StartsLocal: t.StartsAt.In(iranZone).Format("2006-01-02 15:04"),
			EndsLocal:   t.EndsAt.In(iranZone).Format("2006-01-02 15:04"),
		}
		_ = s.db.QueryRowContext(ctx,
			`SELECT COUNT(*) FROM tournament_entries WHERE tournament_id = ?`, t.ID).Scan(&v.Players)
		if userID != 0 {
			var pts sql.NullInt64
			if err := s.db.QueryRowContext(ctx,
				`SELECT points FROM tournament_entries WHERE tournament_id = ? AND user_id = ?`,
				t.ID, userID).Scan(&pts); err == nil {
				v.Joined = true
				v.MyPoints = int(pts.Int64)
				_ = s.db.QueryRowContext(ctx,
					`SELECT COUNT(*)+1 FROM tournament_entries WHERE tournament_id = ? AND points > ?`,
					t.ID, pts.Int64).Scan(&v.MyRank)
			}
		}
		out = append(out, v)
	}
	return out, nil
}

// TournamentStandings رده‌بندیِ یک تورنومنت (تا limit نفرِ برتر).
func (s *Service) TournamentStandings(ctx context.Context, id int64, limit int) ([]TournamentStanding, error) {
	if limit <= 0 {
		limit = 50
	}
	rows, err := s.db.QueryContext(ctx,
		`SELECT e.user_id, COALESCE(u.first_name, u.mobile), COALESCE(u.avatar,''),
		        e.points, e.wins, e.games, e.prize
		 FROM tournament_entries e JOIN users u ON u.id = e.user_id
		 WHERE e.tournament_id = ?
		 ORDER BY e.points DESC, e.wins DESC, e.joined_at ASC
		 LIMIT ?`, id, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []TournamentStanding
	rank := 0
	for rows.Next() {
		rank++
		var st TournamentStanding
		var name sql.NullString
		if err := rows.Scan(&st.UserID, &name, &st.Avatar, &st.Points, &st.Wins, &st.Games, &st.Prize); err != nil {
			return nil, err
		}
		st.Rank = rank
		st.Name = name.String
		out = append(out, st)
	}
	return out, rows.Err()
}

// JoinTournament کاربر را ثبت‌نام کرده و ورودی را از سکه کسر می‌کند.
func (s *Service) JoinTournament(ctx context.Context, userID, id int64) error {
	row := s.db.QueryRowContext(ctx, `SELECT `+tournamentCols+` FROM tournaments WHERE id = ?`, id)
	t, err := scanTournament(row)
	if errors.Is(err, sql.ErrNoRows) {
		return ErrTournamentNotFound
	}
	if err != nil {
		return err
	}
	// ثبت‌نام تا پیش از پایانِ تورنومنت مجاز است.
	if nowUTC().After(t.EndsAt) || t.Settled {
		return ErrTournamentClosed
	}
	// آیا قبلاً عضو شده؟
	var exists int
	_ = s.db.QueryRowContext(ctx,
		`SELECT COUNT(*) FROM tournament_entries WHERE tournament_id = ? AND user_id = ?`,
		id, userID).Scan(&exists)
	if exists > 0 {
		return ErrAlreadyJoined
	}

	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	if t.EntryFee > 0 {
		if err := adjustTx(ctx, tx, userID, CurrencyCoin, -t.EntryFee, "tournament_join", tournamentRef(id)); err != nil {
			return err // ErrInsufficient در صورتِ کمبود
		}
	}
	if _, err := tx.ExecContext(ctx,
		`INSERT INTO tournament_entries (tournament_id, user_id) VALUES (?, ?)`, id, userID); err != nil {
		return err
	}
	return tx.Commit()
}

// AddTournamentPoints امتیازِ کاربر را در همه‌ی تورنومنت‌های فعالی که در آن‌ها عضو
// است افزایش می‌دهد (هنگامِ پایانِ هر بازی). fire-and-forget؛ خطاها لاگ نمی‌شوند.
func (s *Service) AddTournamentPoints(ctx context.Context, userID int64, won bool) {
	pts := tourPointsLoss
	winInc := 0
	if won {
		pts = tourPointsWin
		winInc = 1
	}
	now := nowUTC().Format(tsLayout)
	_, _ = s.db.ExecContext(ctx,
		`UPDATE tournament_entries
		 SET points = points + ?, games = games + 1, wins = wins + ?
		 WHERE user_id = ?
		   AND tournament_id IN (
		     SELECT id FROM tournaments
		     WHERE settled = 0 AND starts_at <= ? AND ends_at >= ?
		   )`,
		pts, winInc, userID, now, now)
}

// SettleTournaments تورنومنت‌های پایان‌یافته‌ی تسویه‌نشده را تسویه و جوایز را پرداخت می‌کند.
func (s *Service) SettleTournaments(ctx context.Context) {
	now := nowUTC().Format(tsLayout)
	rows, err := s.db.QueryContext(ctx,
		`SELECT id, prizes FROM tournaments WHERE settled = 0 AND ends_at < ?`, now)
	if err != nil {
		return
	}
	var ids []int64
	prizeMap := map[int64][]int64{}
	for rows.Next() {
		var id int64
		var prizes string
		if err := rows.Scan(&id, &prizes); err != nil {
			continue
		}
		var p []int64
		_ = json.Unmarshal([]byte(prizes), &p)
		ids = append(ids, id)
		prizeMap[id] = p
	}
	rows.Close()

	for _, id := range ids {
		s.settleOne(ctx, id, prizeMap[id])
	}
}

// settleOne یک تورنومنت را در یک تراکنش تسویه می‌کند.
func (s *Service) settleOne(ctx context.Context, id int64, prizes []int64) {
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return
	}
	defer tx.Rollback()

	if len(prizes) > 0 {
		rows, err := tx.QueryContext(ctx,
			`SELECT user_id FROM tournament_entries WHERE tournament_id = ?
			 ORDER BY points DESC, wins DESC, joined_at ASC LIMIT ?`, id, len(prizes))
		if err != nil {
			return
		}
		var winners []int64
		for rows.Next() {
			var uid int64
			if err := rows.Scan(&uid); err != nil {
				rows.Close()
				return
			}
			winners = append(winners, uid)
		}
		rows.Close()

		for i, uid := range winners {
			amt := prizes[i]
			if amt <= 0 {
				continue
			}
			if err := adjustTx(ctx, tx, uid, CurrencyCoin, amt, "tournament_prize", tournamentRef(id)); err != nil {
				return
			}
			if _, err := tx.ExecContext(ctx,
				`UPDATE tournament_entries SET prize = ? WHERE tournament_id = ? AND user_id = ?`,
				amt, id, uid); err != nil {
				return
			}
		}
	}
	if _, err := tx.ExecContext(ctx, `UPDATE tournaments SET settled = 1 WHERE id = ?`, id); err != nil {
		return
	}
	_ = tx.Commit()
}

func tournamentRef(id int64) string {
	return "tournament:" + strconv.FormatInt(id, 10)
}

// --- ادمین: CRUD تورنومنت ---

// CreateTournament یک تورنومنت جدید می‌سازد و شناسه‌اش را برمی‌گرداند.
func (s *Service) CreateTournament(ctx context.Context, t Tournament) (int64, error) {
	prizes, _ := json.Marshal(t.Prizes)
	res, err := s.db.ExecContext(ctx,
		`INSERT INTO tournaments (title, description, entry_fee, prizes, starts_at, ends_at)
		 VALUES (?, ?, ?, ?, ?, ?)`,
		t.Title, t.Description, t.EntryFee, string(prizes),
		t.StartsAt.UTC().Format(tsLayout), t.EndsAt.UTC().Format(tsLayout))
	if err != nil {
		return 0, err
	}
	return res.LastInsertId()
}

// DeleteTournament یک تورنومنت و ثبت‌نام‌هایش را حذف می‌کند.
func (s *Service) DeleteTournament(ctx context.Context, id int64) error {
	if _, err := s.db.ExecContext(ctx, `DELETE FROM tournament_entries WHERE tournament_id = ?`, id); err != nil {
		return err
	}
	_, err := s.db.ExecContext(ctx, `DELETE FROM tournaments WHERE id = ?`, id)
	return err
}

// AdminListTournaments همه‌ی تورنومنت‌ها را برای پنلِ ادمین برمی‌گرداند.
func (s *Service) AdminListTournaments(ctx context.Context) ([]TournamentView, error) {
	return s.ListTournaments(ctx, 0)
}
