//go:build ignore

// 性能测试数据生成/清理脚本
// 用法:
//   source .env.local
//   go run ./scripts/perf_query/seed.go                       # 默认生成 3000 万卡
//   go run ./scripts/perf_query/seed.go -total 1000000        # 生成 100 万卡（试跑）
//   go run ./scripts/perf_query/seed.go -action cleanup       # 清理测试数据
//   go run ./scripts/perf_query/seed.go -action verify        # 验证数据分布
//   go run ./scripts/perf_query/seed.go -action add-index     # 创建优化索引
//   go run ./scripts/perf_query/seed.go -action drop-index    # 删除优化索引

package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	_ "github.com/jackc/pgx/v5/stdlib"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// 测试数据标记常量
const (
	testBatchNo        = "PERF-TEST-30M"
	testShopCodePrefix = "PERF-TEST-SHOP-"
	testAgentUsername  = "perf_test_agent"
	testAgentPassword  = "PerfTest@123456"
	numShops           = 200
	agentChildShops    = 6 // 代理商有 6 个下级店铺（自己 + 6 = 7 个总共）
)

// 简化模型（仅用于创建测试数据，不引入项目内部包）
type testShop struct {
	ID           uint   `gorm:"primaryKey"`
	ShopName     string `gorm:"column:shop_name"`
	ShopCode     string `gorm:"column:shop_code"`
	ParentID     *uint  `gorm:"column:parent_id"`
	Level        int    `gorm:"column:level"`
	ContactName  string `gorm:"column:contact_name"`
	ContactPhone string `gorm:"column:contact_phone"`
	Status       int    `gorm:"column:status"`
	Creator      uint   `gorm:"column:creator"`
	Updater      uint   `gorm:"column:updater"`
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

func (testShop) TableName() string { return "tb_shop" }

type testAccount struct {
	ID           uint   `gorm:"primaryKey"`
	Username     string `gorm:"column:username"`
	Phone        string `gorm:"column:phone"`
	Password     string `gorm:"column:password"`
	UserType     int    `gorm:"column:user_type"`
	ShopID       *uint  `gorm:"column:shop_id"`
	EnterpriseID *uint  `gorm:"column:enterprise_id"`
	Status       int    `gorm:"column:status"`
	Creator      uint   `gorm:"column:creator"`
	Updater      uint   `gorm:"column:updater"`
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

func (testAccount) TableName() string { return "tb_account" }

var (
	action    = flag.String("action", "seed", "操作: seed / cleanup / verify / add-index / drop-index")
	total     = flag.Int("total", 30000000, "生成卡总数")
	batchSize = flag.Int("batch", 5000000, "每批插入数量")
)

func buildDSN() string {
	return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai",
		getEnv("JUNHONG_DATABASE_HOST", "cxd.whcxd.cn"),
		getEnv("JUNHONG_DATABASE_PORT", "16159"),
		getEnv("JUNHONG_DATABASE_USER", "erp_pgsql"),
		getEnv("JUNHONG_DATABASE_PASSWORD", "erp_2025"),
		getEnv("JUNHONG_DATABASE_DBNAME", "junhong_cmp_test"),
	)
}

func main() {
	flag.Parse()

	db, err := gorm.Open(postgres.Open(buildDSN()), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Warn),
	})
	if err != nil {
		log.Fatalf("❌ 连接数据库失败: %v", err)
	}

	sqlDB, _ := db.DB()
	sqlDB.SetMaxOpenConns(5)
	sqlDB.SetMaxIdleConns(3)
	fmt.Println("✅ 数据库连接成功")

	switch *action {
	case "seed":
		doSeed(db)
	case "cleanup":
		doCleanup(db)
	case "verify":
		doVerify(db)
	case "add-index":
		doAddIndex(db)
	case "drop-index":
		doDropIndex(db)
	default:
		log.Fatalf("❌ 未知操作: %s（支持: seed / cleanup / verify / add-index / drop-index）", *action)
	}
}

// ==================== Seed ====================

