package config

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"os"
	"time"

	"gopkg.in/yaml.v3"
)

const DefaultDBPath = "./data/wormhole.db"

type Config struct {
	Server   ServerConfig   `yaml:"server" json:"server"`
	Database DatabaseConfig `yaml:"database" json:"database"`
	Auth     AuthConfig     `yaml:"auth" json:"auth"`
	Metrics  MetricsConfig  `yaml:"metrics" json:"metrics"`
}

type ServerConfig struct {
	BridgeAddr     string `yaml:"bridge_addr" json:"bridge_addr"`
	HTTPAddr       string `yaml:"http_addr" json:"http_addr"`
	HTTPProxyPort  int    `yaml:"http_proxy_port" json:"http_proxy_port"`
	HTTPSProxyPort int    `yaml:"https_proxy_port" json:"https_proxy_port"`
	TLSCertFile    string `yaml:"tls_cert_file" json:"tls_cert_file"`
	TLSKeyFile     string `yaml:"tls_key_file" json:"tls_key_file"`
}

type DatabaseConfig struct {
	Path string `yaml:"path" json:"path"`
}

type AuthConfig struct {
	JWTSecret        string        `yaml:"jwt_secret" json:"jwt_secret"`
	TokenTTL         time.Duration `yaml:"token_ttl" json:"token_ttl"`
	LoginMaxAttempts int           `yaml:"login_max_attempts" json:"login_max_attempts"`
	LoginWindow      time.Duration `yaml:"login_window" json:"login_window"`
	LoginLockout     time.Duration `yaml:"login_lockout" json:"login_lockout"`
}

type MetricsConfig struct {
	FlushInterval time.Duration `yaml:"flush_interval" json:"flush_interval"`
}

// SettingsDTO is the API/UI representation of runtime settings.
type SettingsDTO struct {
	Server  ServerConfig `json:"server"`
	Auth    AuthSettings `json:"auth"`
	Metrics struct {
		FlushIntervalSec int `json:"flush_interval_sec"`
	} `json:"metrics"`
}

type AuthSettings struct {
	JWTSecret        string `json:"jwt_secret,omitempty"`
	TokenTTLHours    int    `json:"token_ttl_hours"`
	LoginMaxAttempts int    `json:"login_max_attempts"`
	LoginWindowMin   int    `json:"login_window_min"`
	LoginLockoutMin  int    `json:"login_lockout_min"`
}

func Default() *Config {
	secret, _ := randomSecret(32)
	return &Config{
		Server: ServerConfig{
			BridgeAddr:     ":8528",
			HTTPAddr:       ":8529",
			HTTPProxyPort:  8081,
			HTTPSProxyPort: 8443,
		},
		Database: DatabaseConfig{
			Path: DefaultDBPath,
		},
		Auth: AuthConfig{
			JWTSecret:        secret,
			TokenTTL:         24 * time.Hour,
			LoginMaxAttempts: 5,
			LoginWindow:      5 * time.Minute,
			LoginLockout:     15 * time.Minute,
		},
		Metrics: MetricsConfig{
			FlushInterval: 30 * time.Second,
		},
	}
}

func randomSecret(n int) (string, error) {
	b := make([]byte, n)
	if _, err := rand.Read(b); err != nil {
		return "wormhole-change-me-in-production", err
	}
	return hex.EncodeToString(b), nil
}

func (c *Config) Normalize() {
	if c.Server.BridgeAddr == "" {
		c.Server.BridgeAddr = ":8528"
	}
	if c.Server.HTTPAddr == "" {
		c.Server.HTTPAddr = ":8529"
	}
	if c.Server.HTTPProxyPort == 0 {
		c.Server.HTTPProxyPort = 8081
	}
	if c.Server.HTTPSProxyPort == 0 {
		c.Server.HTTPSProxyPort = 8443
	}
	if c.Database.Path == "" {
		c.Database.Path = DefaultDBPath
	}
	if c.Auth.TokenTTL == 0 {
		c.Auth.TokenTTL = 24 * time.Hour
	}
	if c.Auth.LoginMaxAttempts == 0 {
		c.Auth.LoginMaxAttempts = 5
	}
	if c.Auth.LoginWindow == 0 {
		c.Auth.LoginWindow = 5 * time.Minute
	}
	if c.Auth.LoginLockout == 0 {
		c.Auth.LoginLockout = 15 * time.Minute
	}
	if c.Auth.JWTSecret == "" {
		secret, _ := randomSecret(32)
		c.Auth.JWTSecret = secret
	}
	if c.Metrics.FlushInterval == 0 {
		c.Metrics.FlushInterval = 30 * time.Second
	}
}

func (c *Config) ToDTO() SettingsDTO {
	sec := int(c.Metrics.FlushInterval / time.Second)
	if sec < 1 {
		sec = 30
	}
	dto := SettingsDTO{
		Server: c.Server,
		Auth: AuthSettings{
			TokenTTLHours:    int(c.Auth.TokenTTL / time.Hour),
			LoginMaxAttempts: c.Auth.LoginMaxAttempts,
			LoginWindowMin:   int(c.Auth.LoginWindow / time.Minute),
			LoginLockoutMin:  int(c.Auth.LoginLockout / time.Minute),
		},
	}
	dto.Metrics.FlushIntervalSec = sec
	return dto
}

func SettingsFromDTO(d SettingsDTO, existing *Config) *Config {
	cfg := Default()
	if existing != nil {
		*cfg = *existing
	}
	cfg.Server = d.Server
	cfg.Auth.TokenTTL = time.Duration(d.Auth.TokenTTLHours) * time.Hour
	cfg.Auth.LoginMaxAttempts = d.Auth.LoginMaxAttempts
	cfg.Auth.LoginWindow = time.Duration(d.Auth.LoginWindowMin) * time.Minute
	cfg.Auth.LoginLockout = time.Duration(d.Auth.LoginLockoutMin) * time.Minute
	if d.Auth.JWTSecret != "" {
		cfg.Auth.JWTSecret = d.Auth.JWTSecret
	}
	if d.Metrics.FlushIntervalSec > 0 {
		cfg.Metrics.FlushInterval = time.Duration(d.Metrics.FlushIntervalSec) * time.Second
	}
	cfg.Normalize()
	return cfg
}

func (c *Config) Summary() string {
	return fmt.Sprintf(
		"http_addr=%s bridge_addr=%s http_proxy_port=%d https_proxy_port=%d db=%s",
		c.Server.HTTPAddr,
		c.Server.BridgeAddr,
		c.Server.HTTPProxyPort,
		c.Server.HTTPSProxyPort,
		c.Database.Path,
	)
}

// Load reads config from a YAML file (tests only).
func Load(path string) (*Config, error) {
	if path == "" {
		return nil, fmt.Errorf("config path required")
	}

	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	cfg := Default()
	if err := yaml.Unmarshal(data, cfg); err != nil {
		return nil, err
	}
	cfg.Normalize()
	return cfg, nil
}
