package settings

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"sync"
	"time"

	"github.com/rose_cat707/luminary/internal/middleware"
	"github.com/rose_cat707/luminary/internal/model"
	"gorm.io/gorm"
)

// Runtime holds mutable system settings loaded from the database.
type Runtime struct {
	mu sync.RWMutex
	db *gorm.DB
	data model.SystemSettings
}

func NewRuntime(db *gorm.DB) *Runtime {
	return &Runtime{db: db}
}

func (r *Runtime) Load() error {
	var s model.SystemSettings
	err := r.db.First(&s, 1).Error
	if err == gorm.ErrRecordNotFound {
		s = defaultSettings()
		s = mergeLegacySettings(s)
		if err := r.db.Create(&s).Error; err != nil {
			return err
		}
	} else if err != nil {
		return err
	}
	r.mu.Lock()
	r.data = s
	r.mu.Unlock()
	r.applySideEffects()
	return nil
}

// AfterLoad runs post-bootstrap checks that depend on other tables.
func (r *Runtime) AfterLoad() {
	r.EnsureEncryptionKeyWorks()
}

func defaultSettings() model.SystemSettings {
	key := make([]byte, 32)
	_, _ = rand.Read(key)
	return model.SystemSettings{
		ID:                    1,
		EncryptionKey:         hex.EncodeToString(key),
		SessionTTLSeconds:     int64((24 * time.Hour).Seconds()),
		LoginMaxAttempts:      5,
		LoginLockoutSeconds:   int64((15 * time.Minute).Seconds()),
		TrustedProxiesJSON:    "[]",
		UsageFlushSeconds:     5,
		HealthCheckSeconds:    30,
		GatewayMaxRetries:     3,
		GatewayRetryBackoffMs: 200,
		ImageStoragePath:      "images",
		SignedURLTTLSeconds:   3600,
		PredictionWaitSeconds: 60,
	}
}

func (r *Runtime) Snapshot() model.SystemSettings {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.data
}

func (r *Runtime) EncryptionKey() string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.data.EncryptionKey
}

func (r *Runtime) SessionTTL() time.Duration {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return time.Duration(r.data.SessionTTLSeconds) * time.Second
}

func (r *Runtime) LoginMaxAttempts() int {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.data.LoginMaxAttempts
}

func (r *Runtime) LoginLockout() time.Duration {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return time.Duration(r.data.LoginLockoutSeconds) * time.Second
}

func (r *Runtime) TrustedProxies() []string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	var out []string
	_ = json.Unmarshal([]byte(r.data.TrustedProxiesJSON), &out)
	return out
}

func (r *Runtime) UsageFlushInterval() time.Duration {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return time.Duration(r.data.UsageFlushSeconds) * time.Second
}

func (r *Runtime) HealthCheckInterval() time.Duration {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return time.Duration(r.data.HealthCheckSeconds) * time.Second
}

func (r *Runtime) GatewayMaxRetries() int {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.data.GatewayMaxRetries
}

func (r *Runtime) GatewayRetryBackoffMs() int {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.data.GatewayRetryBackoffMs
}

func (r *Runtime) ImageStoragePath() string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if r.data.ImageStoragePath == "" {
		return "images"
	}
	return r.data.ImageStoragePath
}

func (r *Runtime) PublicBaseURL() string {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return r.data.PublicBaseURL
}

func (r *Runtime) SignedURLTTL() time.Duration {
	r.mu.RLock()
	defer r.mu.RUnlock()
	sec := r.data.SignedURLTTLSeconds
	if sec <= 0 {
		sec = 3600
	}
	return time.Duration(sec) * time.Second
}

func (r *Runtime) PredictionWaitSeconds() int64 {
	r.mu.RLock()
	defer r.mu.RUnlock()
	if r.data.PredictionWaitSeconds <= 0 {
		return 60
	}
	return r.data.PredictionWaitSeconds
}

