package data

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

	"kra/internal/conf"
	"kra/internal/integration/storage"

	"google.golang.org/protobuf/encoding/protojson"
	"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")
	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(&integrationConfigPO{}); err != nil {
		t.Fatal(err)
	}
	return db
}

func TestMigrateAllCreatesIntegrationConfigTable(t *testing.T) {
	db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared")
	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); err != nil {
		t.Fatal(err)
	}
	if !db.Migrator().HasTable(&integrationConfigPO{}) {
		t.Fatal("migrateAll did not create sys_integration_configs")
	}
}

func TestStorageIntegrationConfigRoundTrip(t *testing.T) {
	db := openIntegrationConfigTestDB(t)
	storage := &conf.AdminBackend_Storage{
		Type: "aliyun-oss",
		Qiniu: &conf.AdminBackend_Qiniu{
			Zone: "ZoneHuadong", Bucket: "qiniu-bucket", AccessKey: "qiniu-key", SecretKey: "qiniu-secret",
		},
		AliyunOss: &conf.AdminBackend_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: &conf.AdminBackend_ObjectStore{Endpoint: "127.0.0.1:9000", Bucket: "local", ForcePathStyle: true},
	}
	if err := saveStorageIntegrationConfig(db, storage); err != nil {
		t.Fatal(err)
	}
	email := &conf.AdminBackend_Email{
		To: "ops@example.com", From: "mailer@example.com", Host: "smtp.example.com",
		Secret: "smtp-secret", Nickname: "Kra", Port: 465, IsSsl: true,
	}
	if err := saveEmailIntegrationConfig(db, email); err != nil {
		t.Fatal(err)
	}
	if err := db.Create(&integrationConfigPO{Kind: integrationKindPayment, Provider: "wechat-pay", Config: `{"merchant_id":"123"}`}).Error; err != nil {
		t.Fatal(err)
	}

	loaded, found, err := loadStorageIntegrationConfig(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 := loadEmailIntegrationConfig(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(&integrationConfigPO{}).Where("kind = ?", integrationKindStorage).Count(&storageCount).Error; err != nil {
		t.Fatal(err)
	}
	if err = db.Model(&integrationConfigPO{}).Where("kind = ?", integrationKindPayment).Count(&paymentCount).Error; err != nil {
		t.Fatal(err)
	}
	if err = db.Model(&integrationConfigPO{}).Where("kind = ?", integrationKindEmail).Count(&emailCount).Error; err != nil {
		t.Fatal(err)
	}
	if storageCount != int64(len(storageProviderNames)) {
		t.Fatalf("storage row count = %d, want %d", storageCount, len(storageProviderNames))
	}
	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 := &conf.AdminBackend_Storage{
		Type:  "qiniu",
		Qiniu: &conf.AdminBackend_Qiniu{Bucket: "legacy", SecretKey: "legacy-secret"},
	}
	loaded, err := resolveStorageIntegrationConfig(db, legacy)
	if err != nil {
		t.Fatal(err)
	}
	if loaded.Type != "qiniu" || loaded.Qiniu.GetBucket() != "legacy" {
		t.Fatalf("migrated storage = %#v", loaded)
	}

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

func TestMaskStorageSecretsLeavesUnconfiguredProvidersEmpty(t *testing.T) {
	storage := &conf.AdminBackend_Storage{
		Qiniu:     &conf.AdminBackend_Qiniu{},
		AliyunOss: &conf.AdminBackend_ObjectStore{SecretKey: "configured-secret"},
		Minio:     &conf.AdminBackend_ObjectStore{},
	}
	maskStorageSecrets(storage)
	if storage.Qiniu.SecretKey != "" || storage.Minio.SecretKey != "" {
		t.Fatalf("empty provider secrets were masked: %#v", storage)
	}
	if storage.AliyunOss.SecretKey != "******" {
		t.Fatalf("configured secret was not masked: %q", storage.AliyunOss.SecretKey)
	}
}

func TestResolveEmailIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) {
	db := openIntegrationConfigTestDB(t)
	legacy := &conf.AdminBackend_Email{To: "ops@example.com", From: "old@example.com", Host: "smtp.old.example.com", Secret: "old-secret", Port: 465, IsSsl: true}
	loaded, err := resolveEmailIntegrationConfig(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 = resolveEmailIntegrationConfig(db, &conf.AdminBackend_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 := &conf.AdminBackend{
		ConfigPath:   path,
		RouterPrefix: "/api",
		Storage: &conf.AdminBackend_Storage{
			Type:  "qiniu",
			Qiniu: &conf.AdminBackend_Qiniu{SecretKey: "database-only-secret"},
		},
		Email: &conf.AdminBackend_Email{Host: "smtp.database.example.com", Secret: "database-only-email-secret"},
	}
	if err := d.persistConfigValues(&conf.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 := &conf.AdminBackend{
		ConfigPath: configPath,
		Local:      &conf.AdminBackend_Local{StorePath: oldRoot, PathPrefix: "old-files"},
		Storage:    &conf.AdminBackend_Storage{Type: "local"},
	}
	currentStorage, err := storage.NewReloadable(currentAdmin)
	if err != nil {
		t.Fatal(err)
	}
	reloadableDB := &reloadableDB{}
	reloadableDB.current.Store(db)
	d := &Data{
		runtime: conf.NewRuntime(&conf.Data{}, currentAdmin),
		gormDB:  reloadableDB,
		storage: currentStorage,
	}
	d.databaseReady.Store(true)

	nextAdmin := cloneAdminConfig(currentAdmin)
	nextAdmin.Local = &conf.AdminBackend_Local{StorePath: newRoot, PathPrefix: "new-files"}
	nextAdmin.Email = &conf.AdminBackend_Email{
		To: "ops@example.com", From: "mailer@example.com", Host: "smtp.example.com",
		Secret: "runtime-secret", Port: 465, IsSsl: true,
	}
	dataRaw, err := protojson.Marshal(&conf.Data{})
	if err != nil {
		t.Fatal(err)
	}
	adminRaw, err := protojson.Marshal(nextAdmin)
	if err != nil {
		t.Fatal(err)
	}
	if err = d.PersistRuntimeConfig(context.Background(), dataRaw, adminRaw); 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 := loadStorageIntegrationConfig(db)
	if err != nil || !found || loaded.Type != "local" {
		t.Fatalf("database storage config = %#v, found=%v, err=%v", loaded, found, err)
	}
	loadedEmail, found, err := loadEmailIntegrationConfig(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)
	}
}