func doSeed(db *gorm.DB) {
	fmt.Println("\n🚀 开始生成性能测试数据（UNLOGGED 极速模式）")
	fmt.Printf("   目标卡数: %d\n", *total)
	fmt.Printf("   店铺数量: %d（代理商可见 %d 个）\n", numShops, agentChildShops+1)

	overallStart := time.Now()

	fmt.Println("\n📦 步骤 1/8: 创建测试店铺...")
	shopIDs := createTestShops(db)
	fmt.Printf("   ✅ 创建了 %d 个店铺\n", len(shopIDs))

	fmt.Println("\n👤 步骤 2/8: 创建测试代理商账号...")
	createTestAgent(db, shopIDs[0])
	fmt.Printf("   ✅ 账号: %s / %s (shop_id=%d)\n", testAgentUsername, testAgentPassword, shopIDs[0])

	// 调高 PG 配置减少 checkpoint 频率
	fmt.Println("\n⚙️  步骤 3/8: 临时调高 PostgreSQL 写入配置...")
	tuneForBulkLoad(db)

	// 先删索引再插入，速度提升 10~50 倍
	fmt.Println("\n📇 步骤 4/8: 临时删除非主键索引（加速批量插入）...")
	droppedIndexes := dropNonPKIndexes(db)
	fmt.Printf("   ✅ 删除了 %d 个索引\n", len(droppedIndexes))

	// 设置 UNLOGGED 跳过 WAL 写入（表数据少时几乎瞬间完成）
	fmt.Println("\n⚡ 步骤 5/8: 设置表为 UNLOGGED（跳过 WAL，极速写入）...")
	start := time.Now()
	if err := db.Exec("ALTER TABLE tb_iot_card SET UNLOGGED").Error; err != nil {
		log.Fatalf("设置 UNLOGGED 失败: %v", err)
	}
	fmt.Printf("   ✅ 已设置 UNLOGGED (耗时 %v)\n", time.Since(start))

	fmt.Println("\n💳 步骤 6/8: 批量插入卡数据（UNLOGGED + generate_series）...")
	insertCards(db, shopIDs)

	// 恢复为 LOGGED（会重写整张表到 WAL，30M 行约需 3-5 分钟）
	fmt.Println("\n🔒 步骤 7/8: 恢复表为 LOGGED（重写 WAL，请耐心等待）...")
	start = time.Now()
	if err := db.Exec("ALTER TABLE tb_iot_card SET LOGGED").Error; err != nil {
		// SET LOGGED 失败不致命，表仍然可用，只是崩溃会丢数据
		fmt.Printf("   ⚠️  恢复 LOGGED 失败（测试数据可接受）: %v\n", err)
	} else {
		fmt.Printf("   ✅ 已恢复 LOGGED (耗时 %v)\n", time.Since(start))
	}

	fmt.Println("\n📇 步骤 8/8: 重建索引 + ANALYZE...")
	rebuildIndexes(db, droppedIndexes)
	fmt.Printf("   ✅ 重建了 %d 个索引\n", len(droppedIndexes))

	start = time.Now()
	db.Exec("ANALYZE tb_iot_card")
	fmt.Printf("   ✅ ANALYZE 完成 (耗时 %v)\n", time.Since(start))

	// 恢复 PG 配置
	restoreAfterBulkLoad(db)

	fmt.Printf("\n🎉 全部完成！总耗时: %v\n", time.Since(overallStart))
	fmt.Println("\n📝 后续步骤:")
	fmt.Println("   1. 启动 API 服务: source .env.local && go run ./cmd/api/...")
	fmt.Printf("   2. 运行基准测试: go run ./scripts/perf_query/bench.go\n")
	fmt.Println("   3. 测试完成后清理: go run ./scripts/perf_query/seed.go -action cleanup")
}

// tuneForBulkLoad 临时调高 PG 配置以加速批量写入
func tuneForBulkLoad(db *gorm.DB) {
	settings := []struct {
		sql  string
		desc string
	}{
		{"ALTER SYSTEM SET max_wal_size = '4GB'", "max_wal_size → 4GB（减少 checkpoint 频率）"},
		{"ALTER SYSTEM SET checkpoint_timeout = '30min'", "checkpoint_timeout → 30min"},
		{"ALTER SYSTEM SET synchronous_commit = 'off'", "synchronous_commit → off（不等待 WAL 刷盘）"},
	}
	for _, s := range settings {
		if err := db.Exec(s.sql).Error; err != nil {
			fmt.Printf("   ⚠️  %s 失败: %v\n", s.desc, err)
		} else {
			fmt.Printf("   ✅ %s\n", s.desc)
		}
	}
	db.Exec("SELECT pg_reload_conf()")
	fmt.Println("   ✅ 配置已重载")
}

