package economy

import (
	"context"
	"database/sql"
	"errors"
	"strings"
	"sync"
	"time"
)

// خطاهای دامنه.
var (
	ErrTooSoon       = errors.New("daily reward not ready")
	ErrAdNotVerified = errors.New("ad not verified")
	ErrAdCap         = errors.New("daily ad cap reached")
	ErrAdDuplicate   = errors.New("ad reward already claimed")
	ErrNotFound      = errors.New("item not found")
	ErrAlreadyOwned  = errors.New("already owned")
	ErrNotOwned      = errors.New("card not owned")
	ErrIAPInvalid    = errors.New("purchase not valid")
)

const tsLayout = "2006-01-02 15:04:05"

func nowUTC() time.Time      { return time.Now().UTC() }
func nowStr() string         { return nowUTC().Format(tsLayout) }
func parseTS(s string) time.Time {
	t, _ := time.Parse(tsLayout, s)
	return t
}

// Service سرویس اقتصاد بازی.
type Service struct {
	db  *sql.DB
	ad  AdVerifier
	iap IAPVerifier

	mu  sync.RWMutex // محافظ کشِ کاتالوگ
	cat Catalog      // کشِ کاتالوگ خوانده‌شده از DB
}

func New(db *sql.DB, ad AdVerifier, iap IAPVerifier) *Service {
	return &Service{db: db, ad: ad, iap: iap}
}

// Profile وضعیت اقتصادی کاربر.
type Profile struct {
	Coins        int64   `json:"coins"`
	Tickets      int64   `json:"tickets"`
	XP           int64   `json:"xp"`
	Trophies     int     `json:"trophies"`
	Level        int     `json:"level"`
	XPInto       int64   `json:"xp_into_level"`
	XPNeed       int64   `json:"xp_for_next"`
	VIP          bool    `json:"vip"`
	VIPUntil     *string `json:"vip_until"`
	SelectedCard   string `json:"selected_card"`
	SelectedCarpet string `json:"selected_carpet"`
	RankPoints   int64   `json:"rank_points"`
	RankTier     string  `json:"rank_tier"`  // bronze..king
	RankIndex    int     `json:"rank_index"` // ۰..۴
	Avatar       string  `json:"avatar"`     // seed آواتار (برای نمایش سرِ میز)
}

// GetProfile وضعیت اقتصادی کاربر را می‌خواند.
func (s *Service) GetProfile(ctx context.Context, userID int64) (*Profile, error) {
	var p Profile
	var vipUntil, avatar sql.NullString
	err := s.db.QueryRowContext(ctx,
		`SELECT coins, tickets, xp, trophies, vip_until, selected_card, selected_carpet, rank_points, avatar FROM users WHERE id = ?`, userID).
		Scan(&p.Coins, &p.Tickets, &p.XP, &p.Trophies, &vipUntil, &p.SelectedCard, &p.SelectedCarpet, &p.RankPoints, &avatar)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, err
	}
	p.Avatar = avatar.String
	p.Level, p.XPInto, p.XPNeed = LevelInfo(p.XP)
	p.RankTier, p.RankIndex = RankTier(p.RankPoints)
	if vipUntil.Valid {
		p.VIPUntil = &vipUntil.String
		p.VIP = parseTS(vipUntil.String).After(nowUTC())
	}
	return &p, nil
}

func (s *Service) isVIP(ctx context.Context, userID int64) bool {
	var vu sql.NullString
	if err := s.db.QueryRowContext(ctx, `SELECT vip_until FROM users WHERE id = ?`, userID).Scan(&vu); err != nil {
		return false
	}
	return vu.Valid && parseTS(vu.String).After(nowUTC())
}

// ClaimDaily سکه روزانه را در صورت گذشت ۲۴ ساعت می‌دهد.
func (s *Service) ClaimDaily(ctx context.Context, userID int64) (int64, error) {
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return 0, err
	}
	defer tx.Rollback()

	cutoff := nowUTC().Add(-24 * time.Hour).Format(tsLayout)
	res, err := tx.ExecContext(ctx,
		`UPDATE users SET last_daily_at = ?
		 WHERE id = ? AND (last_daily_at IS NULL OR last_daily_at <= ?)`,
		nowStr(), userID, cutoff)
	if err != nil {
		return 0, err
	}
	if n, _ := res.RowsAffected(); n == 0 {
		return 0, ErrTooSoon
	}
	if err := adjustTx(ctx, tx, userID, CurrencyCoin, DailyReward, "daily_reward", ""); err != nil {
		return 0, err
	}
	if err := tx.Commit(); err != nil {
		return 0, err
	}
	return DailyReward, nil
}

