// پکیج admin یک پنل ادمینِ سرور‌سایدِ سبک (HTML) برای مدیریت فروشگاه و کاربران است.
// همه‌ی صفحات پشت Basic Auth هستند و در همان باینری Go سرو می‌شوند (بدون پروسه‌ی جدا).
package admin

import (
	"archive/zip"
	"context"
	"crypto/rand"
	"database/sql"
	"embed"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"

	"hakemsho/internal/economy"
)

//go:embed templates/*.html
var tplFS embed.FS

type Handler struct {
	db         *sql.DB
	eco        *economy.Service
	tpl        *template.Template
	cardsDir   string // پوشه‌ی دیسکیِ آپلودِ اسکین‌ها (همان مسیری که static از آن سرو می‌کند)
	carpetsDir string // پوشه‌ی دیسکیِ آپلودِ تصاویرِ فرش
}

func New(db *sql.DB, eco *economy.Service, cardsDir, carpetsDir string) *Handler {
	tpl := template.Must(template.ParseFS(tplFS, "templates/*.html"))
	return &Handler{db: db, eco: eco, tpl: tpl, cardsDir: cardsDir, carpetsDir: carpetsDir}
}

// Routes زیرروترِ /admin را با Basic Auth برمی‌گرداند.
func (h *Handler) Routes(user, pass string) http.Handler {
	r := chi.NewRouter()
	r.Use(middleware.BasicAuth("hakemsho-admin", map[string]string{user: pass}))
	r.Get("/", h.dashboard)
	r.Get("/shop", h.shop)
	r.Post("/shop/{kind}/add", h.addItem)
	r.Post("/shop/{kind}/delete", h.deleteItem)
	r.Post("/shop/card/{id}/upload", h.uploadDeck)
	r.Post("/shop/{kind}", h.updateItem)
	r.Get("/users", h.users)
	r.Post("/users/coins", h.adjustCoins)
	r.Post("/users/grant", h.grantToUser)
	r.Post("/season/reset", h.resetSeason)
	r.Get("/carpets", h.carpetsAdmin)
	r.Post("/carpets/add", h.carpetAdd)
	r.Post("/carpets/delete", h.carpetDelete)
	r.Post("/carpets/{id}/upload", h.carpetUpload)
	r.Get("/chat", h.chatAdmin)
	r.Post("/chat/pack/add", h.chatPackAdd)
	r.Post("/chat/pack/delete", h.chatPackDelete)
	r.Post("/chat/message/add", h.chatMessageAdd)
	r.Post("/chat/message", h.chatMessageUpdate)
	r.Post("/chat/message/delete", h.chatMessageDelete)
	r.Get("/tournaments", h.tournamentsAdmin)
	r.Post("/tournaments/add", h.tournamentAdd)
	r.Post("/tournaments/delete", h.tournamentDelete)
	return r
}

