// internal/config/config.go
package config

import (
	"fmt"
	"os"
	"path/filepath"
	"sync"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

type Config struct {
	Server   ServerConfig
	Database DatabaseConfig
	Redis    RedisConfig
	SMTP     SMTPConfig
	JWT      JWTConfig
}

type ServerConfig struct {
	Port string
	Env  string
}

type DatabaseConfig struct {
	Host     string
	Port     string
	User     string
	Password string
	Name     string
	SSLMode  string
}

type RedisConfig struct {
	Addr     string
	Password string
	DB       int
}

type SMTPConfig struct {
	Host     string
	Port     int
	Username string
	Password string
	From     string
}

type JWTConfig struct {
	Secret    string
	PublicKey string
	Expiry    string
}

// Package-level state for a single, watched config instance
var (
	vip      *viper.Viper
	cfgCache *Config
	cfgMu    sync.RWMutex
	initOnce sync.Once
	initErr  error

	listeners   []func(*Config)
	listenersMu sync.RWMutex
)

// LoadConfig returns the current configuration. On first call it initializes
// Viper, loads the config from a deterministic location, and starts watching
// for changes. Subsequent calls are cheap and thread-safe.
func LoadConfig() (*Config, error) {
	initOnce.Do(func() {
		vip = viper.New()
		configureViper(vip)
		// Initial read must succeed; later changes are handled via WatchConfig
		if err := vip.ReadInConfig(); err != nil {
			initErr = fmt.Errorf("failed to load config: %w", err)
			return
		}
		// Build cache from initial values
		cfg := buildFromViper(vip)
		cfgMu.Lock()
		cfgCache = cfg
		cfgMu.Unlock()

		// Watch for changes and update cache atomically
		vip.WatchConfig()
		vip.OnConfigChange(func(_ fsnotify.Event) {
			// viper already re-reads on change; rebuild view and swap
			newCfg := buildFromViper(vip)
			cfgMu.Lock()
			cfgCache = newCfg
			cfgMu.Unlock()
			notifyListeners(newCfg)
		})
	})

	if initErr != nil {
		return nil, initErr
	}

	cfgMu.RLock()
	defer cfgMu.RUnlock()
	return cloneConfig(cfgCache), nil
}

// DSN returns the Data Source Name for PostgreSQL connection
func (db DatabaseConfig) DSN() string {
	return fmt.Sprintf(
		"postgres://%s:%s@%s:%s/%s?sslmode=%s",
		db.User, db.Password, db.Host, db.Port, db.Name, db.SSLMode,
	)
}

// RegisterChangeListener registers a callback invoked whenever the configuration
// file changes and the in-memory configuration is updated.
func RegisterChangeListener(listener func(*Config)) {
	listenersMu.Lock()
	defer listenersMu.Unlock()
	listeners = append(listeners, listener)
}

// --- internal helpers ---

func configureViper(v *viper.Viper) {
	// Allow env vars to override any key (expects exact key names used below)
	v.AutomaticEnv()

	if path, ok := resolveConfigFilePath(); ok {
		v.SetConfigFile(path)
		return
	}
	// Fallback to looking for a .env in stable, nearby locations.
	v.SetConfigName(".env")
	v.SetConfigType("env")
	v.AddConfigPath(".")
	v.AddConfigPath("..")
}

func resolveConfigFilePath() (string, bool) {
	if p := os.Getenv("CONFIG_FILE"); p != "" {
		return p, true
	}
	cwd, _ := os.Getwd()
	candidates := []string{
		filepath.Join(cwd, ".env"),
		filepath.Join(cwd, "go-server", ".env"),
		filepath.Join(cwd, "..", ".env"),
		filepath.Join(cwd, "..", "go-server", ".env"),
	}
	for _, c := range candidates {
		if info, err := os.Stat(c); err == nil && !info.IsDir() {
			return c, true
		}
	}
	return "", false
}

func buildFromViper(v *viper.Viper) *Config {
	return &Config{
		Server: ServerConfig{
			Port: v.GetString("SERVER_PORT"),
			Env:  v.GetString("SERVER_ENV"),
		},
		Database: DatabaseConfig{
			Host:     v.GetString("DB_HOST"),
			Port:     v.GetString("DB_PORT"),
			User:     v.GetString("DB_USER"),
			Password: v.GetString("DB_PASSWORD"),
			Name:     v.GetString("DB_NAME"),
			SSLMode:  v.GetString("DB_SSL_MODE"),
		},
		Redis: RedisConfig{
			Addr:     v.GetString("REDIS_ADDR"),
			Password: v.GetString("REDIS_PASSWORD"),
			DB:       v.GetInt("REDIS_DB"),
		},
		SMTP: SMTPConfig{
			Host:     v.GetString("SMTP_HOST"),
			Port:     v.GetInt("SMTP_PORT"),
			Username: v.GetString("SMTP_USERNAME"),
			Password: v.GetString("SMTP_PASSWORD"),
			From:     v.GetString("SMTP_FROM"),
		},
		JWT: JWTConfig{
			Secret:    v.GetString("JWT_SECRET"),
			Expiry:    v.GetString("JWT_EXPIRY"),
			PublicKey: v.GetString("JWT_PUBLIC_KEY"),
		},
	}
}

func cloneConfig(c *Config) *Config {
	if c == nil {
		return nil
	}
	cc := *c
	return &cc
}

func notifyListeners(c *Config) {
	listenersMu.RLock()
	defer listenersMu.RUnlock()
	for _, l := range listeners {
		// Call listeners in-goroutine to avoid blocking the watcher
		go l(cloneConfig(c))
	}
}