// ClaimAdReward پس از تأیید تبلیغ rewarded، سکه می‌دهد (با ضد تکرار و سقف روزانه).
func (s *Service) ClaimAdReward(ctx context.Context, userID int64, token string) (int64, error) {
	ok, err := s.ad.Verify(ctx, userID, token)
	if err != nil || !ok {
		return 0, ErrAdNotVerified
	}

	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return 0, err
	}
	defer tx.Rollback()

	// سقف روزانه
	since := nowUTC().Add(-24 * time.Hour).Format(tsLayout)
	var cnt int
	if err := tx.QueryRowContext(ctx,
		`SELECT COUNT(*) FROM ad_rewards WHERE user_id = ? AND created_at > ?`, userID, since).Scan(&cnt); err != nil {
		return 0, err
	}
	if cnt >= AdDailyCap {
		return 0, ErrAdCap
	}

	// ثبت توکن یکتا (ضد تکرار)
	if _, err := tx.ExecContext(ctx,
		`INSERT INTO ad_rewards (user_id, token, amount) VALUES (?, ?, ?)`, userID, token, AdReward); err != nil {
		if isUnique(err) {
			return 0, ErrAdDuplicate
		}
		return 0, err
	}
	if err := adjustTx(ctx, tx, userID, CurrencyCoin, AdReward, "ad_reward", token); err != nil {
		return 0, err
	}
	if err := tx.Commit(); err != nil {
		return 0, err
	}
	return AdReward, nil
}

// BuyCard اسکین کارت را با سکه باز می‌کند.
func (s *Service) BuyCard(ctx context.Context, userID int64, cardID string) error {
	skin := s.findCardSkin(cardID)
	if skin == nil {
		return ErrNotFound
	}
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	// آیا قبلاً مالک است؟
	var exists int
	_ = tx.QueryRowContext(ctx,
		`SELECT 1 FROM user_cards WHERE user_id = ? AND card_id = ?`, userID, cardID).Scan(&exists)
	if exists == 1 {
		return ErrAlreadyOwned
	}
	if skin.PriceCoins > 0 {
		if err := adjustTx(ctx, tx, userID, CurrencyCoin, -skin.PriceCoins, "buy_card", cardID); err != nil {
			return err
		}
	}
	if _, err := tx.ExecContext(ctx,
		`INSERT INTO user_cards (user_id, card_id) VALUES (?, ?)`, userID, cardID); err != nil {
		return err
	}
	return tx.Commit()
}

// SelectCard کارت انتخابی کاربر را تنظیم می‌کند (باید مالکش باشد یا کارت پیش‌فرض).
func (s *Service) SelectCard(ctx context.Context, userID int64, cardID string) error {
	if s.findCardSkin(cardID) == nil {
		return ErrNotFound
	}
	if cardID != "simple" {
		var exists int
		_ = s.db.QueryRowContext(ctx,
			`SELECT 1 FROM user_cards WHERE user_id = ? AND card_id = ?`, userID, cardID).Scan(&exists)
		if exists != 1 {
			return ErrNotOwned
		}
	}
	_, err := s.db.ExecContext(ctx, `UPDATE users SET selected_card = ? WHERE id = ?`, cardID, userID)
	return err
}

// OwnedCards فهرست کارت‌های متعلق به کاربر (شامل کارت پیش‌فرض).
func (s *Service) OwnedCards(ctx context.Context, userID int64) ([]string, error) {
	rows, err := s.db.QueryContext(ctx, `SELECT card_id FROM user_cards WHERE user_id = ?`, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	owned := []string{"simple"}
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			return nil, err
		}
		if id != "simple" {
			owned = append(owned, id)
		}
	}
	return owned, rows.Err()
}

