package bot

import (
	"fmt"
	"log"
	"strconv"
	"strings"
	"sync"
	"time"

	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"

	"go-helper/internal/chatgpt"
	"go-helper/internal/config"
	"go-helper/internal/database"
	"go-helper/internal/model"
	"go-helper/internal/redeem"
)

// chatSession tracks a two-step flow state per user (redeem or login).
type chatSession struct {
	flowType   string // "redeem" or "login"
	code       string // redemption code (for redeem flow)
	loginMsgID int    // message ID of the login link message (for login flow)
}

// Bot wraps the Telegram bot with application dependencies.
type Bot struct {
	api       *tgbotapi.BotAPI
	db        *database.DB
	cfg       *config.Config
	client    *chatgpt.Client
	oauth     *chatgpt.OAuthManager
	sessions  map[int64]*chatSession // chatID -> session
	panelMsgs map[int64]int          // chatID -> last panel message ID
	mu        sync.Mutex
}

// Start initialises and runs the Telegram bot (blocking).
func Start(db *database.DB, cfg *config.Config, client *chatgpt.Client, oauth *chatgpt.OAuthManager) {
	api, err := tgbotapi.NewBotAPI(cfg.TelegramBotToken)
	if err != nil {
		log.Fatalf("[Bot] 启动失败: %v", err)
	}
	log.Printf("[Bot] 已登录: @%s", api.Self.UserName)

	b := &Bot{
		api:       api,
		db:        db,
		cfg:       cfg,
		client:    client,
		oauth:     oauth,
		sessions:  make(map[int64]*chatSession),
		panelMsgs: make(map[int64]int),
	}

	// Register commands with Telegram.
	b.registerCommands()

	u := tgbotapi.NewUpdate(0)
	u.Timeout = 60
	updates := api.GetUpdatesChan(u)

	for update := range updates {
		if update.CallbackQuery != nil {
			go b.handleCallbackQuery(update.CallbackQuery)
			continue
		}
		if update.Message == nil {
			continue
		}
		go b.handleMessage(update.Message)
	}
}

// isAdmin checks both config and database for admin rights.
func (b *Bot) isAdmin(userID int64) bool {
	if b.cfg.IsAdmin(userID) {
		return true
	}
	isAdmin, _ := b.db.IsAdmin(userID)
	return isAdmin
}

func (b *Bot) registerCommands() {
	// Clear old admin-scoped commands first.
	for _, adminID := range b.cfg.TelegramAdminIDs {
		delCfg := tgbotapi.NewDeleteMyCommandsWithScope(
			tgbotapi.NewBotCommandScopeChat(adminID),
		)
		b.api.Request(delCfg)
	}

	// Only register /start for all users.
	cmds := []tgbotapi.BotCommand{
		{Command: "start", Description: "打开控制面板"},
	}

	cfg := tgbotapi.NewSetMyCommands(cmds...)
	if _, err := b.api.Request(cfg); err != nil {
		log.Printf("[Bot] 注册命令失败: %v", err)
	}

	log.Printf("[Bot] 命令注册完成")
}

func (b *Bot) handleCallbackQuery(cq *tgbotapi.CallbackQuery) {
	chatID := cq.Message.Chat.ID
	userID := cq.From.ID
	msgID := cq.Message.MessageID

	// Answer the callback to remove the loading indicator.
	b.api.Request(tgbotapi.NewCallback(cq.ID, ""))

	switch cq.Data {
	case "cmd:back":
		b.sendMainMenu(chatID, userID, msgID)
		return
	case "cmd:cancel":
		b.mu.Lock()
		_, had := b.sessions[chatID]
		delete(b.sessions, chatID)
		b.mu.Unlock()
		if had {
			b.sendMainMenu(chatID, userID, msgID)
		}
		return
	case "cmd:cancel_to_list":
		b.mu.Lock()
		_, had := b.sessions[chatID]
		delete(b.sessions, chatID)
		b.mu.Unlock()
		if had {
			b.callbackListAccounts(chatID, msgID)
		}
		return
	case "cmd:cancel_to_admin":
		b.mu.Lock()
		_, had := b.sessions[chatID]
		delete(b.sessions, chatID)
		b.mu.Unlock()
		if had {
			b.callbackAdminMenu(chatID, msgID, userID)
		}
		return
	case "cmd:stock":
		b.callbackStock(chatID, msgID)
		return
	case "cmd:redeem":
		// Start redeem flow: ask for code.
		b.mu.Lock()
		b.sessions[chatID] = &chatSession{flowType: "redeem_code", loginMsgID: msgID}
		b.mu.Unlock()
		kb := cancelKeyboard()
		b.editMsgWithKeyboard(chatID, msgID, "🎫 *使用兑换码*\n\n请输入兑换码：", &kb)
		return
	case "cmd:random_invite":
		if !b.isAdmin(userID) {
			return
		}
		b.mu.Lock()
		b.sessions[chatID] = &chatSession{flowType: "random_invite", loginMsgID: msgID}
		b.mu.Unlock()
		kb := cancelKeyboard()
		b.editMsgWithKeyboard(chatID, msgID, "🎲 *随机拉人*\n\n请输入要邀请的用户邮箱：", &kb)
		return
	case "cmd:add_admin":
		// Only "super admins" (in config) can add admins dynamically.
		if !b.cfg.IsAdmin(userID) {
			b.sendAutoDelete(chatID, "❌ 权限不足：只有配置文件中的超级管理员才能添加新管理员。")
			return
		}
		b.mu.Lock()
		b.sessions[chatID] = &chatSession{flowType: "add_admin", loginMsgID: msgID}
		b.mu.Unlock()
		kb := cancelToAdminKeyboard()
		b.editMsgWithKeyboard(chatID, msgID, "👑 *添加管理员*\n\n请输入要添加的 Telegram 用户 ID：", &kb)
		return
	}

	// All other buttons are admin-only.
	if !b.isAdmin(userID) {
		return
	}

	switch cq.Data {
	case "cmd:admin_menu":
		b.callbackAdminMenu(chatID, msgID, userID)
	case "cmd:list_accounts":
		b.callbackListAccounts(chatID, msgID)
	case "cmd:status":
		b.callbackStatus(chatID, msgID)
	case "cmd:refresh_all":
		b.callbackRefresh(chatID, msgID)
	case "cmd:check_sub_all":
		b.callbackCheckSub(chatID, msgID)
	case "cmd:list_invites_all":
		b.callbackListInvites(chatID, msgID)
	case "cmd:codes_menu":
		b.callbackCodesMenu(chatID, msgID)
	case "cmd:clear_unused_codes":
		count, err := b.db.DeleteUnusedCodes()
		msg := ""
		if err != nil {
			msg = fmt.Sprintf("❌ 清空失败: %v", err)
		} else {
			msg = fmt.Sprintf("✅ 已成功清空 %d 个未使用的兑换码", count)
		}
		kb := tgbotapi.NewInlineKeyboardMarkup(tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:codes_menu")))
		b.editMsgWithKeyboard(chatID, msgID, msg, &kb)
	case "cmd:list_codes_all":
		b.callbackListCodes(chatID, msgID, 0)
	case "cmd:gen_codes":
		b.mu.Lock()
		b.sessions[chatID] = &chatSession{flowType: "gen_codes_accounts", loginMsgID: msgID}
		b.mu.Unlock()
		kb := cancelKeyboard()
		b.editMsgWithKeyboard(chatID, msgID, "➕ *生成兑换码*\n\n请输入要生成的账号数量：", &kb)
	case "cmd:login":
		b.handleLogin(chatID, msgID)
	case "cmd:add_rt":
		b.mu.Lock()
		b.sessions[chatID] = &chatSession{flowType: "add_rt", loginMsgID: msgID}
		b.mu.Unlock()
		kb := cancelToListKeyboard()
		b.editMsgWithKeyboard(chatID, msgID, "🔑 *RT添加账号*\n\n请输入 Refresh Token：", &kb)
	case "act:del_pick":
		b.callbackActionPick(chatID, msgID, "del", userID, 0)
	case "act:ref_pick":
		b.callbackActionPick(chatID, msgID, "ref", userID, 0)
	case "act:pending_invites_pick":
		b.callbackActionPick(chatID, msgID, "pending_invite", userID, 0)
	case "act:remove_admin_pick":
		// Only super admins can remove admins
		if !b.cfg.IsAdmin(userID) {
			b.sendAutoDelete(chatID, "❌ 权限不足：只有超级管理员才能移除管理员。")
			return
		}
		b.callbackActionPick(chatID, msgID, "deladmin", userID, 0)
	default:
		// Dynamic callbacks: del:<id>, ref:<id>, codes_page:<n>, deladmin:<id>, pending_invite:<id>, delinv:<acc_id>:<email>, act_page:<action>:<page>
		if strings.HasPrefix(cq.Data, "act_page:") {
			parts := strings.SplitN(strings.TrimPrefix(cq.Data, "act_page:"), ":", 2)
			if len(parts) == 2 {
				action := parts[0]
				page, _ := strconv.Atoi(parts[1])
				b.callbackActionPick(chatID, msgID, action, userID, page)
			}
		} else if strings.HasPrefix(cq.Data, "del:") {
			b.callbackDelAccount(chatID, msgID, strings.TrimPrefix(cq.Data, "del:"))
		} else if strings.HasPrefix(cq.Data, "ref:") {
			b.callbackRefAccount(chatID, msgID, strings.TrimPrefix(cq.Data, "ref:"))
		} else if strings.HasPrefix(cq.Data, "deladmin:") {
			if !b.cfg.IsAdmin(userID) {
				return
			}
			b.callbackDelAdmin(chatID, msgID, strings.TrimPrefix(cq.Data, "deladmin:"))
		} else if strings.HasPrefix(cq.Data, "codes_page:") {
			page, _ := strconv.Atoi(strings.TrimPrefix(cq.Data, "codes_page:"))
			b.callbackListCodes(chatID, msgID, page)
		} else if strings.HasPrefix(cq.Data, "pending_invite:") {
			b.callbackListPendingInvites(chatID, msgID, strings.TrimPrefix(cq.Data, "pending_invite:"))
		} else if strings.HasPrefix(cq.Data, "delinv:") {
			b.callbackDelInvite(chatID, msgID, strings.TrimPrefix(cq.Data, "delinv:"))
		} else if strings.HasPrefix(cq.Data, "delcode:") {
			parts := strings.SplitN(strings.TrimPrefix(cq.Data, "delcode:"), ":", 2)
			if len(parts) == 2 {
				page, _ := strconv.Atoi(parts[0])
				codeStr := parts[1]
				b.callbackDelCode(chatID, msgID, page, codeStr)
			}
		}
	}
}

// sendMainMenu sends or edits the main interactive panel.
func (b *Bot) callbackAdminMenu(chatID int64, msgID int, userID int64) {
	admins, err := b.db.GetAllAdmins()
	if err != nil {
		kb := backButton()
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 查询失败: %v", err), &kb)
		return
	}

	text := "👑 *管理员管理*\n\n"
	if len(admins) == 0 {
		text += "暂无数据库记录的管理员 (仅配置项生效)。"
	} else {
		for i, a := range admins {
			text += fmt.Sprintf("*%d.* ID: `%d` (由 `%d` 于 %s 添加)\n", i+1, a.UserID, a.AddedBy, formatDate(a.CreatedAt.Format(time.RFC3339)))
		}
	}

	var kbrows [][]tgbotapi.InlineKeyboardButton

	// If the user is a super admin (in config), they get the add/remove options.
	if b.cfg.IsAdmin(userID) {
		kbrows = append(kbrows, tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("➕ 添加管理员", "cmd:add_admin"),
			tgbotapi.NewInlineKeyboardButtonData("➖ 移除管理员", "act:remove_admin_pick"),
		))
	}

	// Everyone gets the back button.
	kbrows = append(kbrows, tgbotapi.NewInlineKeyboardRow(
		tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:back"),
	))

	kb := tgbotapi.InlineKeyboardMarkup{InlineKeyboard: kbrows}
	b.editMsgWithKeyboard(chatID, msgID, text, &kb)
}

