// migrate 将旧版 JSON 文件数据迁移到 MySQL 数据库。
//
// 用法:
//
//	go run ./cmd/migrate [--data-dir ./data]
//
// 环境变量（与主程序相同）:
//
//	APP_ENV=production  使用生产数据库（默认使用开发测试库）
//	DB_DSN              自定义 DSN（优先级最高）
package main

import (
	"encoding/base64"
	"encoding/json"
	"flag"
	"log"
	"os"
	"path/filepath"
	"strings"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"sproutgate-backend/internal/database"
	"sproutgate-backend/internal/models"
	"sproutgate-backend/internal/storage"
)

func main() {
	dataDir := flag.String("data-dir", "./data", "旧版 JSON 数据目录路径")
	flag.Parse()

	absDir, err := filepath.Abs(*dataDir)
	if err != nil {
		log.Fatalf("路径解析失败: %v", err)
	}
	log.Printf("数据目录: %s", absDir)

	db, err := database.Open()
	if err != nil {
		log.Fatalf("连接数据库失败: %v", err)
	}

	// 自动迁移表结构
	if err := db.AutoMigrate(
		&storage.DBUser{},
		&storage.DBPendingUser{},
		&storage.DBResetPassword{},
		&storage.DBSecondaryEmailVerification{},
		&storage.DBAppConfig{},
		&storage.DBInviteCode{},
	); err != nil {
		log.Fatalf("表结构同步失败: %v", err)
	}
	log.Println("✓ 表结构已同步")

	migrateAdminConfig(db, absDir)
	migrateAuthConfig(db, absDir)
	migrateEmailConfig(db, absDir)
	migrateCheckinConfig(db, absDir)
	migrateRegistrationConfig(db, absDir)
	migrateUsers(db, absDir)

	log.Println("\n✅ 数据迁移完成！")
}

// ── 辅助函数 ──────────────────────────────────────────────────────────────────

func readJSONFile(path string, target any) error {
	raw, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	return json.Unmarshal(raw, target)
}

// putConfig 将任意值序列化为 JSON 后写入 app_configs（冲突时整体覆盖）。
func putConfig(db *gorm.DB, key string, value any) {
	raw, err := json.Marshal(value)
	if err != nil {
		log.Printf("  ✗ 序列化 [%s] 失败: %v", key, err)
		return
	}
	row := storage.DBAppConfig{ConfigKey: key, ConfigValue: string(raw)}
	if err := db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&row).Error; err != nil {
		log.Printf("  ✗ app_configs[%s] 失败: %v", key, err)
	} else {
		log.Printf("  ✓ app_configs[%s]", key)
	}
}

// ── 各节配置迁移 ──────────────────────────────────────────────────────────────

func migrateAdminConfig(db *gorm.DB, dataDir string) {
	path := filepath.Join(dataDir, "config", "admin.json")
	var cfg storage.AdminConfig
	if err := readJSONFile(path, &cfg); err != nil {
		log.Printf("[admin] 跳过（%v）", err)
		return
	}
	putConfig(db, "admin", cfg)
}

func migrateAuthConfig(db *gorm.DB, dataDir string) {
	path := filepath.Join(dataDir, "config", "auth.json")
	var cfg storage.AuthConfig
	if err := readJSONFile(path, &cfg); err != nil {
		log.Printf("[auth] 跳过（%v）", err)
		return
	}
	putConfig(db, "auth", cfg)
}

func migrateEmailConfig(db *gorm.DB, dataDir string) {
	path := filepath.Join(dataDir, "config", "email.json")
	var cfg storage.EmailConfig
	if err := readJSONFile(path, &cfg); err != nil {
		log.Printf("[email] 跳过（%v）", err)
		return
	}
	putConfig(db, "email", cfg)
}

func migrateCheckinConfig(db *gorm.DB, dataDir string) {
	path := filepath.Join(dataDir, "config", "checkin.json")
	var cfg storage.CheckInConfig
	if err := readJSONFile(path, &cfg); err != nil {
		log.Printf("[checkin] 跳过（%v）", err)
		return
	}
	putConfig(db, "checkin", cfg)
}

type oldRegistrationConfig struct {
	RequireInviteCode bool                  `json:"requireInviteCode"`
	Invites           []storage.InviteEntry `json:"invites"`
}

type registrationPolicy struct {
	RequireInviteCode          bool `json:"requireInviteCode"`
	ForbiddenAccounts          string `json:"forbiddenAccounts"`
	InviteRegisterRewardCoins  *int `json:"inviteRegisterRewardCoins,omitempty"`
}

func migrateRegistrationConfig(db *gorm.DB, dataDir string) {
	path := filepath.Join(dataDir, "config", "registration.json")
	var old oldRegistrationConfig
	if err := readJSONFile(path, &old); err != nil {
		log.Printf("[registration] 跳过（%v）", err)
		return
	}

	putConfig(db, "registration", registrationPolicy{RequireInviteCode: old.RequireInviteCode})

	for _, entry := range old.Invites {
		row := storage.DBInviteCode{
			Code:      entry.Code,
			Note:      entry.Note,
			MaxUses:   entry.MaxUses,
			Uses:      entry.Uses,
			ExpiresAt: entry.ExpiresAt,
			CreatedAt: entry.CreatedAt,
		}
		if err := db.Clauses(clause.OnConflict{DoNothing: true}).Create(&row).Error; err != nil {
			log.Printf("  ✗ 邀请码 %s 失败: %v", entry.Code, err)
		} else {
			log.Printf("  ✓ 邀请码 %s", entry.Code)
		}
	}
}

func migrateUsers(db *gorm.DB, dataDir string) {
	usersDir := filepath.Join(dataDir, "users")
	entries, err := os.ReadDir(usersDir)
	if err != nil {
		log.Printf("[users] 读取目录失败: %v", err)
		return
	}

	total, ok, skipped := 0, 0, 0
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".json") {
			continue
		}
		total++
		path := filepath.Join(usersDir, entry.Name())

		var record models.UserRecord
		if err := readJSONFile(path, &record); err != nil {
			log.Printf("  ✗ 读取 %s 失败: %v", entry.Name(), err)
			skipped++
			continue
		}

		// 优先用 JSON 中的 account 字段，没有则从文件名（base64）解码
		if strings.TrimSpace(record.Account) == "" {
			name := strings.TrimSuffix(entry.Name(), ".json")
			decoded, decErr := base64.RawURLEncoding.DecodeString(name)
			if decErr != nil || len(decoded) == 0 {
				log.Printf("  ✗ 无法确定账号，跳过 %s", entry.Name())
				skipped++
				continue
			}
			record.Account = string(decoded)
		}

		row := storage.DBUserFromRecord(record)
		if err := db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&row).Error; err != nil {
			log.Printf("  ✗ 用户 %s 失败: %v", record.Account, err)
			skipped++
		} else {
			log.Printf("  ✓ 用户 %s", record.Account)
			ok++
		}
	}
	log.Printf("[users] 合计 %d 个，成功 %d，跳过 %d", total, ok, skipped)
}