// addXPTx مقدار XP را با اعمال بوستر فعال اضافه می‌کند.
func (s *Service) addXPTx(ctx context.Context, tx *sql.Tx, userID int64, base int) error {
	mult := 1
	var boostUntil sql.NullString
	var m int
	if err := tx.QueryRowContext(ctx,
		`SELECT xp_boost_mult, xp_boost_until FROM users WHERE id = ?`, userID).Scan(&m, &boostUntil); err == nil {
		if boostUntil.Valid && parseTS(boostUntil.String).After(nowUTC()) && m > 1 {
			mult = m
		}
	}
	_, err := tx.ExecContext(ctx, `UPDATE users SET xp = xp + ? WHERE id = ?`, base*mult, userID)
	return err
}

// PurchaseRequest درخواست تأیید خرید IAP.
type PurchaseRequest struct {
	Store     string `json:"store"`      // bazaar | myket
	Kind      string `json:"kind"`       // coin | ticket | booster | vip
	ProductID string `json:"product_id"` // شناسه بسته در کاتالوگ (SKU فروشگاه)
	Token     string `json:"token"`      // purchaseToken
	// برای تأیید امضای آفلاین (کافه‌بازار): متنِ خامِ خرید و امضای RSA (base64).
	PurchaseData string `json:"purchase_data"`
	Signature    string `json:"signature"`
}

// VerifyPurchase خرید را تأیید و در صورت معتبر بودن اعتبار/بلیط/VIP/بوستر می‌دهد (idempotent).
func (s *Service) VerifyPurchase(ctx context.Context, userID int64, req PurchaseRequest) error {
	// ابتدا بسته را از کاتالوگ می‌یابیم تا پاداش و SKU (شناسه‌ی محصول در فروشگاه)
	// را به‌دست آوریم؛ سپس با همان SKU خرید را تأیید می‌کنیم.
	var coins, tickets int64
	var vipDays int
	var booster *Booster
	var sku string
	switch req.Kind {
	case "coin":
		p := s.findCoinPackage(req.ProductID)
		if p == nil {
			return ErrNotFound
		}
		coins, vipDays, sku = p.Coins, p.VIPDays, p.SKU
		if s.isVIP(ctx, userID) {
			coins += coins / 10 // پاداش ۱۰٪ VIP
		}
	case "ticket":
		p := s.findTicketPackage(req.ProductID)
		if p == nil {
			return ErrNotFound
		}
		tickets, sku = p.Tickets, p.SKU
	case "booster":
		booster = s.findBooster(req.ProductID)
		if booster == nil {
			return ErrNotFound
		}
		sku = booster.SKU
	case "vip":
		p := s.findVIPPackage(req.ProductID)
		if p == nil {
			return ErrNotFound
		}
		vipDays, sku = p.Months*30, p.SKU // هر ماه = ۳۰ روز
	default:
		return ErrNotFound
	}
	if sku == "" {
		sku = req.ProductID // اگر SKU تنظیم نشده، از شناسه‌ی کاتالوگ استفاده می‌شود
	}

	ok, err := s.iap.Verify(ctx, IAPProof{
		Store:        req.Store,
		ProductID:    sku,
		Token:        req.Token,
		PurchaseData: req.PurchaseData,
		Signature:    req.Signature,
	})
	if err != nil || !ok {
		return ErrIAPInvalid
	}

	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	// ثبت خرید (یکتا بودن store+token ⇒ ضد دوباره‌اعتبارسنجی)
	if _, err := tx.ExecContext(ctx,
		`INSERT INTO purchases (user_id, store, product_id, purchase_token, status, coins, tickets, vip_days)
		 VALUES (?, ?, ?, ?, 'verified', ?, ?, ?)`,
		userID, req.Store, req.ProductID, req.Token, coins, tickets, vipDays); err != nil {
		if isUnique(err) {
			return ErrAlreadyOwned // قبلاً پردازش شده
		}
		return err
	}
	if coins > 0 {
		if err := adjustTx(ctx, tx, userID, CurrencyCoin, coins, "purchase_coin", req.ProductID); err != nil {
			return err
		}
	}
	if tickets > 0 {
		if err := adjustTx(ctx, tx, userID, CurrencyTicket, tickets, "purchase_ticket", req.ProductID); err != nil {
			return err
		}
	}
	if vipDays > 0 {
		if err := extendVIPTx(ctx, tx, userID, vipDays); err != nil {
			return err
		}
	}
	if booster != nil {
		until := laterOf(nowUTC(), time.Time{}).Add(time.Duration(booster.Hours) * time.Hour).Format(tsLayout)
		if _, err := tx.ExecContext(ctx,
			`UPDATE users SET xp_boost_mult = ?, xp_boost_until = ? WHERE id = ?`,
			booster.Multiplier, until, userID); err != nil {
			return err
		}
	}
	return tx.Commit()
}