func (b *Bot) callbackDelAdmin(chatID int64, msgID int, idStr string) {
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		return
	}

	if err := b.db.RemoveAdmin(id); err != nil {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回列表", "cmd:admin_menu"),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 移除失败: %v", err), &kb)
		return
	}

	kb := tgbotapi.NewInlineKeyboardMarkup(
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回列表", "cmd:admin_menu"),
		),
	)
	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("✅ 已移除管理员: `%d`", id), &kb)
}

func (b *Bot) sendMainMenu(chatID int64, userID int64, editMsgID int) {
	caption := "🤖 *ChatGPT Team Helper*\n_Unleashing Team Collaboration Powered by AI_"

	var keyboard tgbotapi.InlineKeyboardMarkup
	if b.isAdmin(userID) {
		// Build status summary for admin caption.
		accounts, _ := b.db.GetAllAccounts()
		totalAccounts := len(accounts)
		openAccounts, bannedAccounts, totalUsers, totalInvites := 0, 0, 0, 0
		for _, a := range accounts {
			if a.IsOpen && !a.IsBanned {
				openAccounts++
			}
			if a.IsBanned {
				bannedAccounts++
			}
			totalUsers += a.UserCount
			totalInvites += a.InviteCount
		}
		codeCount, _ := b.db.CountAvailableCodes()

		caption += fmt.Sprintf("\n\n⚙️ *系统状态*\n"+
			"👥 账号: *%d* (开放 %d / 封号 %d)\n"+
			"🎫 可用兑换码: *%d*",
			totalAccounts, openAccounts, bannedAccounts,
			codeCount)

		keyboard = tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("📝 账号列表", "cmd:list_accounts"),
				tgbotapi.NewInlineKeyboardButtonData("📩 待进入邀请", "act:pending_invites_pick"),
			),
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("🎫 兑换码", "cmd:codes_menu"),
				tgbotapi.NewInlineKeyboardButtonData("🎲 随机拉人", "cmd:random_invite"),
			),
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("👑 管理员管理", "cmd:admin_menu"),
			),
		)
	} else {
		keyboard = tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("🎫 使用兑换码", "cmd:redeem"),
			),
		)
	}

	if editMsgID != 0 {
		b.editMsgWithKeyboard(chatID, editMsgID, caption, &keyboard)
		b.mu.Lock()
		b.panelMsgs[chatID] = editMsgID
		b.mu.Unlock()
	} else {
		// Send image.png as photo with buttons.
		photo := tgbotapi.NewPhoto(chatID, tgbotapi.FilePath("image.png"))
		photo.Caption = caption
		photo.ParseMode = "Markdown"
		photo.ReplyMarkup = keyboard
		if sent, err := b.api.Send(photo); err != nil {
			// Fallback to text if image not found.
			msg := tgbotapi.NewMessage(chatID, caption)
			msg.ParseMode = "Markdown"
			msg.ReplyMarkup = keyboard
			if sent2, err2 := b.api.Send(msg); err2 == nil {
				b.mu.Lock()
				b.panelMsgs[chatID] = sent2.MessageID
				b.mu.Unlock()
			}
		} else {
			b.mu.Lock()
			b.panelMsgs[chatID] = sent.MessageID
			b.mu.Unlock()
		}
	}
}

func backButton() tgbotapi.InlineKeyboardMarkup {
	return tgbotapi.NewInlineKeyboardMarkup(
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:back"),
		),
	)
}

func cancelKeyboard() tgbotapi.InlineKeyboardMarkup {
	return tgbotapi.NewInlineKeyboardMarkup(
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("❌ 取消", "cmd:cancel"),
		),
	)
}

func cancelToListKeyboard() tgbotapi.InlineKeyboardMarkup {
	return tgbotapi.NewInlineKeyboardMarkup(
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("❌ 取消", "cmd:cancel_to_list"),
		),
	)
}

func cancelToAdminKeyboard() tgbotapi.InlineKeyboardMarkup {
	return tgbotapi.NewInlineKeyboardMarkup(
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("❌ 取消", "cmd:cancel_to_admin"),
		),
	)
}

func (b *Bot) callbackStock(chatID int64, msgID int) {
	count, err := b.db.CountAvailableCodes()
	if err != nil {
		kb := backButton()
		b.editMsgWithKeyboard(chatID, msgID, "❌ 查询库存失败", &kb)
		return
	}
	kb := backButton()
	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("📦 当前可用兑换码: *%d* 个", count), &kb)
}

func (b *Bot) callbackListAccounts(chatID int64, msgID int) {
	accounts, err := b.db.GetAllAccounts()
	if err != nil {
		kb := backButton()
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 查询失败: %v", err), &kb)
		return
	}
	if len(accounts) == 0 {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("🔗 登录添加", "cmd:login"),
				tgbotapi.NewInlineKeyboardButtonData("🔑 RT添加", "cmd:add_rt"),
			),
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:back"),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, "📋 暂无账号\n\n请添加账号：", &kb)
		return
	}

	// Limit to first 10.
	shown := accounts
	if len(shown) > 10 {
		shown = shown[:10]
	}

	var lines []string
	for _, a := range shown {
		status := "✅"
		if a.IsBanned {
			status = "🚫"
		} else if !a.IsOpen {
			status = "⏸"
		}
		codeCount, _ := b.db.CountAvailableCodesByAccount(a.Email)
		expInfo := ""
		if a.ExpireAt != "" {
			expInfo = fmt.Sprintf(" | 📅 %s", formatDate(a.ExpireAt))
		}
		invInfo := ""
		if a.InviteCount > 0 {
			invInfo = fmt.Sprintf(" (+%d待入)", a.InviteCount)
		}
		accIDShort := a.ChatgptAccountID
		if len(accIDShort) > 8 {
			accIDShort = accIDShort[:8]
		}
		lines = append(lines, fmt.Sprintf(
			"%s `%s` | 👥 %d%s | 🎫 %d码%s",
			status, a.Email, a.UserCount, invInfo,
			codeCount, expInfo))
	}

	text := fmt.Sprintf("📋 *账号列表* (%d 个):\n\n%s", len(accounts), strings.Join(lines, "\n"))
	if len(accounts) > 10 {
		text += fmt.Sprintf("\n\n_仅显示前 10 个，共 %d 个_", len(accounts))
	}

	kb := tgbotapi.NewInlineKeyboardMarkup(
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("🗑 删除账号", "act:del_pick"),
			tgbotapi.NewInlineKeyboardButtonData("🔄 刷新账号", "act:ref_pick"),
		),
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("🔄 刷新全部", "cmd:refresh_all"),
			tgbotapi.NewInlineKeyboardButtonData("📅 查订阅", "cmd:check_sub_all"),
		),
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("🔗 登录添加", "cmd:login"),
			tgbotapi.NewInlineKeyboardButtonData("🔑 RT添加", "cmd:add_rt"),
		),
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:back"),
		),
	)
	b.editMsgWithKeyboard(chatID, msgID, text, &kb)
}

