package data

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"

	integrationbiz "kra/internal/biz/integration"
	"kra/internal/config"
	dataintegration "kra/internal/data/integration"
	"kra/internal/integration/storage"

	"gopkg.in/yaml.v3"
	"gorm.io/gorm"
)

func openIntegrationConfigTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared", nil)
	if err != nil {
		t.Fatal(err)
	}
	sqlDB, err := db.DB()
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = sqlDB.Close() })
	if err = db.AutoMigrate(&dataintegration.ConfigPO{}); err != nil {
		t.Fatal(err)
	}
	return db
}

func TestMigrateAllCreatesIntegrationConfigTable(t *testing.T) {
	db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared", nil)
	if err != nil {
		t.Fatal(err)
	}
	sqlDB, err := db.DB()
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = sqlDB.Close() })
	if err = migrateAll(db, testCatalog()); err != nil {
		t.Fatal(err)
	}
	if !db.Migrator().HasTable(&dataintegration.ConfigPO{}) {
		t.Fatal("migrateAll did not create sys_integration_configs")
	}
}

func TestStorageIntegrationConfigRoundTrip(t *testing.T) {
	db := openIntegrationConfigTestDB(t)
	storage := &config.Storage{
		Type: "aliyun-oss",
		Qiniu: &config.Qiniu{
			Zone: "ZoneHuadong", Bucket: "qiniu-bucket", AccessKey: "qiniu-key", SecretKey: "qiniu-secret",
		},
		AliyunOSS: &config.ObjectStore{
			Endpoint: "oss-cn-hangzhou.aliyuncs.com", Region: "cn-hangzhou", Bucket: "assets",
			AccessKey: "aliyun-key", SecretKey: "aliyun-secret", BaseURL: "https://cdn.example.com", PathPrefix: "uploads",
		},
		Minio: &config.ObjectStore{Endpoint: "127.0.0.1:9000", Bucket: "local", ForcePathStyle: true},
	}
	if err := dataintegration.SaveStorageConfig(db, storage); err != nil {
		t.Fatal(err)
	}
	email := &config.Email{
		To: "ops@example.com", From: "mailer@example.com", Host: "smtp.example.com",
		Secret: "smtp-secret", Nickname: "Kra", Port: 465, IsSSL: true,
	}
	if err := dataintegration.SaveEmailConfig(db, email); err != nil {
		t.Fatal(err)
	}
	if err := db.Create(&dataintegration.ConfigPO{Kind: integrationbiz.IntegrationKindPayment, Provider: "wechat-pay", Config: `{"merchant_id":"123"}`}).Error; err != nil {
		t.Fatal(err)
	}

	loaded, found, err := dataintegration.LoadStorageConfig(db)
	if err != nil {
		t.Fatal(err)
	}
	if !found {
		t.Fatal("storage integration configuration was not found")
	}
	if loaded.Type != "aliyun-oss" {
		t.Fatalf("storage type = %q, want aliyun-oss", loaded.Type)
	}
	if loaded.AliyunOSS == nil || loaded.AliyunOSS.SecretKey != "aliyun-secret" || loaded.AliyunOSS.PathPrefix != "uploads" {
		t.Fatalf("aliyun configuration = %#v", loaded.AliyunOSS)
	}
	if loaded.Qiniu == nil || loaded.Qiniu.SecretKey != "qiniu-secret" {
		t.Fatalf("qiniu configuration = %#v", loaded.Qiniu)
	}

	loadedEmail, found, err := dataintegration.LoadEmailConfig(db)
	if err != nil || !found {
		t.Fatalf("email configuration found=%v, err=%v", found, err)
	}
	if loadedEmail.Host != "smtp.example.com" || loadedEmail.Secret != "smtp-secret" || loadedEmail.Port != 465 {
		t.Fatalf("email configuration = %#v", loadedEmail)
	}

	var storageCount, emailCount, paymentCount int64
	if err = db.Model(&dataintegration.ConfigPO{}).Where("kind = ?", "storage").Count(&storageCount).Error; err != nil {
		t.Fatal(err)
	}
	if err = db.Model(&dataintegration.ConfigPO{}).Where("kind = ?", integrationbiz.IntegrationKindPayment).Count(&paymentCount).Error; err != nil {
		t.Fatal(err)
	}
	if err = db.Model(&dataintegration.ConfigPO{}).Where("kind = ?", "email").Count(&emailCount).Error; err != nil {
		t.Fatal(err)
	}
	if storageCount != 8 {
		t.Fatalf("storage row count = %d, want 8", storageCount)
	}
	if paymentCount != 1 {
		t.Fatalf("payment row count = %d, want 1", paymentCount)
	}
	if emailCount != 1 {
		t.Fatalf("email row count = %d, want 1", emailCount)
	}
}