// extendVIPTx زمان VIP را تمدید می‌کند (از بیشینهٔ اکنون و پایان فعلی).
func extendVIPTx(ctx context.Context, tx *sql.Tx, userID int64, days int) error {
	var cur sql.NullString
	if err := tx.QueryRowContext(ctx, `SELECT vip_until FROM users WHERE id = ?`, userID).Scan(&cur); err != nil {
		return err
	}
	base := nowUTC()
	if cur.Valid {
		if t := parseTS(cur.String); t.After(base) {
			base = t
		}
	}
	until := base.Add(time.Duration(days) * 24 * time.Hour).Format(tsLayout)
	_, err := tx.ExecContext(ctx, `UPDATE users SET vip_until = ? WHERE id = ?`, until, userID)
	return err
}

// GrantVIPDays به‌صورت دستی (ادمین) اشتراک VIP را تمدید می‌کند.
func (s *Service) GrantVIPDays(ctx context.Context, userID int64, days int) error {
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	if err := extendVIPTx(ctx, tx, userID, days); err != nil {
		return err
	}
	return tx.Commit()
}

// GrantCard به‌صورت دستی (ادمین) یک اسکین کارت را برای کاربر باز می‌کند.
func (s *Service) GrantCard(ctx context.Context, userID int64, cardID string) error {
	if s.findCardSkin(cardID) == nil {
		return ErrNotFound
	}
	_, err := s.db.ExecContext(ctx,
		`INSERT OR IGNORE INTO user_cards (user_id, card_id) VALUES (?, ?)`, userID, cardID)
	return err
}

func laterOf(a, b time.Time) time.Time {
	if a.After(b) {
		return a
	}
	return b
}

// --- تسویه‌ی داخل بازی ---

// ChargeEntry ورودی میز را از کاربر کسر می‌کند.
func (s *Service) ChargeEntry(ctx context.Context, userID, entry int64, ref string) error {
	if entry <= 0 {
		return nil
	}
	return s.Adjust(ctx, userID, CurrencyCoin, -entry, "table_entry", ref)
}

// Refund ورودی میز را بازمی‌گرداند (در صورت لغو بازی).
func (s *Service) Refund(ctx context.Context, userID, entry int64, ref string) error {
	if entry <= 0 {
		return nil
	}
	return s.Adjust(ctx, userID, CurrencyCoin, entry, "table_refund", ref)
}

// AwardWinner جایزه، XP و جام را به برنده می‌دهد.
func (s *Service) AwardWinner(ctx context.Context, userID, prize int64, xp, trophy int, ref string) error {
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()
	if prize > 0 {
		if err := adjustTx(ctx, tx, userID, CurrencyCoin, prize, "table_prize", ref); err != nil {
			return err
		}
	}
	if xp > 0 {
		if err := s.addXPTx(ctx, tx, userID, xp); err != nil {
			return err
		}
	}
	if trophy > 0 {
		if _, err := tx.ExecContext(ctx, `UPDATE users SET trophies = trophies + ? WHERE id = ?`, trophy, userID); err != nil {
			return err
		}
	}
	return tx.Commit()
}

// RecordGame نتیجه‌ی بازی را در تاریخچه ثبت می‌کند.
func (s *Service) RecordGame(ctx context.Context, room, playersJSON string, winnerTeam int, kot bool) error {
	_, err := s.db.ExecContext(ctx,
		`INSERT INTO game_history (room, players_json, winner_team, kot) VALUES (?, ?, ?, ?)`,
		room, playersJSON, winnerTeam, boolToInt(kot))
	return err
}

func boolToInt(b bool) int {
	if b {
		return 1
	}
	return 0
}

func isUnique(err error) bool {
	return err != nil && strings.Contains(strings.ToLower(err.Error()), "unique")
}
