// Package config provides configuration loading using Viper.
//
// This package standardizes configuration loading across all services with:
//   - Environment variable support
//   - .env file support for development
//   - Sensible defaults
//   - Type-safe configuration structs
//
// Usage:
//
//	// Initialize configuration (call once at startup)
//	config.MustInit(config.Options{
//	    AppName:     "my-service",
//	    DefaultPort: 8080,
//	})
//
//	// Read configuration
//	appCfg := config.ReadAppConfig()
//	serverCfg := config.ReadServerConfig()
//
//	// Or read specific values
//	dbURL := viper.GetString("DATABASE_URL")
package config

import (
	"fmt"
	"time"

	"github.com/spf13/viper"
)

// ServerConfig holds HTTP server configuration.
type ServerConfig struct {
	Host         string        `json:"host"`
	Port         int           `json:"port"`
	ReadTimeout  time.Duration `json:"read_timeout"`
	WriteTimeout time.Duration `json:"write_timeout"`
	IdleTimeout  time.Duration `json:"idle_timeout"`
}

// Addr returns the server address in host:port format.
func (c ServerConfig) Addr() string {
	return fmt.Sprintf("%s:%d", c.Host, c.Port)
}

// DatabaseConfig holds database connection configuration.
type DatabaseConfig struct {
	URL             string        `json:"url"`
	MaxOpenConns    int           `json:"max_open_conns"`
	MaxIdleConns    int           `json:"max_idle_conns"`
	ConnMaxLifetime time.Duration `json:"conn_max_lifetime"`
}

// AppConfig holds application-level configuration common to all services.
type AppConfig struct {
	Name        string `json:"name"`
	Environment string `json:"environment"`
	Debug       bool   `json:"debug"`
}

// IsDevelopment returns true if the environment is development.
func (c AppConfig) IsDevelopment() bool {
	return c.Environment == "development"
}

// IsProduction returns true if the environment is production.
func (c AppConfig) IsProduction() bool {
	return c.Environment == "production"
}

// LoggingConfig holds logging configuration.
type LoggingConfig struct {
	Level  string `json:"level"`
	Format string `json:"format"`
}

// Options configures the behavior of Init.
type Options struct {
	// AppName is the default name for the application.
	AppName string

	// DefaultPort is the default server port if not specified.
	DefaultPort int

	// EnvFile is the path to the .env file for development.
	// Defaults to ".env" if not specified.
	EnvFile string

	// SetDefaults is an optional function to set additional viper defaults
	// before loading configuration.
	SetDefaults func()

	// SkipEnvFile skips loading from .env file.
	// Useful for production where all config comes from environment.
	SkipEnvFile bool
}

// Init initializes viper with common defaults and loads configuration.
// This should be called once at service startup before using viper.Get* functions.
//
// Load order (later sources override earlier):
//  1. Default values
//  2. .env file (development only)
//  3. Environment variables
func Init(opts Options) error {
	viper.SetConfigType("env")
	viper.SetEnvPrefix("")
	viper.AutomaticEnv()

	// Set common defaults
	setCommonDefaults(opts)

	// Set service-specific defaults
	if opts.SetDefaults != nil {
		opts.SetDefaults()
	}

	// In development, optionally load from .env file
	if !opts.SkipEnvFile {
		env := viper.GetString("APP_ENVIRONMENT")
		if env == "development" || env == "" {
			envFile := opts.EnvFile
			if envFile == "" {
				envFile = ".env"
			}
			viper.SetConfigFile(envFile)
			_ = viper.ReadInConfig() // Ignore error - fallback to env vars
		}
	}

	return nil
}

// MustInit is like Init but panics if initialization fails.
// This is useful in main() where you want to fail fast on configuration errors.
func MustInit(opts Options) {
	if err := Init(opts); err != nil {
		panic(fmt.Sprintf("failed to initialize config: %v", err))
	}
}

// setCommonDefaults sets default values for common configuration fields.
func setCommonDefaults(opts Options) {
	// App defaults
	appName := opts.AppName
	if appName == "" {
		appName = "service"
	}
	viper.SetDefault("APP_NAME", appName)
	viper.SetDefault("APP_ENVIRONMENT", "development")
	viper.SetDefault("APP_DEBUG", false)

	// Server defaults
	viper.SetDefault("SERVER_HOST", "0.0.0.0")
	port := opts.DefaultPort
	if port == 0 {
		port = 8080
	}
	viper.SetDefault("SERVER_PORT", port)
	viper.SetDefault("SERVER_READ_TIMEOUT", "30s")
	viper.SetDefault("SERVER_WRITE_TIMEOUT", "0s") // Disabled for SSE support
	viper.SetDefault("SERVER_IDLE_TIMEOUT", "120s")

	// Database defaults
	viper.SetDefault("DATABASE_MAX_OPEN_CONNS", 25)
	viper.SetDefault("DATABASE_MAX_IDLE_CONNS", 5)
	viper.SetDefault("DATABASE_CONN_MAX_LIFETIME", "5m")

	// Logging defaults
	viper.SetDefault("LOG_LEVEL", "info")
	viper.SetDefault("LOG_FORMAT", "auto") // auto = JSON in prod, text in dev
}

// ReadAppConfig reads AppConfig from viper.
func ReadAppConfig() AppConfig {
	return AppConfig{
		Name:        viper.GetString("APP_NAME"),
		Environment: viper.GetString("APP_ENVIRONMENT"),
		Debug:       viper.GetBool("APP_DEBUG"),
	}
}

// ReadServerConfig reads ServerConfig from viper.
func ReadServerConfig() ServerConfig {
	return ServerConfig{
		Host:         viper.GetString("SERVER_HOST"),
		Port:         viper.GetInt("SERVER_PORT"),
		ReadTimeout:  viper.GetDuration("SERVER_READ_TIMEOUT"),
		WriteTimeout: viper.GetDuration("SERVER_WRITE_TIMEOUT"),
		IdleTimeout:  viper.GetDuration("SERVER_IDLE_TIMEOUT"),
	}
}

// ReadDatabaseConfig reads DatabaseConfig from viper.
func ReadDatabaseConfig() DatabaseConfig {
	return DatabaseConfig{
		URL:             viper.GetString("DATABASE_URL"),
		MaxOpenConns:    viper.GetInt("DATABASE_MAX_OPEN_CONNS"),
		MaxIdleConns:    viper.GetInt("DATABASE_MAX_IDLE_CONNS"),
		ConnMaxLifetime: viper.GetDuration("DATABASE_CONN_MAX_LIFETIME"),
	}
}

// ReadLoggingConfig reads LoggingConfig from viper.
func ReadLoggingConfig() LoggingConfig {
	return LoggingConfig{
		Level:  viper.GetString("LOG_LEVEL"),
		Format: viper.GetString("LOG_FORMAT"),
	}
}

// GetString returns a string configuration value.
func GetString(key string) string {
	return viper.GetString(key)
}

// GetInt returns an integer configuration value.
func GetInt(key string) int {
	return viper.GetInt(key)
}

// GetBool returns a boolean configuration value.
func GetBool(key string) bool {
	return viper.GetBool(key)
}

// GetDuration returns a duration configuration value.
func GetDuration(key string) time.Duration {
	return viper.GetDuration(key)
}

// IsSet returns true if the key is set in configuration.
func IsSet(key string) bool {
	return viper.IsSet(key)
}