// restoreAfterBulkLoad 恢复 PG 配置
func restoreAfterBulkLoad(db *gorm.DB) {
	fmt.Println("\n⚙️  恢复 PostgreSQL 配置...")
	db.Exec("ALTER SYSTEM SET max_wal_size = '1GB'")
	db.Exec("ALTER SYSTEM SET checkpoint_timeout = '5min'")
	db.Exec("ALTER SYSTEM SET synchronous_commit = 'on'")
	db.Exec("SELECT pg_reload_conf()")
	fmt.Println("   ✅ PG 配置已恢复默认")
}

func createTestShops(db *gorm.DB) []uint {
	now := time.Now()
	shopIDs := make([]uint, 0, numShops)

	// 先清理可能存在的旧测试店铺
	db.Exec("DELETE FROM tb_shop WHERE shop_code LIKE ?", testShopCodePrefix+"%")

	// 创建根店铺（代理商总店）
	rootShop := testShop{
		ShopName:     "性能测试-总代理",
		ShopCode:     testShopCodePrefix + "ROOT",
		Level:        1,
		ContactName:  "测试联系人",
		ContactPhone: "13800000000",
		Status:       1,
		Creator:      1,
		Updater:      1,
		CreatedAt:    now,
		UpdatedAt:    now,
	}
	if err := db.Create(&rootShop).Error; err != nil {
		log.Fatalf("创建根店铺失败: %v", err)
	}
	shopIDs = append(shopIDs, rootShop.ID)

	// 创建 6 个子店铺（代理商下级）
	for i := 1; i <= agentChildShops; i++ {
		child := testShop{
			ShopName:     fmt.Sprintf("性能测试-分店%d", i),
			ShopCode:     fmt.Sprintf("%sSUB-%d", testShopCodePrefix, i),
			ParentID:     &rootShop.ID,
			Level:        2,
			ContactName:  "测试联系人",
			ContactPhone: fmt.Sprintf("1380000%04d", i),
			Status:       1,
			Creator:      1,
			Updater:      1,
			CreatedAt:    now,
			UpdatedAt:    now,
		}
		if err := db.Create(&child).Error; err != nil {
			log.Fatalf("创建子店铺失败: %v", err)
		}
		shopIDs = append(shopIDs, child.ID)
	}

	// 创建其余独立店铺（其他代理商）
	for i := agentChildShops + 1; i < numShops; i++ {
		indep := testShop{
			ShopName:     fmt.Sprintf("性能测试-独立店铺%d", i),
			ShopCode:     fmt.Sprintf("%sINDEP-%d", testShopCodePrefix, i),
			Level:        1,
			ContactName:  "测试联系人",
			ContactPhone: fmt.Sprintf("1390000%04d", i),
			Status:       1,
			Creator:      1,
			Updater:      1,
			CreatedAt:    now,
			UpdatedAt:    now,
		}
		if err := db.Create(&indep).Error; err != nil {
			log.Fatalf("创建独立店铺失败: %v", err)
		}
		shopIDs = append(shopIDs, indep.ID)
	}

	return shopIDs
}

func createTestAgent(db *gorm.DB, shopID uint) {
	// 先清理可能存在的旧测试账号
	db.Exec("DELETE FROM tb_account WHERE username = ?", testAgentUsername)

	// bcrypt 加密密码
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(testAgentPassword), bcrypt.DefaultCost)
	if err != nil {
		log.Fatalf("加密密码失败: %v", err)
	}

	account := testAccount{
		Username:  testAgentUsername,
		Phone:     "19999999999",
		Password:  string(hashedPassword),
		UserType:  3, // 代理账号
		ShopID:    &shopID,
		Status:    1,
		Creator:   1,
		Updater:   1,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	if err := db.Create(&account).Error; err != nil {
		log.Fatalf("创建代理账号失败: %v", err)
	}
}

