package config

import (
	"fmt"
	"os"
	"strings"

	"github.com/spf13/viper"
)

// Config represents the application configuration
type Config struct {
	Server  ServerConfig  `mapstructure:"server"`
	Garage  GarageConfig  `mapstructure:"garage"`
	Auth    AuthConfig    `mapstructure:"auth"`
	CORS    CORSConfig    `mapstructure:"cors"`
	Logging LoggingConfig `mapstructure:"logging"`
}

// ServerConfig contains server-related configuration
type ServerConfig struct {
	Host            string `mapstructure:"host"`
	Port            int    `mapstructure:"port"`
	Environment     string `mapstructure:"environment"`
	FrontendPath    string `mapstructure:"frontend_path"`     // Path to frontend dist directory
	Domain          string `mapstructure:"domain"`            // Domain name (e.g., garage-ui.example.com)
	Protocol        string `mapstructure:"protocol"`          // Protocol for internal communication (http/https)
	RootURL         string `mapstructure:"root_url"`          // Full external URL for redirects (e.g., https://garage-ui.example.com)
	MaxBodySize     int64  `mapstructure:"max_body_size"`     // Maximum request body size in bytes (default: 300MB)
	MaxHeaderSize   int    `mapstructure:"max_header_size"`   // Maximum request header size in bytes (default: 1MB)
	ReadBufferSize  int    `mapstructure:"read_buffer_size"`  // Read buffer size in bytes (default: 4KB)
	WriteBufferSize int    `mapstructure:"write_buffer_size"` // Write buffer size in bytes (default: 4KB)
}

// GarageConfig contains Garage S3 connection settings
type GarageConfig struct {
	Endpoint       string `mapstructure:"endpoint"`
	Region         string `mapstructure:"region"`
	UseSSL         bool   `mapstructure:"use_ssl"`
	ForcePathStyle bool   `mapstructure:"force_path_style"`
	AdminEndpoint  string `mapstructure:"admin_endpoint"`
	AdminToken     string `mapstructure:"admin_token"`
}

// AuthConfig contains authentication configuration
type AuthConfig struct {
	Admin      AdminAuthConfig `mapstructure:"admin"`
	OIDC       OIDCConfig      `mapstructure:"oidc"`
	JWTPrivKey string          `mapstructure:"jwt_private_key"` // Ed25519 private key in PEM format for JWT signing (64 bytes)
}

// AdminAuthConfig contains admin authentication settings
type AdminAuthConfig struct {
	Enabled  bool   `mapstructure:"enabled"`
	Username string `mapstructure:"username"`
	Password string `mapstructure:"password"`
}

// OIDCConfig contains OIDC authentication settings
type OIDCConfig struct {
	Enabled           bool     `mapstructure:"enabled"`
	ProviderName      string   `mapstructure:"provider_name"`
	ClientID          string   `mapstructure:"client_id"`
	ClientSecret      string   `mapstructure:"client_secret"`
	Scopes            []string `mapstructure:"scopes"`
	IssuerURL         string   `mapstructure:"issuer_url"`
	SkipIssuerCheck   bool     `mapstructure:"skip_issuer_check"`
	SkipExpiryCheck   bool     `mapstructure:"skip_expiry_check"`
	EmailAttribute    string   `mapstructure:"email_attribute"`
	UsernameAttribute string   `mapstructure:"username_attribute"`
	NameAttribute     string   `mapstructure:"name_attribute"`
	RoleAttributePath string   `mapstructure:"role_attribute_path"`
	AdminRole         string   `mapstructure:"admin_role"`
	TLSSkipVerify     bool     `mapstructure:"tls_skip_verify"`
	SessionMaxAge     int      `mapstructure:"session_max_age"`
	CookieName        string   `mapstructure:"cookie_name"`
	CookieSecure      bool     `mapstructure:"cookie_secure"`
	CookieHTTPOnly    bool     `mapstructure:"cookie_http_only"`
	CookieSameSite    string   `mapstructure:"cookie_same_site"`
}

// CORSConfig contains CORS settings for frontend communication
type CORSConfig struct {
	Enabled          bool     `mapstructure:"enabled"`
	AllowedOrigins   []string `mapstructure:"allowed_origins"`
	AllowedMethods   []string `mapstructure:"allowed_methods"`
	AllowedHeaders   []string `mapstructure:"allowed_headers"`
	AllowCredentials bool     `mapstructure:"allow_credentials"`
	MaxAge           int      `mapstructure:"max_age"`
}

// LoggingConfig contains logging configuration
type LoggingConfig struct {
	Level  string `mapstructure:"level"`
	Format string `mapstructure:"format"`
}