// callbackActionPick shows numbered buttons for selecting an account for delete or refresh.
func (b *Bot) callbackActionPick(chatID int64, msgID int, action string, userID int64, page int) {
	accounts, err := b.db.GetAllAccounts()
	if err != nil || len(accounts) == 0 {
		kb := backButton()
		b.editMsgWithKeyboard(chatID, msgID, "❌ 暂无账号", &kb)
		return
	}

	label := "📧 选择要删除的账号"
	var backCallback string

	// deladmin has its own simple logic without pagination
	if action == "deladmin" {
		label = "👑 选择要移除的管理员"
		backCallback = "cmd:admin_menu"

		admins, err := b.db.GetAllAdmins()
		if err != nil || len(admins) == 0 {
			kb := tgbotapi.NewInlineKeyboardMarkup(tgbotapi.NewInlineKeyboardRow(tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:admin_menu")))
			b.editMsgWithKeyboard(chatID, msgID, "❌ 暂无动态添加的管理员", &kb)
			return
		}

		shownAdmins := admins
		if len(shownAdmins) > 10 {
			shownAdmins = shownAdmins[:10]
		}

		var lns []string
		var rws [][]tgbotapi.InlineKeyboardButton
		var rBtns []tgbotapi.InlineKeyboardButton
		for i, a := range shownAdmins {
			lns = append(lns, fmt.Sprintf("*%d.* ID: `%d`", i+1, a.UserID))
			rBtns = append(rBtns, tgbotapi.NewInlineKeyboardButtonData(
				fmt.Sprintf("%d", i+1),
				fmt.Sprintf("%s:%d", action, a.UserID),
			))
			if len(rBtns) == 5 {
				rws = append(rws, tgbotapi.NewInlineKeyboardRow(rBtns...))
				rBtns = nil
			}
		}
		if len(rBtns) > 0 {
			rws = append(rws, tgbotapi.NewInlineKeyboardRow(rBtns...))
		}
		rws = append(rws, tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回列表", backCallback),
		))

		kba := tgbotapi.InlineKeyboardMarkup{InlineKeyboard: rws}
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("%s:\n\n%s", label, strings.Join(lns, "\n")), &kba)
		return
	}

	backCallback = "cmd:list_accounts"
	prefix := "📧"

	if action == "del" {
		label = "🗑️ 选择要删除的账号"
		prefix = "🗑"
	} else if action == "ref" {
		label = "🔄 选择要刷新的账号"
		prefix = "🔄"
	} else if action == "pending_invite" {
		label = "📩 选择管理账户"
		prefix = "📧"
		backCallback = "cmd:back"
	}

	// Pagination parameters
	pageSize := 8
	total := len(accounts)
	totalPages := (total + pageSize - 1) / pageSize
	if page < 0 {
		page = 0
	}
	if page >= totalPages && totalPages > 0 {
		page = totalPages - 1
	}

	start := page * pageSize
	end := start + pageSize
	if end > total {
		end = total
	}
	shown := accounts[start:end]

	var rows [][]tgbotapi.InlineKeyboardButton

	// Create a button map
	for _, a := range shown {
		btnAction := fmt.Sprintf("%s:%d", action, a.ID)

		rows = append(rows, tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData(
				fmt.Sprintf("%s %s", prefix, a.Email),
				btnAction,
			),
		))
	}

	// Pagination buttons
	var navRow []tgbotapi.InlineKeyboardButton
	if page > 0 {
		navRow = append(navRow, tgbotapi.NewInlineKeyboardButtonData("⬅️ 上一页", fmt.Sprintf("act_page:%s:%d", action, page-1)))
	}
	if page < totalPages-1 {
		navRow = append(navRow, tgbotapi.NewInlineKeyboardButtonData("下一页 ➡️", fmt.Sprintf("act_page:%s:%d", action, page+1)))
	}
	if len(navRow) > 0 {
		rows = append(rows, tgbotapi.NewInlineKeyboardRow(navRow...))
	}

	rows = append(rows, tgbotapi.NewInlineKeyboardRow(
		tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", backCallback),
	))

	kb := tgbotapi.InlineKeyboardMarkup{InlineKeyboard: rows}

	pageInfo := ""
	if totalPages > 1 {
		pageInfo = fmt.Sprintf("\n(第 %d/%d 页)", page+1, totalPages)
	}

	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("%s%s:", label, pageInfo), &kb)
}

func (b *Bot) callbackDelAccount(chatID int64, msgID int, idStr string) {
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		return
	}

	acct, err := b.db.GetAccountByID(id)
	if err != nil {
		kb := backButton()
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 账号不存在: %v", err), &kb)
		return
	}

	if err := b.db.DeleteAccount(id); err != nil {
		kb := backButton()
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 删除失败: %v", err), &kb)
		return
	}

	// Show result then go back to list.
	kb := tgbotapi.NewInlineKeyboardMarkup(
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回列表", "cmd:list_accounts"),
		),
	)
	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("✅ 已删除账号 ID=%d (%s)\n关联的兑换码也已清除", id, acct.Email), &kb)
}

func (b *Bot) callbackRefAccount(chatID int64, msgID int, idStr string) {
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		return
	}

	acc, err := b.db.GetAccountByID(id)
	if err != nil {
		kb := backButton()
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 账号不存在: %v", err), &kb)
		return
	}
	if acc.RefreshToken == "" {
		kb := backButton()
		b.editMsgWithKeyboard(chatID, msgID, "❌ 该账号未配置 Refresh Token", &kb)
		return
	}

	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("⏳ 正在刷新 ID=%d...", id), nil)

	result, err := b.client.RefreshAccessToken(acc.RefreshToken)
	if err != nil {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回列表", "cmd:list_accounts"),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 刷新失败: %s", err.Error()), &kb)
		return
	}

	_ = b.db.UpdateAccountTokens(id, result.AccessToken, result.RefreshToken)

	kb := tgbotapi.NewInlineKeyboardMarkup(
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回列表", "cmd:list_accounts"),
		),
	)

	msg := fmt.Sprintf("✅ 已刷新账号 ID=%d (%s)", id, acc.Email)
	b.editMsgWithKeyboard(chatID, msgID, msg, &kb)
}

func (b *Bot) callbackStatus(chatID int64, msgID int) {
	accounts, err := b.db.GetAllAccounts()
	if err != nil {
		kb := backButton()
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 查询失败: %v", err), &kb)
		return
	}

	totalAccounts := len(accounts)
	openAccounts := 0
	bannedAccounts := 0
	totalUsers := 0
	totalInvites := 0
	for _, a := range accounts {
		if a.IsOpen && !a.IsBanned {
			openAccounts++
		}
		if a.IsBanned {
			bannedAccounts++
		}
		totalUsers += a.UserCount
		totalInvites += a.InviteCount
	}

	codeCount, _ := b.db.CountAvailableCodes()

	text := fmt.Sprintf("📊 *系统状态:*\n\n"+
		"👥 账号总数: *%d* (开放 %d / 封号 %d)\n"+
		"👤 总用户数: *%d* / 总邀请: *%d*\n"+
		"🎫 可用兑换码: *%d*",
		totalAccounts, openAccounts, bannedAccounts,
		totalUsers, totalInvites, codeCount)

	kb := backButton()
	b.editMsgWithKeyboard(chatID, msgID, text, &kb)
}

func (b *Bot) callbackRefresh(chatID int64, msgID int) {
	kb := backButton()
	b.editMsgWithKeyboard(chatID, msgID, "⏳ 正在刷新全部 Token...", nil)

	accs, err := b.db.GetAccountsWithRT()
	if err != nil || len(accs) == 0 {
		b.editMsgWithKeyboard(chatID, msgID, "❌ 没有可刷新的账号", &kb)
		return
	}

	ok, fail := 0, 0
	for _, a := range accs {
		result, err := b.client.RefreshAccessToken(a.RefreshToken)
		if err != nil {
			fail++
			continue
		}
		if err := b.db.UpdateAccountTokens(a.ID, result.AccessToken, result.RefreshToken); err != nil {
			fail++
			continue
		}
		// Sync member counts after token refresh.
		a.Token = result.AccessToken
		if userTotal, _, err2 := b.client.GetUsers(&a); err2 == nil {
			invTotal := a.InviteCount
			if inv, _, err3 := b.client.GetInvites(&a); err3 == nil {
				invTotal = inv
			}
			_ = b.db.UpdateAccountCounts(a.ID, userTotal, invTotal)
		}
		ok++
	}

	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("✅ 刷新完成: 成功 %d, 失败 %d", ok, fail), &kb)
}