func insertCards(db *gorm.DB, shopIDs []uint) {
	idStrs := make([]string, len(shopIDs))
	for i, id := range shopIDs {
		idStrs[i] = strconv.FormatUint(uint64(id), 10)
	}
	pgArray := "ARRAY[" + strings.Join(idStrs, ",") + "]::bigint[]"

	sqlTemplate := fmt.Sprintf(`
INSERT INTO tb_iot_card (
    iccid, card_category, carrier_id, carrier_type, carrier_name,
    imsi, msisdn, batch_no, supplier, cost_price, distribute_price,
    status, shop_id, activation_status, real_name_status, network_status,
    data_usage_mb, enable_polling, created_at, updated_at, creator, updater, series_id,
    is_standalone
)
SELECT
    'T' || lpad(i::text, 19, '0'),
    CASE WHEN random() < 0.85 THEN 'normal' ELSE 'industry' END,
    (i %% 4 + 1),
    CASE (i %% 4) WHEN 0 THEN 'CMCC' WHEN 1 THEN 'CUCC' WHEN 2 THEN 'CTCC' ELSE 'CBN' END,
    CASE (i %% 4) WHEN 0 THEN '中国移动' WHEN 1 THEN '中国联通' WHEN 2 THEN '中国电信' ELSE '中国广电' END,
    lpad(i::text, 15, '0'),
    '1' || lpad((i %% 10000000)::text, 10, '0'),
    '%s',
    CASE (i %% 3) WHEN 0 THEN '供应商A' WHEN 1 THEN '供应商B' ELSE '供应商C' END,
    (random() * 5000 + 500)::bigint,
    (random() * 3000 + 1000)::bigint,
    CASE WHEN random() < 0.15 THEN 1 WHEN random() < 0.30 THEN 2 WHEN random() < 0.90 THEN 3 ELSE 4 END,
    (%s)[GREATEST(1, LEAST(%d, floor(%d * power(random(), 2.5))::int + 1))],
    CASE WHEN random() < 0.7 THEN 1 ELSE 0 END,
    CASE WHEN random() < 0.6 THEN 1 ELSE 0 END,
    CASE WHEN random() < 0.8 THEN 1 ELSE 0 END,
    (random() * 100000)::bigint,
    random() < 0.9,
    now() - interval '1 day' * (random() * 730),
    now() - interval '1 day' * (random() * 30),
    1, 1,
    CASE WHEN random() < 0.8 THEN (random() * 20 + 1)::int ELSE NULL END,
    true
FROM generate_series($1::bigint, $2::bigint) AS s(i)
`, testBatchNo, pgArray, numShops, numShops)

	// 500 万/批 × 4 并发 worker（HDD 上减少 I/O 竞争）
	const workerCount = 4
	const chunkSize = 5000000

	type task struct {
		batchNum int
		start    int
		end      int
	}

	totalBatches := (*total + chunkSize - 1) / chunkSize
	taskCh := make(chan task, totalBatches)
	var wg sync.WaitGroup
	var insertedCount int64
	var errCount int64
	totalStart := time.Now()

	dsn := buildDSN()

	for w := 0; w < workerCount; w++ {
		wg.Add(1)
		go func(workerID int) {
			defer wg.Done()
			conn, err := sql.Open("pgx", dsn)
			if err != nil {
				log.Printf("Worker %d 连接失败: %v", workerID, err)
				return
			}
			defer conn.Close()
			conn.SetMaxOpenConns(1)
			conn.Exec("SET synchronous_commit = off")
			conn.Exec("SET work_mem = '256MB'")

			for t := range taskCh {
				_, err := conn.Exec(sqlTemplate, t.start, t.end)
				if err != nil {
					log.Printf("Worker %d 批次 %d 失败: %v", workerID, t.batchNum, err)
					atomic.AddInt64(&errCount, 1)
					continue
				}

				count := t.end - t.start + 1
				done := atomic.AddInt64(&insertedCount, int64(count))
				elapsed := time.Since(totalStart).Seconds()
				speed := float64(done) / elapsed
				remaining := float64(int64(*total)-done) / speed
				fmt.Printf("   [W%d] 批次 %d/%d 完成 | 累计 %d/%d (%.1f%%) | %.0f条/秒 | 剩余 %.0f秒\n",
					workerID, t.batchNum, totalBatches, done, *total,
					float64(done)*100/float64(*total), speed, remaining)
			}
		}(w)
	}

	for b := 0; b < totalBatches; b++ {
		start := b*chunkSize + 1
		end := (b + 1) * chunkSize
		if end > *total {
			end = *total
		}
		taskCh <- task{batchNum: b + 1, start: start, end: end}
	}
	close(taskCh)
	wg.Wait()

	elapsed := time.Since(totalStart)
	fmt.Printf("   ✅ 共插入 %d 条 (失败 %d 批) | 总耗时 %v | 平均 %.0f 条/秒\n",
		atomic.LoadInt64(&insertedCount), atomic.LoadInt64(&errCount), elapsed.Round(time.Second),
		float64(atomic.LoadInt64(&insertedCount))/elapsed.Seconds())
}