func (h *Handler) render(w http.ResponseWriter, page string, data any) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := h.tpl.ExecuteTemplate(w, page, data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

// --- داشبورد ---

func (h *Handler) dashboard(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	count := func(q string) int64 {
		var n int64
		_ = h.db.QueryRowContext(ctx, q).Scan(&n)
		return n
	}
	data := map[string]any{
		"Users":     count(`SELECT COUNT(*) FROM users`),
		"Purchases": count(`SELECT COUNT(*) FROM purchases WHERE status='verified'`),
		"Games":     count(`SELECT COUNT(*) FROM game_history`),
		"Coins":     count(`SELECT COALESCE(SUM(coins),0) FROM users`),
		"Season":    h.eco.Season(ctx),
		"Saved":     r.URL.Query().Get("saved") == "1",
		"Nav":       "dashboard",
	}
	h.render(w, "dashboard.html", data)
}

// --- فروشگاه (ویرایش کاتالوگ) ---

func (h *Handler) shop(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	data := map[string]any{
		"Nav": "shop",
		"Coins": h.rows(ctx, `SELECT id,title,coins,vip_days,price_toman,bonus_pct,sku,sort,enabled FROM coin_packages ORDER BY sort`,
			"id", "title", "coins", "vip_days", "price_toman", "bonus_pct", "sku", "sort", "enabled"),
		"Tickets": h.rows(ctx, `SELECT id,title,tickets,price_toman,sku,sort,enabled FROM ticket_packages ORDER BY sort`,
			"id", "title", "tickets", "price_toman", "sku", "sort", "enabled"),
		"Cards": h.rows(ctx, `SELECT id,title,price_coins,sort,enabled FROM card_skins ORDER BY sort`,
			"id", "title", "price_coins", "sort", "enabled"),
		"Boosters": h.rows(ctx, `SELECT id,title,multiplier,hours,price_toman,sku,sort,enabled FROM boosters ORDER BY sort`,
			"id", "title", "multiplier", "hours", "price_toman", "sku", "sort", "enabled"),
		"VIP": h.rows(ctx, `SELECT id,title,months,price_toman,sku,sort,enabled FROM vip_packages ORDER BY sort`,
			"id", "title", "months", "price_toman", "sku", "sort", "enabled"),
		"Tiers": h.rows(ctx, `SELECT id,title,hands,entry,prize,xp,trophy,rank_reward,sort,enabled FROM table_tiers ORDER BY sort`,
			"id", "title", "hands", "entry", "prize", "xp", "trophy", "rank_reward", "sort", "enabled"),
		"Saved": r.URL.Query().Get("saved") == "1",
	}
	h.render(w, "shop.html", data)
}

// updateItem یک ردیفِ کاتالوگ را بر اساس kind به‌روزرسانی و کش را تازه می‌کند.
func (h *Handler) updateItem(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, "bad form", http.StatusBadRequest)
		return
	}
	kind := chi.URLParam(r, "kind")
	id := r.FormValue("id")
	f := func(k string) int { v, _ := strconv.Atoi(r.FormValue(k)); return v }
	en := 0
	if r.FormValue("enabled") == "on" {
		en = 1
	}

	var q string
	var args []any
	switch kind {
	case "coin":
		q = `UPDATE coin_packages SET title=?,coins=?,vip_days=?,price_toman=?,bonus_pct=?,sort=?,enabled=? WHERE id=?`
		args = []any{r.FormValue("title"), f("coins"), f("vip_days"), f("price_toman"), f("bonus_pct"), f("sort"), en, id}
	case "ticket":
		q = `UPDATE ticket_packages SET title=?,tickets=?,price_toman=?,sort=?,enabled=? WHERE id=?`
		args = []any{r.FormValue("title"), f("tickets"), f("price_toman"), f("sort"), en, id}
	case "card":
		q = `UPDATE card_skins SET title=?,price_coins=?,sort=?,enabled=? WHERE id=?`
		args = []any{r.FormValue("title"), f("price_coins"), f("sort"), en, id}
	case "booster":
		q = `UPDATE boosters SET title=?,multiplier=?,hours=?,price_toman=?,sort=?,enabled=? WHERE id=?`
		args = []any{r.FormValue("title"), f("multiplier"), f("hours"), f("price_toman"), f("sort"), en, id}
	case "vip":
		q = `UPDATE vip_packages SET title=?,months=?,price_toman=?,sort=?,enabled=? WHERE id=?`
		args = []any{r.FormValue("title"), f("months"), f("price_toman"), f("sort"), en, id}
	case "tier":
		q = `UPDATE table_tiers SET title=?,hands=?,entry=?,prize=?,xp=?,trophy=?,rank_reward=?,sort=?,enabled=? WHERE id=?`
		args = []any{r.FormValue("title"), f("hands"), f("entry"), f("prize"), f("xp"), f("trophy"), f("rank_reward"), f("sort"), en, id}
	default:
		http.Error(w, "unknown kind", http.StatusBadRequest)
		return
	}

	if _, err := h.db.ExecContext(r.Context(), q, args...); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	_ = h.eco.LoadCatalog(r.Context()) // تازه‌سازی کشِ کاتالوگ
	http.Redirect(w, r, "/admin/shop?saved=1", http.StatusSeeOther)
}