func (b *Bot) callbackCheckSub(chatID int64, msgID int) {
	kb := backButton()
	accs, err := b.db.GetAllAccounts()
	if err != nil || len(accs) == 0 {
		b.editMsgWithKeyboard(chatID, msgID, "📋 暂无账号", &kb)
		return
	}

	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("⏳ 正在查询 %d 个账号的订阅信息...", len(accs)), nil)

	var lines []string
	for _, acc := range accs {
		if acc.Token == "" {
			lines = append(lines, fmt.Sprintf("⚠️ ID=%d %s: 无 Token", acc.ID, acc.Email))
			continue
		}
		infos, err := b.client.FetchAccountInfo(acc.Token)
		if err != nil {
			lines = append(lines, fmt.Sprintf("❌ ID=%d %s: %s", acc.ID, acc.Email, err.Error()))
			continue
		}
		for _, info := range infos {
			subStatus := "❌ 无有效订阅"
			if info.HasActiveSubscription {
				subStatus = "✅ 订阅有效"
			}
			expiry := formatDate(info.ExpiresAt)
			if info.ExpiresAt == "" {
				expiry = "未知"
			}
			lines = append(lines, fmt.Sprintf(
				"`%d` %s\n    %s | 📅 到期: %s | %s",
				acc.ID, acc.Email, info.Name, expiry, subStatus))
			if info.ExpiresAt != "" && info.AccountID == acc.ChatgptAccountID {
				_ = b.db.UpdateAccountInfo(acc.ID, acc.ChatgptAccountID, info.ExpiresAt)
			}
		}
	}

	b.editMsgWithKeyboard(chatID, msgID, "📋 *订阅信息:*\n\n"+strings.Join(lines, "\n\n"), &kb)
}

func (b *Bot) callbackListInvites(chatID int64, msgID int) {
	kb := backButton()
	accs, err := b.db.GetAllAccounts()
	if err != nil || len(accs) == 0 {
		b.editMsgWithKeyboard(chatID, msgID, "📋 暂无账号", &kb)
		return
	}

	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("⏳ 正在查询 %d 个账号的待进入邀请...", len(accs)), nil)

	var lines []string
	totalInvites := 0
	for _, acc := range accs {
		if acc.Token == "" {
			continue
		}
		_, invites, err := b.client.GetInvites(&acc)
		if err != nil {
			lines = append(lines, fmt.Sprintf("❌ ID=%d %s: %s", acc.ID, acc.Email, err.Error()))
			continue
		}
		if len(invites) == 0 {
			continue
		}
		totalInvites += len(invites)
		var inviteLines []string
		for _, inv := range invites {
			inviteLines = append(inviteLines, fmt.Sprintf("    📧 `%s` (%s)", inv.EmailAddress, inv.Role))
		}
		lines = append(lines, fmt.Sprintf("*ID=%d* %s (%d 个):\n%s",
			acc.ID, acc.Email, len(invites), strings.Join(inviteLines, "\n")))
	}

	if totalInvites == 0 {
		b.editMsgWithKeyboard(chatID, msgID, "📭 暂无待进入的邀请", &kb)
		return
	}

	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("📧 *待进入邀请* (%d 个):\n\n%s",
		totalInvites, strings.Join(lines, "\n\n")), &kb)
}

func (b *Bot) callbackListCodes(chatID int64, msgID int, page int) {
	const pageSize = 10

	codes, err := b.db.GetAllCodes()
	if err != nil {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:codes_menu"),
				tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 查询失败: %v", err), &kb)
		return
	}
	if len(codes) == 0 {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:codes_menu"),
				tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, "📭 暂无兑换码", &kb)
		return
	}

	totalPages := (len(codes) + pageSize - 1) / pageSize
	if page < 0 {
		page = 0
	}
	if page >= totalPages {
		page = totalPages - 1
	}

	start := page * pageSize
	end := start + pageSize
	if end > len(codes) {
		end = len(codes)
	}

	var lines []string
	var codeRows [][]tgbotapi.InlineKeyboardButton
	var codeBtns []tgbotapi.InlineKeyboardButton

	for i, c := range codes[start:end] {
		status := "🟢"
		extra := ""
		if c.IsRedeemed {
			status = "🔴"
			by := ""
			if c.RedeemedBy != nil && *c.RedeemedBy != "" {
				by = *c.RedeemedBy
			}
			extra = fmt.Sprintf(" → %s", by)
		} else {
			codeBtns = append(codeBtns, tgbotapi.NewInlineKeyboardButtonData(
				fmt.Sprintf("🗑️ %d", i+1),
				fmt.Sprintf("delcode:%d:%s", page, c.Code),
			))
			if len(codeBtns) == 5 {
				codeRows = append(codeRows, tgbotapi.NewInlineKeyboardRow(codeBtns...))
				codeBtns = nil
			}
		}
		lines = append(lines, fmt.Sprintf("%d. %s `%s` (%s)%s", i+1, status, c.Code, c.AccountEmail, extra))
	}
	if len(codeBtns) > 0 {
		codeRows = append(codeRows, tgbotapi.NewInlineKeyboardRow(codeBtns...))
	}

	// Build keyboard with pagination buttons.
	var rows [][]tgbotapi.InlineKeyboardButton
	rows = append(rows, codeRows...)

	if totalPages > 1 {
		var navBtns []tgbotapi.InlineKeyboardButton
		if page > 0 {
			navBtns = append(navBtns, tgbotapi.NewInlineKeyboardButtonData("⬅️ 上一页", fmt.Sprintf("codes_page:%d", page-1)))
		}
		navBtns = append(navBtns, tgbotapi.NewInlineKeyboardButtonData(fmt.Sprintf("%d/%d", page+1, totalPages), "noop"))
		if page < totalPages-1 {
			navBtns = append(navBtns, tgbotapi.NewInlineKeyboardButtonData("➡️ 下一页", fmt.Sprintf("codes_page:%d", page+1)))
		}
		rows = append(rows, navBtns)
	}
	rows = append(rows, tgbotapi.NewInlineKeyboardRow(
		tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:codes_menu"),
		tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
	))
	kb := tgbotapi.InlineKeyboardMarkup{InlineKeyboard: rows}

	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("🎫 *兑换码列表* (%d 个):\n\n%s", len(codes), strings.Join(lines, "\n")), &kb)
}

func (b *Bot) callbackDelCode(chatID int64, msgID int, page int, code string) {
	_ = b.db.DeleteCode(code)
	b.callbackListCodes(chatID, msgID, page)
}

func (b *Bot) callbackCodesMenu(chatID int64, msgID int) {
	kb := tgbotapi.NewInlineKeyboardMarkup(
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("📋 查看兑换码", "cmd:list_codes_all"),
			tgbotapi.NewInlineKeyboardButtonData("➕ 生成兑换码", "cmd:gen_codes"),
		),
		tgbotapi.NewInlineKeyboardRow(
			tgbotapi.NewInlineKeyboardButtonData("🗑️ 一键清空未使用", "cmd:clear_unused_codes"),
			tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:back"),
		),
	)
	b.editMsgWithKeyboard(chatID, msgID, "🎫 *兑换码管理*\n\n请选择操作：", &kb)
}