// ==================== Cleanup ====================

func doCleanup(db *gorm.DB) {
	fmt.Println("\n🧹 开始清理性能测试数据")

	// 删除测试卡数据（分批删除）
	fmt.Println("\n💳 删除测试卡数据...")
	totalDeleted := 0
	start := time.Now()
	for {
		result := db.Exec("DELETE FROM tb_iot_card WHERE ctid IN (SELECT ctid FROM tb_iot_card WHERE batch_no = ? LIMIT 500000)", testBatchNo)
		if result.Error != nil {
			log.Fatalf("删除失败: %v", result.Error)
		}
		if result.RowsAffected == 0 {
			break
		}
		totalDeleted += int(result.RowsAffected)
		elapsed := time.Since(start)
		speed := float64(totalDeleted) / elapsed.Seconds()
		fmt.Printf("   已删除 %d 条 (%.0f 条/秒)\n", totalDeleted, speed)
	}
	fmt.Printf("   ✅ 共删除 %d 条卡数据 (耗时 %v)\n", totalDeleted, time.Since(start).Round(time.Second))

	// 删除测试店铺
	fmt.Println("\n📦 删除测试店铺...")
	result := db.Exec("DELETE FROM tb_shop WHERE shop_code LIKE ?", testShopCodePrefix+"%")
	fmt.Printf("   ✅ 删除了 %d 个店铺\n", result.RowsAffected)

	// 删除测试账号
	fmt.Println("\n👤 删除测试账号...")
	result = db.Exec("DELETE FROM tb_account WHERE username = ?", testAgentUsername)
	fmt.Printf("   ✅ 删除了 %d 个账号\n", result.RowsAffected)

	// 删除优化索引（如果存在）
	fmt.Println("\n📇 清理优化索引...")
	db.Exec("DROP INDEX IF EXISTS idx_iot_card_perf_shop_created")
	db.Exec("DROP INDEX IF EXISTS idx_iot_card_perf_shop_status_created")
	fmt.Println("   ✅ 索引已清理")

	fmt.Println("\n⚠️  建议手动执行 VACUUM FULL tb_iot_card; 回收磁盘空间")
	fmt.Println("🎉 清理完成！")
}

// ==================== Verify ====================