// addItem یک ردیفِ جدید به کاتالوگ اضافه می‌کند (افزودن محصول).
func (h *Handler) addItem(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, "bad form", http.StatusBadRequest)
		return
	}
	kind := chi.URLParam(r, "kind")
	id := r.FormValue("id")
	if id == "" {
		http.Error(w, "id required", http.StatusBadRequest)
		return
	}
	f := func(k string) int { v, _ := strconv.Atoi(r.FormValue(k)); return v }
	en := 0
	if r.FormValue("enabled") == "on" {
		en = 1
	}
	sku := newUUID() // شناسه‌ی محصولِ فروشگاه (uuid) خودکار تولید می‌شود

	var q string
	var args []any
	switch kind {
	case "coin":
		q = `INSERT INTO coin_packages (id,title,coins,vip_days,price_toman,bonus_pct,sku,sort,enabled) VALUES (?,?,?,?,?,?,?,?,?)`
		args = []any{id, r.FormValue("title"), f("coins"), f("vip_days"), f("price_toman"), f("bonus_pct"), sku, f("sort"), en}
	case "ticket":
		q = `INSERT INTO ticket_packages (id,title,tickets,price_toman,sku,sort,enabled) VALUES (?,?,?,?,?,?,?)`
		args = []any{id, r.FormValue("title"), f("tickets"), f("price_toman"), sku, f("sort"), en}
	case "card":
		q = `INSERT INTO card_skins (id,title,price_coins,sort,enabled) VALUES (?,?,?,?,?)`
		args = []any{id, r.FormValue("title"), f("price_coins"), f("sort"), en}
	case "booster":
		q = `INSERT INTO boosters (id,title,multiplier,hours,price_toman,sku,sort,enabled) VALUES (?,?,?,?,?,?,?)`
		args = []any{id, r.FormValue("title"), f("multiplier"), f("hours"), f("price_toman"), sku, f("sort"), en}
	case "vip":
		q = `INSERT INTO vip_packages (id,title,months,price_toman,sku,sort,enabled) VALUES (?,?,?,?,?,?,?)`
		args = []any{id, r.FormValue("title"), f("months"), f("price_toman"), sku, f("sort"), en}
	case "tier":
		q = `INSERT INTO table_tiers (id,title,hands,entry,prize,xp,trophy,rank_reward,sort,enabled) VALUES (?,?,?,?,?,?,?,?,?,?)`
		args = []any{id, r.FormValue("title"), f("hands"), f("entry"), f("prize"), f("xp"), f("trophy"), f("rank_reward"), f("sort"), en}
	default:
		http.Error(w, "unknown kind", http.StatusBadRequest)
		return
	}

	if _, err := h.db.ExecContext(r.Context(), q, args...); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	_ = h.eco.LoadCatalog(r.Context())
	http.Redirect(w, r, "/admin/shop?saved=1", http.StatusSeeOther)
}

// validCardFile نامِ کانونیِ خروجی را برای یک ورودیِ zip برمی‌گرداند (یا ok=false).
// مجاز: back.(jpg|png) → "back.jpg"  و  <کد>.png → "<کدِ بزرگ>.png"
// که <کد> = رتبه (A,2..10,J,Q,K) + خال (S,H,D,C)، مثل AS.png یا 10H.png.
// خروجی همیشه «کدِ بزرگ + پسوندِ کوچک» است تا با درخواست‌های اپ روی فایل‌سیستمِ
// حساس‌به‌حروف (لینوکس) دقیقاً مطابقت کند.
func validCardFile(name string) (string, bool) {
	base := filepath.Base(name)
	low := strings.ToLower(base)
	if low == "back.jpg" || low == "back.png" {
		return "back.jpg", true // اپ پشتِ کارت را با نامِ back.jpg می‌خواهد
	}
	if !strings.HasSuffix(low, ".png") {
		return "", false
	}
	code := strings.ToUpper(base[:len(base)-len(".png")])
	suits := "SHDC"
	if len(code) < 2 || !strings.Contains(suits, code[len(code)-1:]) {
		return "", false
	}
	ranks := map[string]bool{"A": true, "2": true, "3": true, "4": true, "5": true,
		"6": true, "7": true, "8": true, "9": true, "10": true, "J": true, "Q": true, "K": true}
	if !ranks[code[:len(code)-1]] {
		return "", false
	}
	return code + ".png", true
}