// Load reads the configuration from the specified file
func Load(configPath string) (*Config, error) {
	// Set default config file name if not specified
	if configPath == "" {
		configPath = "config.yaml"
	}

	// Configure viper to read the config file
	viper.SetConfigFile(configPath)
	viper.SetConfigType("yaml")

	// Allow environment variables to override config values
	// Environment variables take precedence over config file
	viper.AutomaticEnv()
	viper.SetEnvPrefix("GARAGE_UI")
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

	// Env vars override config file values
	bindEnvVars()

	// Read the config file (optional - will use defaults and env vars if not found)
	if _, err := os.Stat(configPath); err == nil {
		if err := viper.ReadInConfig(); err != nil {
			return nil, fmt.Errorf("error reading config file: %w", err)
		}
	}

	// Unmarshal the config into the Config struct
	var cfg Config
	if err := viper.Unmarshal(&cfg); err != nil {
		return nil, fmt.Errorf("error unmarshaling config: %w", err)
	}

	// Validate the configuration
	if err := cfg.Validate(); err != nil {
		return nil, fmt.Errorf("invalid configuration: %w", err)
	}

	return &cfg, nil
}

// bindEnvVars binds all environment variables to their corresponding config keys
func bindEnvVars() {
	// Server config
	viper.BindEnv("server.host", "GARAGE_UI_SERVER_HOST")
	viper.BindEnv("server.port", "GARAGE_UI_SERVER_PORT")
	viper.BindEnv("server.environment", "GARAGE_UI_SERVER_ENVIRONMENT")
	viper.BindEnv("server.frontend_path", "GARAGE_UI_SERVER_FRONTEND_PATH")
	viper.BindEnv("server.domain", "GARAGE_UI_SERVER_DOMAIN")
	viper.BindEnv("server.protocol", "GARAGE_UI_SERVER_PROTOCOL")
	viper.BindEnv("server.root_url", "GARAGE_UI_SERVER_ROOT_URL")
	viper.BindEnv("server.max_body_size", "GARAGE_UI_SERVER_MAX_BODY_SIZE")
	viper.BindEnv("server.max_header_size", "GARAGE_UI_SERVER_MAX_HEADER_SIZE")
	viper.BindEnv("server.read_buffer_size", "GARAGE_UI_SERVER_READ_BUFFER_SIZE")
	viper.BindEnv("server.write_buffer_size", "GARAGE_UI_SERVER_WRITE_BUFFER_SIZE")

	// Garage config
	viper.BindEnv("garage.endpoint", "GARAGE_UI_GARAGE_ENDPOINT")
	viper.BindEnv("garage.region", "GARAGE_UI_GARAGE_REGION")
	viper.BindEnv("garage.use_ssl", "GARAGE_UI_GARAGE_USE_SSL")
	viper.BindEnv("garage.force_path_style", "GARAGE_UI_GARAGE_FORCE_PATH_STYLE")
	viper.BindEnv("garage.admin_endpoint", "GARAGE_UI_GARAGE_ADMIN_ENDPOINT")
	viper.BindEnv("garage.admin_token", "GARAGE_UI_GARAGE_ADMIN_TOKEN")

	// Auth config
	viper.BindEnv("auth.admin.enabled", "GARAGE_UI_AUTH_ADMIN_ENABLED")
	viper.BindEnv("auth.admin.username", "GARAGE_UI_AUTH_ADMIN_USERNAME")
	viper.BindEnv("auth.admin.password", "GARAGE_UI_AUTH_ADMIN_PASSWORD")
	viper.BindEnv("auth.jwt_private_key", "GARAGE_UI_AUTH_JWT_PRIVATE_KEY")

	// OIDC config
	viper.BindEnv("auth.oidc.enabled", "GARAGE_UI_AUTH_OIDC_ENABLED")
	viper.BindEnv("auth.oidc.provider_name", "GARAGE_UI_AUTH_OIDC_PROVIDER_NAME")
	viper.BindEnv("auth.oidc.client_id", "GARAGE_UI_AUTH_OIDC_CLIENT_ID")
	viper.BindEnv("auth.oidc.client_secret", "GARAGE_UI_AUTH_OIDC_CLIENT_SECRET")
	viper.BindEnv("auth.oidc.scopes", "GARAGE_UI_AUTH_OIDC_SCOPES")
	viper.BindEnv("auth.oidc.issuer_url", "GARAGE_UI_AUTH_OIDC_ISSUER_URL")
	viper.BindEnv("auth.oidc.skip_issuer_check", "GARAGE_UI_AUTH_OIDC_SKIP_ISSUER_CHECK")
	viper.BindEnv("auth.oidc.skip_expiry_check", "GARAGE_UI_AUTH_OIDC_SKIP_EXPIRY_CHECK")
	viper.BindEnv("auth.oidc.email_attribute", "GARAGE_UI_AUTH_OIDC_EMAIL_ATTRIBUTE")
	viper.BindEnv("auth.oidc.username_attribute", "GARAGE_UI_AUTH_OIDC_USERNAME_ATTRIBUTE")
	viper.BindEnv("auth.oidc.name_attribute", "GARAGE_UI_AUTH_OIDC_NAME_ATTRIBUTE")
	viper.BindEnv("auth.oidc.role_attribute_path", "GARAGE_UI_AUTH_OIDC_ROLE_ATTRIBUTE_PATH")
	viper.BindEnv("auth.oidc.admin_role", "GARAGE_UI_AUTH_OIDC_ADMIN_ROLE")
	viper.BindEnv("auth.oidc.tls_skip_verify", "GARAGE_UI_AUTH_OIDC_TLS_SKIP_VERIFY")
	viper.BindEnv("auth.oidc.session_max_age", "GARAGE_UI_AUTH_OIDC_SESSION_MAX_AGE")
	viper.BindEnv("auth.oidc.cookie_name", "GARAGE_UI_AUTH_OIDC_COOKIE_NAME")
	viper.BindEnv("auth.oidc.cookie_secure", "GARAGE_UI_AUTH_OIDC_COOKIE_SECURE")
	viper.BindEnv("auth.oidc.cookie_http_only", "GARAGE_UI_AUTH_OIDC_COOKIE_HTTP_ONLY")
	viper.BindEnv("auth.oidc.cookie_same_site", "GARAGE_UI_AUTH_OIDC_COOKIE_SAME_SITE")

	// CORS config
	viper.BindEnv("cors.enabled", "GARAGE_UI_CORS_ENABLED")
	viper.BindEnv("cors.allowed_origins", "GARAGE_UI_CORS_ALLOWED_ORIGINS")
	viper.BindEnv("cors.allowed_methods", "GARAGE_UI_CORS_ALLOWED_METHODS")
	viper.BindEnv("cors.allowed_headers", "GARAGE_UI_CORS_ALLOWED_HEADERS")
	viper.BindEnv("cors.allow_credentials", "GARAGE_UI_CORS_ALLOW_CREDENTIALS")
	viper.BindEnv("cors.max_age", "GARAGE_UI_CORS_MAX_AGE")

	// Logging config
	viper.BindEnv("logging.level", "GARAGE_UI_LOGGING_LEVEL")
	viper.BindEnv("logging.format", "GARAGE_UI_LOGGING_FORMAT")
}