func (b *Bot) handleMessage(msg *tgbotapi.Message) {
	chatID := msg.Chat.ID
	userID := msg.From.ID
	text := strings.TrimSpace(msg.Text)

	// Check two-step session flows (redeem or login).
	b.mu.Lock()
	sess, hasSess := b.sessions[chatID]
	b.mu.Unlock()

	if hasSess && !strings.HasPrefix(text, "/") {
		switch sess.flowType {
		case "redeem_code":
			b.handleRedeemCode(chatID, text)
		case "redeem":
			b.handleRedeemEmail(chatID, sess, text)
		case "login":
			b.handleLoginCallback(chatID, msg.MessageID, text)
		case "random_invite":
			b.mu.Lock()
			panelMsgID := sess.loginMsgID
			delete(b.sessions, chatID)
			b.mu.Unlock()
			b.deleteMsg(chatID, msg.MessageID)
			// Inline random invite logic.
			email := strings.ToLower(strings.TrimSpace(text))
			if email == "" {
				return
			}
			accounts, err := b.db.GetOpenAccounts(b.cfg.TeamCapacity)
			if err != nil || len(accounts) == 0 {
				kb := tgbotapi.NewInlineKeyboardMarkup(
					tgbotapi.NewInlineKeyboardRow(
						tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
					),
				)
				b.editMsgWithKeyboard(chatID, panelMsgID, "❌ 暂无可用的空位账号", &kb)
				return
			}
			account := &accounts[0]
			b.editMsgWithKeyboard(chatID, panelMsgID, fmt.Sprintf("⏳ 正在邀请到账号 ID=%d (%s)...", account.ID, account.Email), nil)
			if err := b.client.InviteUser(email, account); err != nil {
				kb := tgbotapi.NewInlineKeyboardMarkup(
					tgbotapi.NewInlineKeyboardRow(
						tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
					),
				)
				b.editMsgWithKeyboard(chatID, panelMsgID, fmt.Sprintf("❌ 邀请失败: %s", err.Error()), &kb)
				return
			}
			time.Sleep(time.Second)
			syncCounts(b.db, b.client, account)
			// Consume one unused redemption code for this account.
			if codes, err := b.db.GetCodesByAccount(account.Email); err == nil {
				for _, c := range codes {
					if !c.IsRedeemed {
						_ = b.db.RedeemCode(c.ID, email)
						break
					}
				}
			}
			kb := tgbotapi.NewInlineKeyboardMarkup(
				tgbotapi.NewInlineKeyboardRow(
					tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
				),
			)
			b.editMsgWithKeyboard(chatID, panelMsgID, fmt.Sprintf("✅ 已成功邀请 `%s` 到账号 ID=%d (%s)", email, account.ID, account.Email), &kb)
		case "gen_codes_accounts":
			n, err := strconv.Atoi(strings.TrimSpace(text))
			if err != nil || n <= 0 || n > 20 {
				b.sendAutoDelete(chatID, "❌ 请输入1-20的整数")
				return
			}
			b.mu.Lock()
			panelMsgID := sess.loginMsgID
			delete(b.sessions, chatID)
			b.mu.Unlock()
			b.deleteMsg(chatID, msg.MessageID)
			b.handleGenCodes(chatID, panelMsgID, strconv.Itoa(n))
		case "add_rt":
			rt := strings.TrimSpace(text)
			b.mu.Lock()
			panelMsgID := sess.loginMsgID
			delete(b.sessions, chatID)
			b.mu.Unlock()
			b.deleteMsg(chatID, msg.MessageID)
			b.handleAddAccount(chatID, panelMsgID, rt)
		case "add_admin":
			idStr := strings.TrimSpace(text)
			b.mu.Lock()
			panelMsgID := sess.loginMsgID
			delete(b.sessions, chatID)
			b.mu.Unlock()
			b.deleteMsg(chatID, msg.MessageID)
			b.handleAddAdminMsg(chatID, panelMsgID, idStr, userID)
		}
		return
	}

	if !msg.IsCommand() {
		return
	}

	cmd := msg.Command()
	args := strings.TrimSpace(msg.CommandArguments())

	// Handle /cancel for any active session.
	if cmd == "cancel" {
		b.deleteMsg(chatID, msg.MessageID) // delete user's /cancel immediately
		b.mu.Lock()
		sess, had := b.sessions[chatID]
		var panelMsgID int
		if had {
			panelMsgID = sess.loginMsgID
		}
		delete(b.sessions, chatID)
		b.mu.Unlock()
		if had {
			if panelMsgID != 0 {
				b.sendMainMenu(chatID, userID, panelMsgID)
			} else {
				replyID := b.sendAndGetID(chatID, "❎ 已取消操作")
				go func() {
					time.Sleep(20 * time.Second)
					b.deleteMsg(chatID, replyID)
				}()
			}
		}
		return
	}

	switch cmd {
	case "start":
		b.sendMainMenu(chatID, userID, 0)
	case "redeem":
		b.handleRedeemStart(chatID, args)
	case "add_account":
		b.requireAdmin(chatID, userID, func() { b.handleAddAccount(chatID, 0, args) })
	case "gen_codes":
		b.requireAdmin(chatID, userID, func() { b.handleGenCodes(chatID, 0, args) })
	case "add_admin": // Super admin only
		if b.cfg.IsAdmin(userID) {
			b.handleAddAdmin(chatID, userID, args)
		} else {
			b.sendAutoDelete(chatID, "❌ 权限不足：只有配置文件中的超级管理员才能添加新管理员。")
		}
	case "login":
		b.requireAdmin(chatID, userID, func() { b.handleLogin(chatID, 0) })
	}
}

// ─── User Commands ──────────────────────────────────────────

func (b *Bot) handleRedeemStart(chatID int64, args string) {
	code := strings.TrimSpace(strings.ToUpper(args))
	if code == "" {
		// No code provided: start interactive flow.
		b.mu.Lock()
		b.sessions[chatID] = &chatSession{flowType: "redeem_code"}
		b.mu.Unlock()
		b.send(chatID, "🎫 请输入兑换码：")
		return
	}

	// Code provided directly: validate and proceed.
	b.handleRedeemCode(chatID, code)
}

func (b *Bot) handleRedeemCode(chatID int64, input string) {
	code := strings.TrimSpace(strings.ToUpper(input))

	// Get the panel message ID from the session.
	b.mu.Lock()
	panelMsgID := 0
	if s, ok := b.sessions[chatID]; ok {
		panelMsgID = s.loginMsgID
	}
	b.mu.Unlock()

	backKb := backButton()

	// Validate code exists and is unused before asking for email.
	rc, err := b.db.GetCodeByCode(code)
	if err != nil {
		b.mu.Lock()
		delete(b.sessions, chatID)
		b.mu.Unlock()
		if panelMsgID != 0 {
			b.editMsgWithKeyboard(chatID, panelMsgID, "❌ 兑换码不存在或已失效", &backKb)
		} else {
			b.sendAutoDelete(chatID, "❌ 兑换码不存在或已失效")
		}
		return
	}
	if rc.IsRedeemed {
		b.mu.Lock()
		delete(b.sessions, chatID)
		b.mu.Unlock()
		if panelMsgID != 0 {
			b.editMsgWithKeyboard(chatID, panelMsgID, "❌ 该兑换码已被使用", &backKb)
		} else {
			b.sendAutoDelete(chatID, "❌ 该兑换码已被使用")
		}
		return
	}

	b.mu.Lock()
	b.sessions[chatID] = &chatSession{flowType: "redeem", code: code, loginMsgID: panelMsgID}
	b.mu.Unlock()

	if panelMsgID != 0 {
		kb := cancelKeyboard()
		b.editMsgWithKeyboard(chatID, panelMsgID, "✅ 兑换码有效！请输入您的邮箱地址：", &kb)
	} else {
		b.send(chatID, "✅ 兑换码有效！请输入您的邮箱地址：")
	}
}

func (b *Bot) handleRedeemEmail(chatID int64, sess *chatSession, email string) {
	panelMsgID := sess.loginMsgID
	b.mu.Lock()
	delete(b.sessions, chatID)
	b.mu.Unlock()

	backKb := backButton()

	if panelMsgID != 0 {
		b.editMsgWithKeyboard(chatID, panelMsgID, "⏳ 正在处理兑换，请稍候...", nil)

		result, err := redeem.Redeem(b.db, b.client, sess.code, email, b.cfg.TeamCapacity)
		if err != nil {
			b.editMsgWithKeyboard(chatID, panelMsgID, fmt.Sprintf("❌ 兑换失败: %s", err.Error()), &backKb)
			return
		}
		b.editMsgWithKeyboard(chatID, panelMsgID, fmt.Sprintf("🎉 %s", result.Message), &backKb)
	} else {
		msgID := b.sendAndGetID(chatID, "⏳ 正在处理兑换，请稍候...")

		result, err := redeem.Redeem(b.db, b.client, sess.code, email, b.cfg.TeamCapacity)
		if err != nil {
			b.editMsg(chatID, msgID, fmt.Sprintf("❌ 兑换失败: %s", err.Error()))
			return
		}
		b.editMsg(chatID, msgID, fmt.Sprintf("🎉 %s", result.Message))
	}
}

func (b *Bot) handleStock(chatID int64) {
	count, err := b.db.CountAvailableCodes()
	if err != nil {
		b.sendAutoDelete(chatID, "❌ 查询库存失败")
		return
	}
	b.send(chatID, fmt.Sprintf("📦 当前可用兑换码: *%d* 个", count))
}

// ─── Admin Commands ─────────────────────────────────────────