// uploadDeck یک فایلِ zip شاملِ تصاویرِ کارت را برای یک اسکین آپلود می‌کند و
// در پوشه‌ی دیسکیِ ماندگار (cardsDir/<id>/) استخراج می‌کند تا بدونِ کامپایلِ
// دوباره از همان مسیرِ /cards/<id>/... سرو شود.
func (h *Handler) uploadDeck(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	if id == "" || strings.ContainsAny(id, "/\\.") {
		http.Error(w, "invalid skin id", http.StatusBadRequest)
		return
	}
	if h.cardsDir == "" {
		http.Error(w, "CARDS_DIR تنظیم نشده است", http.StatusInternalServerError)
		return
	}
	if err := r.ParseMultipartForm(64 << 20); err != nil { // حداکثر ۶۴MB
		http.Error(w, "bad upload", http.StatusBadRequest)
		return
	}
	file, _, err := r.FormFile("deck")
	if err != nil {
		http.Error(w, "deck (zip) لازم است", http.StatusBadRequest)
		return
	}
	defer file.Close()
	buf, err := io.ReadAll(io.LimitReader(file, 64<<20))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	zr, err := zip.NewReader(strings.NewReader(string(buf)), int64(len(buf)))
	if err != nil {
		http.Error(w, "فایل zip معتبر نیست", http.StatusBadRequest)
		return
	}

	dir := filepath.Join(h.cardsDir, id)
	if err := os.MkdirAll(dir, 0o755); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	written := 0
	for _, zf := range zr.File {
		if zf.FileInfo().IsDir() {
			continue
		}
		out, ok := validCardFile(zf.Name)
		if !ok {
			continue // فایل‌های نامربوط/ناقص نادیده گرفته می‌شوند
		}
		if err := extractZipEntry(zf, filepath.Join(dir, out)); err != nil {
			http.Error(w, fmt.Sprintf("استخراج %s: %v", zf.Name, err), http.StatusInternalServerError)
			return
		}
		written++
	}
	if written == 0 {
		http.Error(w, "هیچ تصویرِ کارتِ معتبری در zip یافت نشد (مثل AS.png، 10H.png، back.jpg)", http.StatusBadRequest)
		return
	}
	http.Redirect(w, r, "/admin/shop?saved=1", http.StatusSeeOther)
}

func extractZipEntry(zf *zip.File, dst string) error {
	rc, err := zf.Open()
	if err != nil {
		return err
	}
	defer rc.Close()
	f, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(f, io.LimitReader(rc, 16<<20)) // سقفِ هر فایل ۱۶MB
	return err
}

// deleteItem یک ردیفِ کاتالوگ را حذف می‌کند.
func (h *Handler) deleteItem(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, "bad form", http.StatusBadRequest)
		return
	}
	tables := map[string]string{
		"coin": "coin_packages", "ticket": "ticket_packages", "card": "card_skins",
		"booster": "boosters", "vip": "vip_packages", "tier": "table_tiers",
	}
	tbl, ok := tables[chi.URLParam(r, "kind")]
	if !ok {
		http.Error(w, "unknown kind", http.StatusBadRequest)
		return
	}
	if _, err := h.db.ExecContext(r.Context(),
		"DELETE FROM "+tbl+" WHERE id = ?", r.FormValue("id")); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	_ = h.eco.LoadCatalog(r.Context())
	http.Redirect(w, r, "/admin/shop?saved=1", http.StatusSeeOther)
}

// resetSeason امتیازِ رتبه‌ی همه را صفر کرده و فصلِ جدید را آغاز می‌کند.
func (h *Handler) resetSeason(w http.ResponseWriter, r *http.Request) {
	if err := h.eco.ResetSeason(r.Context()); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/?saved=1", http.StatusSeeOther)
}

// --- کاربران ---