type UpdateInput struct {
	EncryptionKey         *string  `json:"encryption_key"`
	SessionTTLSeconds     *int64   `json:"session_ttl_seconds"`
	LoginMaxAttempts      *int     `json:"login_max_attempts"`
	LoginLockoutSeconds   *int64   `json:"login_lockout_seconds"`
	TrustedProxies        []string `json:"trusted_proxies"`
	UsageFlushSeconds     *int64   `json:"usage_flush_seconds"`
	HealthCheckSeconds    *int64   `json:"health_check_seconds"`
	GatewayMaxRetries     *int     `json:"gateway_max_retries"`
	GatewayRetryBackoffMs *int     `json:"gateway_retry_backoff_ms"`
	ImageStoragePath      *string  `json:"image_storage_path"`
	PublicBaseURL         *string  `json:"public_base_url"`
	SignedURLTTLSeconds   *int64   `json:"signed_url_ttl_seconds"`
	PredictionWaitSeconds *int64   `json:"prediction_wait_seconds"`
}

func (r *Runtime) Update(in UpdateInput) error {
	r.mu.Lock()
	s := r.data
	if in.EncryptionKey != nil && *in.EncryptionKey != "" {
		s.EncryptionKey = *in.EncryptionKey
	}
	if in.SessionTTLSeconds != nil && *in.SessionTTLSeconds > 0 {
		s.SessionTTLSeconds = *in.SessionTTLSeconds
	}
	if in.LoginMaxAttempts != nil && *in.LoginMaxAttempts > 0 {
		s.LoginMaxAttempts = *in.LoginMaxAttempts
	}
	if in.LoginLockoutSeconds != nil && *in.LoginLockoutSeconds > 0 {
		s.LoginLockoutSeconds = *in.LoginLockoutSeconds
	}
	if in.TrustedProxies != nil {
		b, _ := json.Marshal(in.TrustedProxies)
		s.TrustedProxiesJSON = string(b)
	}
	if in.UsageFlushSeconds != nil && *in.UsageFlushSeconds > 0 {
		s.UsageFlushSeconds = *in.UsageFlushSeconds
	}
	if in.HealthCheckSeconds != nil && *in.HealthCheckSeconds > 0 {
		s.HealthCheckSeconds = *in.HealthCheckSeconds
	}
	if in.GatewayMaxRetries != nil && *in.GatewayMaxRetries > 0 {
		s.GatewayMaxRetries = *in.GatewayMaxRetries
	}
	if in.GatewayRetryBackoffMs != nil && *in.GatewayRetryBackoffMs >= 0 {
		s.GatewayRetryBackoffMs = *in.GatewayRetryBackoffMs
	}
	if in.ImageStoragePath != nil {
		s.ImageStoragePath = *in.ImageStoragePath
	}
	if in.PublicBaseURL != nil {
		s.PublicBaseURL = *in.PublicBaseURL
	}
	if in.SignedURLTTLSeconds != nil && *in.SignedURLTTLSeconds > 0 {
		s.SignedURLTTLSeconds = *in.SignedURLTTLSeconds
	}
	if in.PredictionWaitSeconds != nil && *in.PredictionWaitSeconds > 0 {
		s.PredictionWaitSeconds = *in.PredictionWaitSeconds
	}
	if err := r.db.Save(&s).Error; err != nil {
		return err
	}
	r.data = s
	r.mu.Unlock()
	r.applySideEffects()
	return nil
}

func (r *Runtime) applySideEffects() {
	middleware.ConfigureTrustedProxies(r.TrustedProxies())
}

func (r *Runtime) PublicView() map[string]any {
	s := r.Snapshot()
	masked := "(已设置)"
	if s.EncryptionKey == "" {
		masked = "(未设置)"
	}
	var proxies []string
	_ = json.Unmarshal([]byte(s.TrustedProxiesJSON), &proxies)
	return map[string]any{
		"encryption_key_set":     s.EncryptionKey != "",
		"encryption_key_hint":    masked,
		"session_ttl_seconds":      s.SessionTTLSeconds,
		"login_max_attempts":       s.LoginMaxAttempts,
		"login_lockout_seconds":    s.LoginLockoutSeconds,
		"trusted_proxies":          proxies,
		"usage_flush_seconds":      s.UsageFlushSeconds,
		"health_check_seconds":     s.HealthCheckSeconds,
		"gateway_max_retries":      s.GatewayMaxRetries,
		"gateway_retry_backoff_ms": s.GatewayRetryBackoffMs,
		"image_storage_path":       s.ImageStoragePath,
		"public_base_url":          s.PublicBaseURL,
		"signed_url_ttl_seconds":   s.SignedURLTTLSeconds,
		"prediction_wait_seconds":  s.PredictionWaitSeconds,
	}
}