func (b *Bot) handleAddAccount(chatID int64, panelMsgID int, args string) {
	rt := strings.TrimSpace(args)
	if rt == "" {
		b.sendAutoDelete(chatID, "❌ 用法: /add\\_account `<refresh_token>`")
		return
	}

	// If panelMsgID is provided, show progress on the panel; otherwise send a new message.
	msgID := panelMsgID
	if msgID == 0 {
		msgID = b.sendAndGetID(chatID, "⏳ 正在刷新 Token 并获取账号信息...")
	} else {
		b.editMsgWithKeyboard(chatID, msgID, "⏳ 正在刷新 Token 并获取账号信息...", nil)
	}

	// 1. Refresh to get access token.
	tokenResult, err := b.client.RefreshAccessToken(rt)
	if err != nil {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回列表", "cmd:list_accounts"),
				tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
			),
		)
		if panelMsgID != 0 {
			b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ Refresh Token 无效: %s", err.Error()), &kb)
		} else {
			b.editMsg(chatID, msgID, fmt.Sprintf("❌ Refresh Token 无效: %s", err.Error()))
		}
		return
	}

	// 2. Fetch account info.
	infos, err := b.client.FetchAccountInfo(tokenResult.AccessToken)
	if err != nil {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回列表", "cmd:list_accounts"),
				tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
			),
		)
		if panelMsgID != 0 {
			b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 获取账号信息失败: %s", err.Error()), &kb)
		} else {
			b.editMsg(chatID, msgID, fmt.Sprintf("❌ 获取账号信息失败: %s", err.Error()))
		}
		return
	}

	email := tokenResult.GetEmail()

	// 3. Create DB records for each team account found.
	var lines []string
	for _, info := range infos {
		accountEmail := email
		if accountEmail == "" {
			accountEmail = info.Name // Fallback
		}

		// Check for duplicate by OpenAI Account ID (most reliable)
		if existing, err2 := b.db.GetAccountByChatGPTAccountID(info.AccountID); err2 == nil && existing != nil {
			lines = append(lines, fmt.Sprintf("⚠️ %s (%s): 已存在 (ID=%d)，跳过", info.Name, accountEmail, existing.ID))
			continue
		}

		id, err := b.db.CreateAccount(&model.GptAccount{
			Email:            accountEmail,
			Token:            tokenResult.AccessToken,
			RefreshToken:     tokenResult.RefreshToken,
			ChatgptAccountID: info.AccountID,
			ExpireAt:         info.ExpiresAt,
			IsOpen:           true,
		})
		if err != nil {
			lines = append(lines, fmt.Sprintf("❌ %s (%s): 创建失败 - %v", info.Name, accountEmail, err))
			continue
		}

		// Generate codes based on remaining capacity: 6 - (current user count + pending invites).
		newAcct, _ := b.db.GetAccountByID(id)
		codeCount := 6
		if newAcct != nil {
			var userTotal, inviteTotal int
			if ut, _, err2 := b.client.GetUsers(newAcct); err2 == nil {
				userTotal = ut
			}
			if it, _, err3 := b.client.GetInvites(newAcct); err3 == nil {
				inviteTotal = it
			}
			codeCount = 6 - (userTotal + inviteTotal)
			_ = b.db.UpdateAccountCounts(id, userTotal, inviteTotal)
		}
		if codeCount < 0 {
			codeCount = 0
		}
		codes := redeem.GenerateCodes(codeCount)
		if err := b.db.CreateCodes(accountEmail, codes); err != nil {
			lines = append(lines, fmt.Sprintf("⚠️ ID=%d %s: 账号已创建但生成兑换码失败", id, accountEmail))
			continue
		}

		subInfo := ""
		if info.ExpiresAt != "" {
			subInfo = fmt.Sprintf("\n    📅 订阅到期: %s", formatDate(info.ExpiresAt))
		}
		lines = append(lines, fmt.Sprintf("✅ %s | %s%s\n    🎫 已生成 %d 个兑换码",
			accountEmail, info.AccountID, subInfo, len(codes)))
	}

	resultText := "📋 *添加结果:*\n\n" + strings.Join(lines, "\n")
	if panelMsgID != 0 {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回列表", "cmd:list_accounts"),
				tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, resultText, &kb)
	} else {
		b.editMsg(chatID, msgID, resultText)
	}
}

func (b *Bot) callbackListPendingInvites(chatID int64, msgID int, accountIDStr string) {
	accountID, err := strconv.ParseInt(accountIDStr, 10, 64)
	if err != nil {
		return
	}

	account, err := b.db.GetAccountByID(accountID)
	if err != nil {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "act:pending_invites_pick"),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 账号不存在: %v", err), &kb)
		return
	}

	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("⏳ 正在查询账号 ID=%d 的待进入邀请...", accountID), nil)

	_, invites, err := b.client.GetInvites(account)
	if err != nil {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "act:pending_invites_pick"),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 查询失败: %v", err), &kb)
		return
	}

	// Sync invite count to DB so the main page status is accurate.
	_ = b.db.UpdateAccountCounts(account.ID, account.UserCount, len(invites))

	var rows [][]tgbotapi.InlineKeyboardButton
	if len(invites) == 0 {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "act:pending_invites_pick"),
				tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("📭 账号 ID=%d (%s) 暂无待进入的邀请", accountID, account.Email), &kb)
		return
	}

	text := fmt.Sprintf("📩 *待进入邀请* — ID=%d (%s)\n共 %d 个\n\n点击可删除对应邀请：", accountID, account.Email, len(invites))

	for _, inv := range invites {
		cbData := fmt.Sprintf("delinv:%d:%s", accountID, inv.EmailAddress)
		// Telegram callback data has a 64-byte limit.
		if len(cbData) <= 64 {
			rows = append(rows, tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData(fmt.Sprintf("❌ %s", inv.EmailAddress), cbData),
			))
		}
	}
	rows = append(rows, tgbotapi.NewInlineKeyboardRow(
		tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "act:pending_invites_pick"),
		tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
	))

	kb := tgbotapi.InlineKeyboardMarkup{InlineKeyboard: rows}
	b.editMsgWithKeyboard(chatID, msgID, text, &kb)
}

func (b *Bot) callbackDelInvite(chatID int64, msgID int, data string) {
	// data format: <acc_id>:<email>
	idx := strings.Index(data, ":")
	if idx < 0 {
		return
	}
	accIDStr := data[:idx]
	email := data[idx+1:]

	accountID, err := strconv.ParseInt(accIDStr, 10, 64)
	if err != nil {
		return
	}

	account, err := b.db.GetAccountByID(accountID)
	if err != nil {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "act:pending_invites_pick"),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 账号不存在: %v", err), &kb)
		return
	}

	b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("⏳ 正在删除邀请 %s...", email), nil)

	if err := b.client.DeleteInvite(account, email); err != nil {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", fmt.Sprintf("pending_invite:%d", accountID)),
			),
		)
		b.editMsgWithKeyboard(chatID, msgID, fmt.Sprintf("❌ 删除邀请失败: %s", err.Error()), &kb)
		return
	}

	time.Sleep(2 * time.Second)
	syncCounts(b.db, b.client, account)

	// Re-trigger the list to show updated invites.
	b.callbackListPendingInvites(chatID, msgID, accIDStr)
}

func (b *Bot) handleGenCodes(chatID int64, panelMsgID int, args string) {
	n, err := strconv.Atoi(strings.TrimSpace(args))
	if err != nil || n <= 0 {
		b.sendAutoDelete(chatID, "❌ 参数错误")
		return
	}

	accounts, err := b.db.GetAllAccounts()
	if err != nil || len(accounts) == 0 {
		b.sendAutoDelete(chatID, "❌ 暂无账号")
		return
	}

	// Actually, let's look at the remaining capacity of the account to see how many we CAN generate.
	// We'll calculate the available capacity below per account based on TeamCapacity and UserCount.
	var eligible []model.GptAccount
	for _, acc := range accounts {
		if acc.IsBanned || !acc.IsOpen {
			continue // Skip banned or closed accounts
		}

		// We want to generate codes for this account up to the allowed capacity.
		existing, _ := b.db.CountAvailableCodesByAccount(acc.Email)

		maxCodes := b.cfg.TeamCapacity - acc.UserCount - acc.InviteCount
		if maxCodes < 0 {
			maxCodes = 0
		}

		if existing <= maxCodes {
			eligible = append(eligible, acc)
		}
	}

	if len(eligible) == 0 {
		if panelMsgID != 0 {
			kb := tgbotapi.NewInlineKeyboardMarkup(
				tgbotapi.NewInlineKeyboardRow(
					tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:codes_menu"),
					tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
				),
			)
			b.editMsgWithKeyboard(chatID, panelMsgID, "✅ 所有账号均已满员或已生成充足兑换码，无需继续生成", &kb)
		} else {
			b.send(chatID, "✅ 所有账号均已满员或已生成充足兑换码，无需继续生成")
		}
		return
	}

	if n > len(eligible) {
		n = len(eligible)
	}

	var allLines []string
	for i := 0; i < n; i++ {
		email := eligible[i].Email
		existing, _ := b.db.CountAvailableCodesByAccount(email)

		maxCodes := b.cfg.TeamCapacity - eligible[i].UserCount - eligible[i].InviteCount
		if maxCodes < 0 {
			maxCodes = 0
		}

		needed := maxCodes - existing
		if needed <= 0 {
			continue
		}
		codes := redeem.GenerateCodes(needed)
		if err := b.db.CreateCodes(email, codes); err != nil {
			allLines = append(allLines, fmt.Sprintf("❌ `%s`: %v", email, err))
			continue
		}
		codeList := make([]string, len(codes))
		for j, c := range codes {
			codeList[j] = fmt.Sprintf("`%s`", c)
		}
		allLines = append(allLines, fmt.Sprintf("✅ `%s` (+%d个):\n%s",
			email, needed, strings.Join(codeList, "\n")))
	}

	availLeft := len(eligible) - n
	suffix := ""
	if availLeft > 0 {
		suffix = fmt.Sprintf("\n⚠️ 还有 %d 个账号可继续生成", availLeft)
	}

	result := fmt.Sprintf("✅ 已处理 %d 个账号，补充兑换码至名额上限：\n\n%s%s",
		n, strings.Join(allLines, "\n\n"), suffix)

	if panelMsgID != 0 {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 返回", "cmd:codes_menu"),
				tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
			),
		)
		b.editMsgWithKeyboard(chatID, panelMsgID, result, &kb)
	} else {
		b.send(chatID, result)
	}
}