func (h *Handler) users(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	q := r.URL.Query().Get("q")
	query := `SELECT id, mobile, coins, tickets, xp, trophies, is_admin FROM users`
	var args []any
	if q != "" {
		query += ` WHERE mobile LIKE ?`
		args = append(args, "%"+q+"%")
	}
	query += ` ORDER BY id DESC LIMIT 100`

	rows, err := h.db.QueryContext(ctx, query, args...)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()
	type userRow struct {
		ID                          int64
		Mobile                      string
		Coins, Tickets, XP, Trophy  int64
		Level                       int
		IsAdmin                     bool
	}
	var list []userRow
	for rows.Next() {
		var u userRow
		var admin int
		if err := rows.Scan(&u.ID, &u.Mobile, &u.Coins, &u.Tickets, &u.XP, &u.Trophy, &admin); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		u.IsAdmin = admin == 1
		u.Level, _, _ = economy.LevelInfo(u.XP)
		list = append(list, u)
	}
	h.render(w, "users.html", map[string]any{
		"Nav":   "users",
		"Users": list,
		"Q":     q,
		"Saved": r.URL.Query().Get("saved") == "1",
	})
}

// adjustCoins سکه‌ی یک کاربر را افزایش/کاهش می‌دهد (لاگ در wallet_tx).
func (h *Handler) adjustCoins(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, "bad form", http.StatusBadRequest)
		return
	}
	userID, _ := strconv.ParseInt(r.FormValue("user_id"), 10, 64)
	amount, _ := strconv.ParseInt(r.FormValue("amount"), 10, 64)
	if userID > 0 && amount != 0 {
		_ = h.eco.Adjust(r.Context(), userID, economy.CurrencyCoin, amount, "admin_adjust", "")
	}
	http.Redirect(w, r, "/admin/users?saved=1", http.StatusSeeOther)
}

// grantToUser یک محصول (سکه/بلیط/VIP/کارت) را به کاربر اعطا می‌کند.
func (h *Handler) grantToUser(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, "bad form", http.StatusBadRequest)
		return
	}
	userID, _ := strconv.ParseInt(r.FormValue("user_id"), 10, 64)
	kind := r.FormValue("kind")
	value := r.FormValue("value")
	ctx := r.Context()
	if userID <= 0 {
		http.Redirect(w, r, "/admin/users", http.StatusSeeOther)
		return
	}

	var err error
	switch kind {
	case "coins":
		amount, _ := strconv.ParseInt(value, 10, 64)
		err = h.eco.Adjust(ctx, userID, economy.CurrencyCoin, amount, "admin_grant", "")
	case "tickets":
		amount, _ := strconv.ParseInt(value, 10, 64)
		err = h.eco.Adjust(ctx, userID, economy.CurrencyTicket, amount, "admin_grant", "")
	case "vip":
		days, _ := strconv.Atoi(value)
		err = h.eco.GrantVIPDays(ctx, userID, days)
	case "card":
		err = h.eco.GrantCard(ctx, userID, value)
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	http.Redirect(w, r, "/admin/users?saved=1", http.StatusSeeOther)
}

// rows یک کوئری را به []map[string]any تبدیل می‌کند (برای رندرِ عمومیِ جدول‌ها).
// --- مدیریتِ فرش‌ها ---

func (h *Handler) carpetsAdmin(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	carpets := h.rows(ctx,
		`SELECT id,title,price_coins,vip,sort,enabled FROM carpets ORDER BY sort`,
		"id", "title", "price_coins", "vip", "sort", "enabled")
	h.render(w, "carpets.html", map[string]any{
		"Nav":     "carpets",
		"Carpets": carpets,
		"Saved":   r.URL.Query().Get("saved") == "1",
	})
}