func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
	db := openIntegrationConfigTestDB(t)
	legacy := &config.Storage{
		Type:  "qiniu",
		Qiniu: &config.Qiniu{Bucket: "legacy", SecretKey: "legacy-secret"},
	}
	loaded, err := dataintegration.ResolveStorageConfig(db, legacy)
	if err != nil {
		t.Fatal(err)
	}
	if loaded.Type != "qiniu" || loaded.Qiniu.Bucket != "legacy" {
		t.Fatalf("migrated storage = %#v", loaded)
	}

	other := &config.Storage{
		Type:  "minio",
		Minio: &config.ObjectStore{Bucket: "must-not-replace-database"},
	}
	loaded, err = dataintegration.ResolveStorageConfig(db, other)
	if err != nil {
		t.Fatal(err)
	}
	if loaded.Type != "qiniu" || loaded.Qiniu.SecretKey != "legacy-secret" {
		t.Fatalf("database configuration was replaced by legacy config: %#v", loaded)
	}
}

func TestMaskStorageSecretsLeavesUnconfiguredProvidersEmpty(t *testing.T) {
	storage := &config.Storage{
		Qiniu:     &config.Qiniu{},
		AliyunOSS: &config.ObjectStore{SecretKey: "configured-secret"},
		Minio:     &config.ObjectStore{},
	}
	config.MaskStorageSecrets(storage)
	if storage.Qiniu.SecretKey != "" || storage.Minio.SecretKey != "" {
		t.Fatalf("empty provider secrets were masked: %#v", storage)
	}
	if storage.AliyunOSS.SecretKey != config.MaskedSecret {
		t.Fatalf("configured secret was not masked: %q", storage.AliyunOSS.SecretKey)
	}
}

func TestResolveEmailIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
	db := openIntegrationConfigTestDB(t)
	legacy := &config.Email{To: "ops@example.com", From: "old@example.com", Host: "smtp.old.example.com", Secret: "old-secret", Port: 465, IsSSL: true}
	loaded, err := dataintegration.ResolveEmailConfig(db, legacy)
	if err != nil {
		t.Fatal(err)
	}
	if loaded.Host != legacy.Host || loaded.Secret != legacy.Secret {
		t.Fatalf("migrated email = %#v", loaded)
	}
	loaded, err = dataintegration.ResolveEmailConfig(db, &config.Email{Host: "must-not-replace.example.com"})
	if err != nil {
		t.Fatal(err)
	}
	if loaded.Host != legacy.Host || loaded.Secret != legacy.Secret {
		t.Fatalf("database email was replaced by legacy config: %#v", loaded)
	}
}