func (b *Bot) handleStatus(chatID int64) {
	accounts, err := b.db.GetAllAccounts()
	if err != nil {
		b.sendAutoDelete(chatID, fmt.Sprintf("❌ 查询失败: %v", err))
		return
	}

	total := len(accounts)
	open, banned := 0, 0
	totalUsers, totalInvites := 0, 0
	for _, a := range accounts {
		if a.IsBanned {
			banned++
		} else if a.IsOpen {
			open++
		}
		totalUsers += a.UserCount
		totalInvites += a.InviteCount
	}

	codeCount, _ := b.db.CountAvailableCodes()

	b.send(chatID, fmt.Sprintf(
		"📊 *系统状态*\n\n"+
			"📁 账号总数: *%d*\n"+
			"  ├ 开放中: *%d*\n"+
			"  ├ 已封号: *%d*\n"+
			"  └ 已关闭: *%d*\n\n"+
			"👥 总用户数: *%d* | 待邀请: *%d*\n"+
			"🎫 可用兑换码: *%d*",
		total, open, banned, total-open-banned,
		totalUsers, totalInvites, codeCount))
}

func (b *Bot) handleAddAdmin(chatID int64, addedBy int64, args string) {
	b.handleAddAdminMsg(chatID, 0, args, addedBy)
}

func (b *Bot) handleAddAdminMsg(chatID int64, panelMsgID int, args string, addedBy int64) {
	idStr := strings.TrimSpace(args)
	if idStr == "" {
		if panelMsgID != 0 {
			kb := tgbotapi.NewInlineKeyboardMarkup(
				tgbotapi.NewInlineKeyboardRow(
					tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
				),
			)
			b.editMsgWithKeyboard(chatID, panelMsgID, "❌ 用法: `/add_admin <TG用户ID>` 或者在面板中输入有效ID", &kb)
		} else {
			b.sendAutoDelete(chatID, "❌ 用法: /add\\_admin `<TG用户ID>`")
		}
		return
	}
	newID, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		if panelMsgID != 0 {
			kb := tgbotapi.NewInlineKeyboardMarkup(
				tgbotapi.NewInlineKeyboardRow(
					tgbotapi.NewInlineKeyboardButtonData("🏠 主界面", "cmd:back"),
				),
			)
			b.editMsgWithKeyboard(chatID, panelMsgID, "❌ 用户ID格式错误，请输入数字", &kb)
		} else {
			b.sendAutoDelete(chatID, "❌ 用户ID格式错误，请输入数字")
		}
		return
	}
	if b.isAdmin(newID) {
		if panelMsgID != 0 {
			kb := tgbotapi.NewInlineKeyboardMarkup(
				tgbotapi.NewInlineKeyboardRow(
					tgbotapi.NewInlineKeyboardButtonData("⬅️ 列表后台", "cmd:admin_menu"),
				),
			)
			b.editMsgWithKeyboard(chatID, panelMsgID, fmt.Sprintf("ℹ️ 用户 `%d` 已经是管理员", newID), &kb)
		} else {
			b.send(chatID, fmt.Sprintf("ℹ️ 用户 `%d` 已经是管理员", newID))
		}
		return
	}

	if err := b.db.AddAdmin(newID, addedBy); err != nil {
		if panelMsgID != 0 {
			kb := tgbotapi.NewInlineKeyboardMarkup(
				tgbotapi.NewInlineKeyboardRow(
					tgbotapi.NewInlineKeyboardButtonData("⬅️ 列表后台", "cmd:admin_menu"),
				),
			)
			b.editMsgWithKeyboard(chatID, panelMsgID, fmt.Sprintf("❌ 添加失败: %v", err), &kb)
		} else {
			b.send(chatID, fmt.Sprintf("❌ 添加失败: %v", err))
		}
		return
	}

	msg := fmt.Sprintf("✅ 已成功将用户 `%d` 添加为管理员！", newID)
	if panelMsgID != 0 {
		kb := tgbotapi.NewInlineKeyboardMarkup(
			tgbotapi.NewInlineKeyboardRow(
				tgbotapi.NewInlineKeyboardButtonData("⬅️ 列表后台", "cmd:admin_menu"),
			),
		)
		b.editMsgWithKeyboard(chatID, panelMsgID, msg, &kb)
	} else {
		b.send(chatID, msg)
	}
}

func (b *Bot) handleLogin(chatID int64, editMsgID int) {
	authURL, _, err := b.oauth.GenerateAuthURL()
	if err != nil {
		b.sendAutoDelete(chatID, fmt.Sprintf("❌ 生成登录链接失败: %s", err.Error()))
		return
	}

	text := fmt.Sprintf(
		"🔗 *OpenAI 登录链接*\n\n"+
			"[点击这里登录](%s)\n\n"+
			"ℹ️ 登录完成后，浏览器会跳转到一个无法访问的页面，这是正常的\n"+
			"👉 请复制浏览器地址栏中的 *完整 URL* 粘贴回这里",
		authURL)

	kb := cancelToListKeyboard()

	var loginMsgID int
	if editMsgID != 0 {
		b.editMsgWithKeyboard(chatID, editMsgID, text, &kb)
		loginMsgID = editMsgID
	} else {
		loginMsgID = b.sendAndGetID(chatID, text)
	}

	b.mu.Lock()
	b.sessions[chatID] = &chatSession{flowType: "login", loginMsgID: loginMsgID}
	b.mu.Unlock()

	// Auto-delete login link after 2 minutes if session is still active.
	go func() {
		time.Sleep(2 * time.Minute)
		b.mu.Lock()
		sess, exists := b.sessions[chatID]
		if exists && sess.flowType == "login" && sess.loginMsgID == loginMsgID {
			delete(b.sessions, chatID)
			b.mu.Unlock()
			if editMsgID == 0 {
				b.deleteMsg(chatID, loginMsgID)
			}
		} else {
			b.mu.Unlock()
		}
	}()
}

func (b *Bot) handleLoginCallback(chatID int64, pastedMsgID int, callbackURL string) {
	// Get session and clean up.
	b.mu.Lock()
	sess := b.sessions[chatID]
	loginMsgID := 0
	if sess != nil {
		loginMsgID = sess.loginMsgID
	}
	delete(b.sessions, chatID)
	b.mu.Unlock()

	// Delete the pasted URL message (contains sensitive code).
	b.deleteMsg(chatID, pastedMsgID)

	// Edit the original login message to show progress.
	b.editMsg(chatID, loginMsgID, "⏳ 正在处理登录信息...")

	result, err := b.oauth.ExchangeCallbackURL(callbackURL)
	if err != nil {
		b.editMsg(chatID, loginMsgID, fmt.Sprintf("❌ 登录失败: %s", err.Error()))
		return
	}

	b.editMsg(chatID, loginMsgID, "✅ Token 获取成功，正在获取账号信息...")

	infos, err := b.client.FetchAccountInfo(result.AccessToken)
	if err != nil {
		id, dbErr := b.db.CreateAccount(&model.GptAccount{
			Email:            result.Email,
			Token:            result.AccessToken,
			RefreshToken:     result.RefreshToken,
			ChatgptAccountID: result.AccountID,
			IsOpen:           true,
		})
		if dbErr != nil {
			b.editMsg(chatID, loginMsgID, fmt.Sprintf("❌ 创建账号失败: %v", dbErr))
			return
		}
		b.editMsg(chatID, loginMsgID, fmt.Sprintf("⚠️ 账号已创建 (ID=%d)，但获取详情失败: %s\nRT 已保存。", id, err.Error()))
		return
	}

	var lines []string
	for _, info := range infos {
		accountEmail := result.Email
		if accountEmail == "" {
			accountEmail = info.Name // Fallback
		}

		// Check for duplicate by OpenAI Account ID (most reliable)
		if existing, err2 := b.db.GetAccountByChatGPTAccountID(info.AccountID); err2 == nil && existing != nil {
			lines = append(lines, fmt.Sprintf("⚠️ %s (%s): 已存在 (ID=%d)，跳过", info.Name, accountEmail, existing.ID))
			continue
		}

		id, dbErr := b.db.CreateAccount(&model.GptAccount{
			Email:            accountEmail,
			Token:            result.AccessToken,
			RefreshToken:     result.RefreshToken,
			ChatgptAccountID: info.AccountID,
			ExpireAt:         info.ExpiresAt,
			IsOpen:           true,
		})
		if dbErr != nil {
			lines = append(lines, fmt.Sprintf("❌ %s (%s): 创建失败 - %v", info.Name, accountEmail, dbErr))
			continue
		}

		subInfo := ""
		if info.ExpiresAt != "" {
			subInfo = fmt.Sprintf(" | 📅 %s", formatDate(info.ExpiresAt))
		}
		lines = append(lines, fmt.Sprintf("✅ ID=%d %s%s",
			id, info.Name, subInfo))
	}

	b.editMsg(chatID, loginMsgID, "📋 *登录添加结果:*\n\n"+strings.Join(lines, "\n"))
}