func (h *Handler) carpetAdd(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	id := r.FormValue("id")
	if id == "" || strings.ContainsAny(id, "/\\.") {
		http.Error(w, "invalid id", http.StatusBadRequest)
		return
	}
	num := func(k string) int { v, _ := strconv.Atoi(r.FormValue(k)); return v }
	vip := 0
	if r.FormValue("vip") == "on" {
		vip = 1
	}
	_, err := h.db.ExecContext(r.Context(),
		`INSERT OR REPLACE INTO carpets (id,title,price_coins,vip,sort,enabled) VALUES (?,?,?,?,?,1)`,
		id, r.FormValue("title"), num("price_coins"), vip, num("sort"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/carpets?saved=1", http.StatusSeeOther)
}

func (h *Handler) carpetDelete(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	_, _ = h.db.ExecContext(r.Context(), `DELETE FROM carpets WHERE id=?`, r.FormValue("id"))
	http.Redirect(w, r, "/admin/carpets?saved=1", http.StatusSeeOther)
}

// carpetUpload یک تصویرِ jpg/png را برای یک فرش آپلود و با نامِ <id>.jpg ذخیره می‌کند.
func (h *Handler) carpetUpload(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	if id == "" || strings.ContainsAny(id, "/\\.") {
		http.Error(w, "invalid id", http.StatusBadRequest)
		return
	}
	if h.carpetsDir == "" {
		http.Error(w, "CARPETS_DIR تنظیم نشده است", http.StatusInternalServerError)
		return
	}
	if err := r.ParseMultipartForm(16 << 20); err != nil {
		http.Error(w, "bad upload", http.StatusBadRequest)
		return
	}
	file, _, err := r.FormFile("image")
	if err != nil {
		http.Error(w, "image لازم است", http.StatusBadRequest)
		return
	}
	defer file.Close()
	if err := os.MkdirAll(h.carpetsDir, 0o755); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	out, err := os.Create(filepath.Join(h.carpetsDir, id+".jpg"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer out.Close()
	if _, err := io.Copy(out, io.LimitReader(file, 16<<20)); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/carpets?saved=1", http.StatusSeeOther)
}

// --- مدیریتِ بسته‌های چت و پیام‌ها ---

func (h *Handler) chatAdmin(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	packs := h.rows(ctx,
		`SELECT id,title,kind,price_coins,vip,sort,enabled FROM chat_packs ORDER BY sort`,
		"id", "title", "kind", "price_coins", "vip", "sort", "enabled")
	// پیام‌های هر بسته را زیرِ همان بسته قرار می‌دهیم.
	for _, p := range packs {
		id, _ := p["id"].(string)
		p["messages"] = h.messagesOf(ctx, id)
	}
	h.render(w, "chat.html", map[string]any{
		"Nav":   "chat",
		"Packs": packs,
		"Saved": r.URL.Query().Get("saved") == "1",
	})
}

func (h *Handler) messagesOf(ctx context.Context, packID string) []map[string]any {
	rs, err := h.db.QueryContext(ctx,
		`SELECT id,body,sort FROM chat_messages WHERE pack_id=? ORDER BY sort,id`, packID)
	if err != nil {
		return nil
	}
	defer rs.Close()
	var out []map[string]any
	for rs.Next() {
		var id, sort int
		var body string
		if err := rs.Scan(&id, &body, &sort); err != nil {
			return out
		}
		out = append(out, map[string]any{"id": id, "body": body, "sort": sort})
	}
	return out
}

func (h *Handler) chatPackAdd(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	id := r.FormValue("id")
	if id == "" {
		http.Error(w, "id required", http.StatusBadRequest)
		return
	}
	num := func(k string) int { v, _ := strconv.Atoi(r.FormValue(k)); return v }
	vip := 0
	if r.FormValue("vip") == "on" {
		vip = 1
	}
	kind := r.FormValue("kind")
	if kind != "emoji" {
		kind = "text"
	}
	_, err := h.db.ExecContext(r.Context(),
		`INSERT OR REPLACE INTO chat_packs (id,title,kind,price_coins,vip,sort,enabled) VALUES (?,?,?,?,?,?,1)`,
		id, r.FormValue("title"), kind, num("price_coins"), vip, num("sort"))
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/chat?saved=1", http.StatusSeeOther)
}

func (h *Handler) chatPackDelete(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	id := r.FormValue("id")
	_, _ = h.db.ExecContext(r.Context(), `DELETE FROM chat_messages WHERE pack_id=?`, id)
	_, _ = h.db.ExecContext(r.Context(), `DELETE FROM chat_packs WHERE id=?`, id)
	http.Redirect(w, r, "/admin/chat?saved=1", http.StatusSeeOther)
}

func (h *Handler) chatMessageAdd(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	pack := r.FormValue("pack_id")
	body := r.FormValue("body")
	if pack == "" || body == "" {
		http.Redirect(w, r, "/admin/chat", http.StatusSeeOther)
		return
	}
	sort, _ := strconv.Atoi(r.FormValue("sort"))
	_, err := h.db.ExecContext(r.Context(),
		`INSERT INTO chat_messages (pack_id,body,sort) VALUES (?,?,?)`, pack, body, sort)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/chat?saved=1", http.StatusSeeOther)
}

func (h *Handler) chatMessageUpdate(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	id, _ := strconv.Atoi(r.FormValue("id"))
	body := r.FormValue("body")
	if id == 0 || body == "" {
		http.Redirect(w, r, "/admin/chat", http.StatusSeeOther)
		return
	}
	sort, _ := strconv.Atoi(r.FormValue("sort"))
	if _, err := h.db.ExecContext(r.Context(),
		`UPDATE chat_messages SET body=?, sort=? WHERE id=?`, body, sort, id); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/chat?saved=1", http.StatusSeeOther)
}

func (h *Handler) chatMessageDelete(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	id, _ := strconv.Atoi(r.FormValue("id"))
	_, _ = h.db.ExecContext(r.Context(), `DELETE FROM chat_messages WHERE id=?`, id)
	http.Redirect(w, r, "/admin/chat?saved=1", http.StatusSeeOther)
}

// newUUID یک UUIDv4 برای شناسه‌ی محصولِ فروشگاه (SKU) تولید می‌کند.
func newUUID() string {
	var b [16]byte
	if _, err := rand.Read(b[:]); err != nil {
		return fmt.Sprintf("sku-%d", time.Now().UnixNano())
	}
	b[6] = (b[6] & 0x0f) | 0x40 // نسخه ۴
	b[8] = (b[8] & 0x3f) | 0x80 // variant
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}

func (h *Handler) rows(ctx context.Context, query string, cols ...string) []map[string]any {
	rs, err := h.db.QueryContext(ctx, query)
	if err != nil {
		return nil
	}
	defer rs.Close()
	var out []map[string]any
	for rs.Next() {
		vals := make([]any, len(cols))
		ptrs := make([]any, len(cols))
		for i := range vals {
			ptrs[i] = &vals[i]
		}
		if err := rs.Scan(ptrs...); err != nil {
			return out
		}
		m := make(map[string]any, len(cols))
		for i, c := range cols {
			m[c] = vals[i]
		}
		out = append(out, m)
	}
	return out
}

// --- مدیریتِ تورنومنت‌ها ---

func (h *Handler) tournamentsAdmin(w http.ResponseWriter, r *http.Request) {
	list, _ := h.eco.AdminListTournaments(r.Context())
	h.render(w, "tournaments.html", map[string]any{
		"Nav":         "tournaments",
		"Tournaments": list,
		"Saved":       r.URL.Query().Get("saved") == "1",
	})
}

func (h *Handler) tournamentAdd(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	num := func(k string) int64 { v, _ := strconv.ParseInt(r.FormValue(k), 10, 64); return v }
	// جوایز: مقادیرِ سکه با کاما جدا شده ("۵۰۰۰,۳۰۰۰,۱۰۰۰").
	var prizes []int64
	for _, p := range strings.Split(r.FormValue("prizes"), ",") {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		if v, err := strconv.ParseInt(p, 10, 64); err == nil {
			prizes = append(prizes, v)
		}
	}
	// ورودیِ datetime-local به وقتِ تهران (UTC+3:30) تفسیر و به UTC ذخیره می‌شود.
	iran := time.FixedZone("IRST", 12600)
	parseDT := func(k string) time.Time {
		t, err := time.ParseInLocation("2006-01-02T15:04", r.FormValue(k), iran)
		if err != nil {
			return time.Now().UTC()
		}
		return t.UTC()
	}
	if r.FormValue("title") == "" {
		http.Error(w, "عنوان لازم است", http.StatusBadRequest)
		return
	}
	_, err := h.eco.CreateTournament(r.Context(), economy.Tournament{
		Title:       r.FormValue("title"),
		Description: r.FormValue("description"),
		EntryFee:    num("entry_fee"),
		Prizes:      prizes,
		StartsAt:    parseDT("starts_at"),
		EndsAt:      parseDT("ends_at"),
	})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/admin/tournaments?saved=1", http.StatusSeeOther)
}

func (h *Handler) tournamentDelete(w http.ResponseWriter, r *http.Request) {
	_ = r.ParseForm()
	id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64)
	_ = h.eco.DeleteTournament(r.Context(), id)
	http.Redirect(w, r, "/admin/tournaments?saved=1", http.StatusSeeOther)
}