func doVerify(db *gorm.DB) {
	fmt.Println("\n📊 验证测试数据分布")

	// 总卡数
	var total int64
	db.Raw("SELECT count(*) FROM tb_iot_card WHERE batch_no = ? AND deleted_at IS NULL", testBatchNo).Scan(&total)
	fmt.Printf("\n总测试卡数: %d\n", total)

	// 代理商可见卡数
	var agentVisible int64
	db.Raw(`
		SELECT count(*) FROM tb_iot_card 
		WHERE batch_no = ? AND deleted_at IS NULL 
		AND shop_id IN (SELECT id FROM tb_shop WHERE shop_code LIKE ? AND deleted_at IS NULL)
	`, testBatchNo, testShopCodePrefix+"ROOT").Scan(&agentVisible)

	var agentSubVisible int64
	db.Raw(`
		SELECT count(*) FROM tb_iot_card 
		WHERE batch_no = ? AND deleted_at IS NULL 
		AND shop_id IN (
			SELECT id FROM tb_shop 
			WHERE (shop_code LIKE ? OR shop_code LIKE ?) AND deleted_at IS NULL
		)
	`, testBatchNo, testShopCodePrefix+"ROOT", testShopCodePrefix+"SUB-%").Scan(&agentSubVisible)
	fmt.Printf("代理商总店卡数: %d\n", agentVisible)
	fmt.Printf("代理商(含下级)卡数: %d (占比 %.1f%%)\n", agentSubVisible, float64(agentSubVisible)*100/float64(total))

	// 前 20 名店铺分布
	type shopDist struct {
		ShopID uint
		Cnt    int64
	}
	var dists []shopDist
	db.Raw(`
		SELECT shop_id, count(*) as cnt FROM tb_iot_card 
		WHERE batch_no = ? AND deleted_at IS NULL 
		GROUP BY shop_id ORDER BY cnt DESC LIMIT 20
	`, testBatchNo).Scan(&dists)

	fmt.Println("\n前 20 名店铺卡量分布:")
	fmt.Printf("  %-12s %-12s %-8s\n", "shop_id", "卡数", "占比")
	for _, d := range dists {
		fmt.Printf("  %-12d %-12d %.2f%%\n", d.ShopID, d.Cnt, float64(d.Cnt)*100/float64(total))
	}

	// 状态分布
	type statusDist struct {
		Status int
		Cnt    int64
	}
	var statuses []statusDist
	db.Raw("SELECT status, count(*) as cnt FROM tb_iot_card WHERE batch_no = ? AND deleted_at IS NULL GROUP BY status ORDER BY status", testBatchNo).Scan(&statuses)

	fmt.Println("\n状态分布:")
	statusNames := map[int]string{1: "在库", 2: "已分销", 3: "已激活", 4: "已停用"}
	for _, s := range statuses {
		fmt.Printf("  状态 %d (%s): %d (%.1f%%)\n", s.Status, statusNames[s.Status], s.Cnt, float64(s.Cnt)*100/float64(total))
	}

	// 运营商分布
	type carrierDist struct {
		CarrierID uint
		Cnt       int64
	}
	var carriers []carrierDist
	db.Raw("SELECT carrier_id, count(*) as cnt FROM tb_iot_card WHERE batch_no = ? AND deleted_at IS NULL GROUP BY carrier_id ORDER BY carrier_id", testBatchNo).Scan(&carriers)

	fmt.Println("\n运营商分布:")
	for _, c := range carriers {
		fmt.Printf("  carrier_id=%d: %d (%.1f%%)\n", c.CarrierID, c.Cnt, float64(c.Cnt)*100/float64(total))
	}

	// 表大小
	var tableSize, indexSize string
	db.Raw("SELECT pg_size_pretty(pg_relation_size('tb_iot_card'))").Scan(&tableSize)
	db.Raw("SELECT pg_size_pretty(pg_indexes_size('tb_iot_card'))").Scan(&indexSize)
	fmt.Printf("\n表大小: %s | 索引大小: %s\n", tableSize, indexSize)

	// 测试代理商账号信息
	var agentInfo struct {
		ID       uint
		Username string
		ShopID   uint
	}
	db.Raw("SELECT id, username, shop_id FROM tb_account WHERE username = ? AND deleted_at IS NULL", testAgentUsername).Scan(&agentInfo)
	fmt.Printf("\n测试代理商: id=%d, username=%s, shop_id=%d\n", agentInfo.ID, agentInfo.Username, agentInfo.ShopID)
}

// ==================== Bulk Load Index Management ====================

type indexDef struct {
	Name       string
	Definition string
}

func dropNonPKIndexes(db *gorm.DB) []indexDef {
	sqlDB, _ := db.DB()

	var indexes []indexDef
	rows, err := sqlDB.Query(`
		SELECT indexname, indexdef FROM pg_indexes 
		WHERE tablename = 'tb_iot_card' AND indexname != 'iot_cards_pkey'
		ORDER BY indexname
	`)
	if err != nil {
		log.Fatalf("查询索引失败: %v", err)
	}
	defer rows.Close()

	for rows.Next() {
		var idx indexDef
		rows.Scan(&idx.Name, &idx.Definition)
		indexes = append(indexes, idx)
	}

	for _, idx := range indexes {
		fmt.Printf("   删除 %s ...\n", idx.Name)
		sqlDB.Exec("DROP INDEX IF EXISTS " + idx.Name)
	}

	return indexes
}