func (b *Bot) handleListCodes(chatID int64, args string) {
	target := strings.TrimSpace(args)
	if target == "" {
		b.sendAutoDelete(chatID, "❌ 用法: /list\\_codes `<账号邮箱|all>`")
		return
	}

	var codes []model.RedemptionCode
	var err error
	if strings.EqualFold(target, "all") {
		codes, err = b.db.GetAllCodes()
	} else {
		codes, err = b.db.GetCodesByAccount(target)
	}
	if err != nil {
		b.sendAutoDelete(chatID, fmt.Sprintf("❌ 查询失败: %v", err))
		return
	}
	if len(codes) == 0 {
		b.send(chatID, "📭 暂无兑换码")
		return
	}

	var lines []string
	for _, c := range codes {
		status := "🟢"
		extra := ""
		if c.IsRedeemed {
			status = "🔴"
			by := ""
			if c.RedeemedBy != nil && *c.RedeemedBy != "" {
				by = *c.RedeemedBy
			}
			extra = fmt.Sprintf(" → %s", by)
		}
		lines = append(lines, fmt.Sprintf("%s `%s` (%s)%s", status, c.Code, c.AccountEmail, extra))
	}

	header := fmt.Sprintf("🎫 *兑换码列表* (%d 个):\n\n", len(codes))
	b.send(chatID, header+strings.Join(lines, "\n"))
}

func (b *Bot) handleListInvites(chatID int64, args string) {
	target := strings.TrimSpace(args)
	if target == "" {
		b.sendAutoDelete(chatID, "❌ 用法: /list\\_invites `<账号ID|all>`")
		return
	}

	var accountList []model.GptAccount

	if strings.EqualFold(target, "all") {
		accs, err := b.db.GetAllAccounts()
		if err != nil {
			b.sendAutoDelete(chatID, fmt.Sprintf("❌ 查询失败: %v", err))
			return
		}
		accountList = accs
	} else {
		id, err := strconv.ParseInt(target, 10, 64)
		if err != nil {
			b.sendAutoDelete(chatID, "❌ 账号ID格式错误")
			return
		}
		acc, err := b.db.GetAccountByID(id)
		if err != nil {
			b.sendAutoDelete(chatID, fmt.Sprintf("❌ 账号不存在: %v", err))
			return
		}
		accountList = append(accountList, *acc)
	}

	if len(accountList) == 0 {
		b.send(chatID, "📋 暂无账号")
		return
	}

	msgID := b.sendAndGetID(chatID, fmt.Sprintf("⏳ 正在查询 %d 个账号的待进入邀请...", len(accountList)))

	var lines []string
	totalInvites := 0
	for _, acc := range accountList {
		if acc.Token == "" {
			continue
		}
		_, invites, err := b.client.GetInvites(&acc)
		if err != nil {
			lines = append(lines, fmt.Sprintf("❌ ID=%d %s: %s", acc.ID, acc.Email, err.Error()))
			continue
		}
		if len(invites) == 0 {
			continue
		}
		totalInvites += len(invites)
		var inviteLines []string
		for _, inv := range invites {
			inviteLines = append(inviteLines, fmt.Sprintf("    📧 `%s` (%s)", inv.EmailAddress, inv.Role))
		}
		lines = append(lines, fmt.Sprintf("*ID=%d* %s (%d 个):\n%s",
			acc.ID, acc.Email, len(invites), strings.Join(inviteLines, "\n")))
	}

	if totalInvites == 0 {
		b.editMsg(chatID, msgID, "📭 暂无待进入的邀请")
		return
	}

	b.editMsg(chatID, msgID, fmt.Sprintf("📋 *待进入邀请* (%d 个):\n\n%s\n\n💡 使用 /kick `<账号ID>` `<邮箱>` 可取消邀请",
		totalInvites, strings.Join(lines, "\n\n")))
}

func (b *Bot) handleDelAccount(chatID int64, args string) {
	idStr := strings.TrimSpace(args)
	if idStr == "" {
		b.sendAutoDelete(chatID, "❌ 用法: /del\\_account `<账号ID>`")
		return
	}
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		b.sendAutoDelete(chatID, "❌ 账号ID格式错误，请输入数字")
		return
	}

	// Get account info first for display.
	acct, err := b.db.GetAccountByID(id)
	if err != nil {
		b.sendAutoDelete(chatID, fmt.Sprintf("❌ 账号不存在: %v", err))
		return
	}

	if err := b.db.DeleteAccount(id); err != nil {
		b.sendAutoDelete(chatID, fmt.Sprintf("❌ 删除失败: %v", err))
		return
	}

	b.send(chatID, fmt.Sprintf("✅ 已删除账号 ID=%d (%s)\n关联的兑换码也已清除", id, acct.Email))
}

// ─── Helpers ────────────────────────────────────────────────

func (b *Bot) send(chatID int64, text string) {
	msg := tgbotapi.NewMessage(chatID, text)
	msg.ParseMode = "Markdown"
	msg.DisableWebPagePreview = true
	if _, err := b.api.Send(msg); err != nil {
		log.Printf("[Bot] 发送消息失败: %v", err)
	}
}

// sendAutoDelete sends a message and auto-deletes it after 1 minute.
func (b *Bot) sendAutoDelete(chatID int64, text string) {
	msgID := b.sendAndGetID(chatID, text)
	if msgID != 0 {
		go func() {
			time.Sleep(time.Minute)
			b.deleteMsg(chatID, msgID)
		}()
	}
}

func (b *Bot) sendAndGetID(chatID int64, text string) int {
	msg := tgbotapi.NewMessage(chatID, text)
	msg.ParseMode = "Markdown"
	msg.DisableWebPagePreview = true
	sentMsg, err := b.api.Send(msg)
	if err != nil {
		log.Printf("[Bot] 发送消息失败: %v", err)
		return 0
	}
	return sentMsg.MessageID
}

func (b *Bot) editMsg(chatID int64, msgID int, text string) {
	if msgID == 0 {
		b.send(chatID, text)
		return
	}
	edit := tgbotapi.NewEditMessageText(chatID, msgID, text)
	edit.ParseMode = "Markdown"
	edit.DisableWebPagePreview = true
	if _, err := b.api.Send(edit); err != nil {
		// Fallback: try editing as caption (for photo messages).
		editCaption := tgbotapi.NewEditMessageCaption(chatID, msgID, text)
		editCaption.ParseMode = "Markdown"
		if _, err2 := b.api.Send(editCaption); err2 != nil {
			log.Printf("[Bot] 编辑消息失败: %v", err2)
		}
	}
}

func (b *Bot) editMsgWithKeyboard(chatID int64, msgID int, text string, keyboard *tgbotapi.InlineKeyboardMarkup) {
	edit := tgbotapi.NewEditMessageText(chatID, msgID, text)
	edit.ParseMode = "Markdown"
	edit.DisableWebPagePreview = true
	if keyboard != nil {
		edit.ReplyMarkup = keyboard
	}
	if _, err := b.api.Send(edit); err != nil {
		// Fallback: try editing as caption (for photo messages).
		editCaption := tgbotapi.NewEditMessageCaption(chatID, msgID, text)
		editCaption.ParseMode = "Markdown"
		if keyboard != nil {
			editCaption.ReplyMarkup = keyboard
		}
		if _, err2 := b.api.Send(editCaption); err2 != nil {
			log.Printf("[Bot] 编辑消息失败: %v", err2)
		}
	}
}

func (b *Bot) deleteMsg(chatID int64, msgID int) {
	if msgID == 0 {
		return
	}
	del := tgbotapi.NewDeleteMessage(chatID, msgID)
	if _, err := b.api.Request(del); err != nil {
		log.Printf("[Bot] 删除消息失败: %v", err)
	}
}

func (b *Bot) requireAdmin(chatID int64, userID int64, fn func()) {
	if !b.cfg.IsAdmin(userID) {
		b.send(chatID, "🚫 该命令仅限管理员使用")
		return
	}
	fn()
}

func syncCounts(db *database.DB, client *chatgpt.Client, account *model.GptAccount) {
	userTotal, _, err := client.GetUsers(account)
	if err != nil {
		return
	}
	inviteTotal, _, err := client.GetInvites(account)
	if err != nil {
		return
	}
	_ = db.UpdateAccountCounts(account.ID, userTotal, inviteTotal)
}

// formatDate extracts the date part (YYYY-MM-DD) from an ISO datetime string.
func formatDate(s string) string {
	if t, err := time.Parse(time.RFC3339, s); err == nil {
		return t.Format("2006-01-02")
	}
	if len(s) >= 10 {
		return s[:10]
	}
	return s
}
