package data

import (
	"context"
	"errors"
	"fmt"

	"kra/internal/biz/system"
	configpkg "kra/internal/config"
	dataintegration "kra/internal/data/integration"
	"kra/internal/integration/storage"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

func (d *Data) Config() *configpkg.Config {
	if d == nil || d.runtime == nil {
		return nil
	}
	return d.runtime.Snapshot()
}

func refreshDatabaseSources(value *configpkg.Data) error {
	if value == nil {
		return nil
	}
	if err := refreshDatabaseSource(value.Database); err != nil {
		return err
	}
	for _, database := range value.DatabaseList {
		if database == nil || database.Disable {
			continue
		}
		if err := refreshDatabaseSource(database); err != nil {
			return err
		}
	}
	return nil
}

func refreshDatabaseSource(database *configpkg.Database) error {
	if database == nil {
		return nil
	}
	if !databaseHasStructuredFields(database) {
		return nil
	}
	source, err := databaseDSN(database, "")
	if err != nil {
		return err
	}
	database.Source = source
	return nil
}

func (d *Data) PersistConfig(context.Context) error { return d.persistConfig() }
func (d *Data) PersistRuntimeConfig(ctx context.Context, value *configpkg.Config) error {
	current := d.Config()
	if current == nil {
		current = &configpkg.Config{}
	}
	next := configpkg.MergeRuntimeConfig(current, value)
	if next.Data == nil {
		next.Data = &configpkg.Data{}
	}
	if err := refreshDatabaseSources(next.Data); err != nil {
		return err
	}
	candidateStorage, err := storage.New(next.Admin)
	if err != nil {
		return err
	}
	if err := d.persistStorageIntegrationConfig(ctx, next.Admin.Storage); err != nil {
		return err
	}
	if err := d.persistEmailIntegrationConfig(ctx, next.Admin.Email); err != nil {
		return err
	}
	if err := d.persistConfigValues(next.Data, next.Admin); err != nil {
		return err
	}
	d.runtime.Replace(next)
	if d.storage != nil {
		d.storage.Replace(candidateStorage)
	}
	return nil
}
func (d *Data) ReloadConfig(ctx context.Context) error {
	return d.reloadConfig(ctx)
}

func (d *Data) IsInitialized(context.Context) (bool, error) {
	return d.databaseReady.Load(), nil
}

// InitializeDatabase opens and activates the configured database. The callback
// is the application-level first-install hook; data owns only lifecycle and
// schema migration, while initialize owns system seed orchestration.
func (d *Data) InitializeDatabase(ctx context.Context, input *system.DatabaseConfig, seed func(context.Context, *gorm.DB) error) error {
	database := &configpkg.Database{}
	if current := d.runtime.Data(); current != nil && current.Database != nil {
		database = configpkg.CloneDatabase(current.Database)
	}
	database.Driver = input.Driver
	database.Host = input.Host
	database.Port = input.Port
	database.User = input.User
	database.Password = input.Password
	database.Name = input.Name
	database.Path = input.Path
	database.Config = input.Config
	database.Source = ""
	source, err := databaseDSN(database, "")
	if err != nil {
		return err
	}
	database.Source = source
	d.initMu.Lock()
	defer d.initMu.Unlock()
	initialized, err := d.IsInitialized(ctx)
	if err != nil {
		return err
	}
	if initialized {
		return errors.New("数据库已初始化，无需重复初始化")
	}
	candidate, err := openDatabase(database, true, input.Template, d.logger())
	if err != nil {
		return err
	}
	activated := false
	defer func() {
		if !activated {
			if sqlDB, closeErr := candidate.DB(); closeErr == nil {
				_ = sqlDB.Close()
			}
		}
	}()
	db := candidate.WithContext(ctx)
	if err := migrateAll(db, d.catalog); err != nil {
		return err
	}
	if seed != nil {
		if err := seed(ctx, db); err != nil {
			return err
		}
	}
	currentAdmin := d.runtime.Admin()
	var legacyStorage *configpkg.Storage
	if currentAdmin != nil {
		legacyStorage = currentAdmin.Storage
	}
	storageConfig, err := dataintegration.ResolveStorageConfig(candidate.WithContext(ctx), legacyStorage)
	if err != nil {
		return fmt.Errorf("initialize storage integration configuration: %w", err)
	}
	var legacyEmail *configpkg.Email
	if currentAdmin != nil {
		legacyEmail = currentAdmin.Email
	}
	emailConfig, err := dataintegration.ResolveEmailConfig(candidate.WithContext(ctx), legacyEmail)
	if err != nil {
		return fmt.Errorf("initialize email integration configuration: %w", err)
	}
	signingKey := uuid.NewString()
	if err := d.persistDatabaseConfig(database, signingKey); err != nil {
		return fmt.Errorf("persist database configuration: %w", err)
	}
	integrationConfigs, err := dataintegration.ReadRuntime(candidate)
	if err != nil {
		return fmt.Errorf("initialize integration runtime: %w", err)
	}
	d.activateDatabase(candidate, database)
	activated = true
	current := d.Config()
	if current == nil {
		current = &configpkg.Config{}
	}
	currentData, currentAdmin := current.Data, current.Admin
	if currentAdmin == nil {
		currentAdmin = &configpkg.Admin{}
	}
	if currentAdmin.JWT == nil {
		currentAdmin.JWT = &configpkg.JWT{}
	}
	currentAdmin.JWT.SigningKey = signingKey
	currentAdmin.Storage = storageConfig
	currentAdmin.Email = emailConfig
	d.runtime.Replace(&configpkg.Config{Server: current.Server, Data: currentData, Admin: currentAdmin})
	if d.integrations != nil {
		d.integrations.Replace(integrationConfigs)
	}
	return nil
}