// Validate checks if the configuration is valid
func (c *Config) Validate() error {
	// Validate server config
	if c.Server.Port <= 0 || c.Server.Port > 65535 {
		return fmt.Errorf("invalid server port: %d", c.Server.Port)
	}

	// Validate Garage config
	if c.Garage.Endpoint == "" {
		return fmt.Errorf("garage endpoint is required")
	}
	if c.Garage.AdminEndpoint == "" {
		return fmt.Errorf("garage admin_endpoint is required")
	}
	if c.Garage.AdminToken == "" {
		return fmt.Errorf("garage admin_token is required")
	}

	// Validate admin auth if enabled
	if c.Auth.Admin.Enabled {
		if c.Auth.Admin.Username == "" || c.Auth.Admin.Password == "" {
			return fmt.Errorf("admin auth username and password are required when admin auth is enabled")
		}
	}

	// Validate OIDC config if enabled
	if c.Auth.OIDC.Enabled {
		if c.Auth.OIDC.ClientID == "" {
			return fmt.Errorf("oidc client_id is required when oidc is enabled")
		}
		if c.Auth.OIDC.IssuerURL == "" {
			return fmt.Errorf("oidc issuer_url is required when oidc is enabled")
		}
		if c.Server.RootURL == "" {
			return fmt.Errorf("server.root_url is required when oidc is enabled")
		}
		if len(c.Auth.OIDC.Scopes) == 0 {
			return fmt.Errorf("oidc scopes are required when oidc is enabled")
		}
		// Every authenticated route on this service grants full admin
		// access — there is no separate authorization layer. An empty
		// admin_role would therefore promote every user in the IdP realm
		// to cluster admin. Require operators to opt in explicitly.
		if c.Auth.OIDC.AdminRole == "" {
			return fmt.Errorf("oidc admin_role is required when oidc is enabled: leaving it empty would grant cluster-admin access to any authenticated IdP user")
		}
	}

	return nil
}

// GetAddress returns the full server address (host:port)
func (c *Config) GetAddress() string {
	return fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
}

// IsDevelopment returns true if running in development mode
func (c *Config) IsDevelopment() bool {
	return c.Server.Environment == "development"
}

// IsProduction returns true if running in production mode
func (c *Config) IsProduction() bool {
	return c.Server.Environment == "production"
}