func rebuildIndexes(db *gorm.DB, indexes []indexDef) {
	sqlDB, _ := db.DB()

	for _, idx := range indexes {
		fmt.Printf("   重建 %s ...", idx.Name)
		start := time.Now()
		if _, err := sqlDB.Exec(idx.Definition); err != nil {
			fmt.Printf(" ❌ 失败: %v\n", err)
			continue
		}
		fmt.Printf(" ✅ (%v)\n", time.Since(start).Round(time.Second))
	}
}

// ==================== Optimization Index Management ====================

func doAddIndex(db *gorm.DB) {
	fmt.Println("\n📇 创建优化复合索引（CONCURRENTLY，不阻塞读写）")

	indexes := []struct {
		name string
		sql  string
	}{
		{
			name: "idx_iot_card_perf_shop_created",
			sql:  "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_iot_card_perf_shop_created ON tb_iot_card (shop_id, created_at DESC) WHERE deleted_at IS NULL",
		},
		{
			name: "idx_iot_card_perf_shop_status_created",
			sql:  "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_iot_card_perf_shop_status_created ON tb_iot_card (shop_id, status, created_at DESC) WHERE deleted_at IS NULL",
		},
	}

	// CONCURRENTLY 不能在事务内执行，直接用底层连接
	sqlDB, _ := db.DB()

	// pg_trgm GIN 索引：加速 ICCID/MSISDN 中间模糊搜索（LIKE '%xxx%'）
	fmt.Println("\n   启用 pg_trgm 扩展...")
	if _, err := sqlDB.Exec("CREATE EXTENSION IF NOT EXISTS pg_trgm"); err != nil {
		fmt.Printf("   ⚠️  pg_trgm 扩展创建失败（可能需要超级管理员权限）: %v\n", err)
	} else {
		fmt.Println("   ✅ pg_trgm 扩展已启用")
		trgmIndexes := []struct {
			name string
			sql  string
		}{
			{
				name: "idx_iot_card_iccid_trgm",
				sql:  "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_iot_card_iccid_trgm ON tb_iot_card USING gin (iccid gin_trgm_ops)",
			},
			{
				name: "idx_iot_card_msisdn_trgm",
				sql:  "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_iot_card_msisdn_trgm ON tb_iot_card USING gin (msisdn gin_trgm_ops)",
			},
		}
		for _, idx := range trgmIndexes {
			indexes = append(indexes, struct {
				name string
				sql  string
			}{idx.name, idx.sql})
		}
	}

	for _, idx := range indexes {
		fmt.Printf("   创建 %s ...", idx.name)
		start := time.Now()
		if _, err := sqlDB.Exec(idx.sql); err != nil {
			fmt.Printf(" ❌ 失败: %v\n", err)
			continue
		}
		fmt.Printf(" ✅ (%v)\n", time.Since(start).Round(time.Second))
	}

	// 删除重复索引
	fmt.Println("   清理重复索引 idx_tb_iot_card_shop_id（与 idx_iot_card_shop_id 重复）...")
	sqlDB.Exec("DROP INDEX IF EXISTS idx_tb_iot_card_shop_id")

	fmt.Println("\n   更新统计信息...")
	sqlDB.Exec("ANALYZE tb_iot_card")
	fmt.Println("   ✅ 索引优化完成")
}

func doDropIndex(db *gorm.DB) {
	fmt.Println("\n📇 删除优化索引（恢复到原始状态）")
	sqlDB, _ := db.DB()
	sqlDB.Exec("DROP INDEX IF EXISTS idx_iot_card_perf_shop_created")
	sqlDB.Exec("DROP INDEX IF EXISTS idx_iot_card_perf_shop_status_created")
	sqlDB.Exec("DROP INDEX IF EXISTS idx_iot_card_iccid_trgm")
	sqlDB.Exec("DROP INDEX IF EXISTS idx_iot_card_msisdn_trgm")
	sqlDB.Exec("ANALYZE tb_iot_card")
	fmt.Println("   ✅ 已删除优化索引")
}

// ==================== Utils ====================

func getEnv(key, fallback string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return fallback
}