func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) {
	path := filepath.Join(t.TempDir(), "config.yaml")
	input := []byte("data: {}\nadmin:\n  router_prefix: /old\n  storage:\n    type: qiniu\n    qiniu:\n      secret_key: legacy-secret\n  email:\n    host: smtp.legacy.example.com\n    secret: legacy-email-secret\n  extension_key: retained\n")
	if err := os.WriteFile(path, input, 0o600); err != nil {
		t.Fatal(err)
	}
	d := &Data{}
	admin := &config.Admin{
		ConfigPath:   path,
		RouterPrefix: "/api",
		Storage: &config.Storage{
			Type:  "qiniu",
			Qiniu: &config.Qiniu{SecretKey: "database-only-secret"},
		},
		Email: &config.Email{Host: "smtp.database.example.com", Secret: "database-only-email-secret"},
	}
	if err := d.persistConfigValues(&config.Data{}, admin); err != nil {
		t.Fatal(err)
	}

	raw, err := os.ReadFile(path)
	if err != nil {
		t.Fatal(err)
	}
	var document map[string]any
	if err = yaml.Unmarshal(raw, &document); err != nil {
		t.Fatal(err)
	}
	adminValue, ok := document["admin"].(map[string]any)
	if !ok {
		t.Fatalf("admin config = %#v", document["admin"])
	}
	if _, exists := adminValue["storage"]; exists {
		t.Fatalf("storage remained in YAML: %s", raw)
	}
	if _, exists := adminValue["email"]; exists {
		t.Fatalf("email remained in YAML: %s", raw)
	}
	if adminValue["extension_key"] != "retained" {
		t.Fatalf("extension key was not retained: %#v", adminValue)
	}
}

func TestPersistRuntimeConfigReplacesActiveStorage(t *testing.T) {
	db := openIntegrationConfigTestDB(t)
	configPath := filepath.Join(t.TempDir(), "config.yaml")
	if err := os.WriteFile(configPath, []byte("data: {}\nadmin: {}\n"), 0o600); err != nil {
		t.Fatal(err)
	}
	oldRoot := filepath.Join(t.TempDir(), "old")
	newRoot := filepath.Join(t.TempDir(), "new")
	currentAdmin := &config.Admin{
		ConfigPath: configPath,
		Local:      &config.Local{StorePath: oldRoot, PathPrefix: "old-files"},
		Storage:    &config.Storage{Type: "local"},
	}
	currentStorage, err := storage.NewReloadable(currentAdmin)
	if err != nil {
		t.Fatal(err)
	}
	d := &Data{
		runtime: config.NewStore(&config.Config{Data: &config.Data{}, Admin: currentAdmin}),
		gormDB:  newReloadableDB(db),
		storage: currentStorage,
	}
	d.databaseReady.Store(true)

	nextAdmin := cloneAdminConfig(currentAdmin)
	nextAdmin.Local = &config.Local{StorePath: newRoot, PathPrefix: "new-files"}
	nextAdmin.Email = &config.Email{
		To: "ops@example.com", From: "mailer@example.com", Host: "smtp.example.com",
		Secret: "runtime-secret", Port: 465, IsSSL: true,
	}
	if err = d.PersistRuntimeConfig(context.Background(), &config.Config{Data: &config.Data{}, Admin: nextAdmin}); err != nil {
		t.Fatal(err)
	}

	stored, err := d.storage.Put(context.Background(), "active.txt", strings.NewReader("active"))
	if err != nil {
		t.Fatal(err)
	}
	if stored.URL != "/new-files/active.txt" {
		t.Fatalf("active storage URL = %q, want /new-files/active.txt", stored.URL)
	}
	if _, err = os.Stat(filepath.Join(newRoot, "active.txt")); err != nil {
		t.Fatalf("active storage did not write to the new root: %v", err)
	}
	loaded, found, err := dataintegration.LoadStorageConfig(db)
	if err != nil || !found || loaded.Type != "local" {
		t.Fatalf("database storage config = %#v, found=%v, err=%v", loaded, found, err)
	}
	loadedEmail, found, err := dataintegration.LoadEmailConfig(db)
	if err != nil || !found || loadedEmail.Secret != "runtime-secret" {
		t.Fatalf("database email config = %#v, found=%v, err=%v", loadedEmail, found, err)
	}
	if runtimeEmail := d.runtime.Admin().Email; runtimeEmail == nil || runtimeEmail.Host != "smtp.example.com" {
		t.Fatalf("runtime email config = %#v", runtimeEmail)
	}
}
