package config

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"
)

// Config holds all application configuration
type Config struct {
	App            AppConfig
	Server         ServerConfig
	HTTP           HTTPConfig
	Security       SecurityConfig
	Database       DatabaseConfig
	Cache          CacheConfig
	AWS            AWSConfig
	Logger         LoggerConfig
	Mailgun        MailgunConfig
	Meilisearch    MeilisearchConfig
	Scheduler      SchedulerConfig
	RateLimit      RateLimitConfig
	LeaderElection LeaderElectionConfig
}

// AppConfig holds application-level configuration
type AppConfig struct {
	Environment     string
	Version         string
	JWTSecret       string
	GeoLiteDBPath   string
	BannedCountries []string
}

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

// ServerConfig holds HTTP server configuration
type ServerConfig struct {
	Host string
	Port int
}

// HTTPConfig holds HTTP request handling configuration
type HTTPConfig struct {
	MaxRequestBodySize int64         // Maximum request body size in bytes
	ReadTimeout        time.Duration // Maximum duration for reading the entire request
	WriteTimeout       time.Duration // Maximum duration before timing out writes of the response
	IdleTimeout        time.Duration // Maximum amount of time to wait for the next request
}

// SecurityConfig holds security-related configuration
type SecurityConfig struct {
	TrustedProxies  []string // CIDR blocks of trusted reverse proxies for X-Forwarded-For validation
	IPEncryptionKey string   // 32-character hex key (16 bytes) for IP address encryption (GDPR compliance)
	AllowedOrigins  []string // CORS allowed origins (e.g., https://getmaplepress.com)
}

// DatabaseConfig holds Cassandra database configuration
type DatabaseConfig struct {
	Hosts          []string
	Keyspace       string
	Consistency    string
	Replication    int
	MigrationsPath string
}

// CacheConfig holds Redis cache configuration
type CacheConfig struct {
	Host     string
	Port     int
	Password string
	DB       int
}

// AWSConfig holds AWS S3 configuration
type AWSConfig struct {
	AccessKey  string
	SecretKey  string
	Endpoint   string
	Region     string
	BucketName string
}

// LoggerConfig holds logging configuration
type LoggerConfig struct {
	Level  string
	Format string
}

// MailgunConfig holds Mailgun email service configuration
type MailgunConfig struct {
	APIKey           string
	Domain           string
	APIBase          string
	SenderEmail      string
	MaintenanceEmail string
	FrontendDomain   string
	BackendDomain    string
}

// MeilisearchConfig holds Meilisearch configuration
type MeilisearchConfig struct {
	Host        string
	APIKey      string
	IndexPrefix string
}

// SchedulerConfig holds scheduler configuration
type SchedulerConfig struct {
	QuotaResetEnabled  bool
	QuotaResetSchedule string // Cron format: "0 0 1 * *" = first day of month at midnight
	IPCleanupEnabled   bool
	IPCleanupSchedule  string // Cron format: "0 2 * * *" = daily at 2 AM
}

// RateLimitConfig holds rate limiting configuration
type RateLimitConfig struct {
	// Registration endpoint rate limiting
	// CWE-307: Prevents automated account creation and bot signups
	RegistrationEnabled     bool
	RegistrationMaxRequests int
	RegistrationWindow      time.Duration

	// Login endpoint rate limiting
	// CWE-307: Dual protection (IP-based + account lockout) against brute force attacks
	LoginEnabled                   bool
	LoginMaxAttemptsPerIP          int
	LoginIPWindow                  time.Duration
	LoginMaxFailedAttemptsPerAccount int
	LoginAccountLockoutDuration    time.Duration

	// Generic CRUD endpoints rate limiting
	// CWE-770: Protects authenticated endpoints (tenant/user/site management) from resource exhaustion
	GenericEnabled     bool
	GenericMaxRequests int
	GenericWindow      time.Duration

	// Plugin API endpoints rate limiting
	// CWE-770: Lenient limits for core business endpoints (WordPress plugin integration)
	PluginAPIEnabled     bool
	PluginAPIMaxRequests int
	PluginAPIWindow      time.Duration
}

// LeaderElectionConfig holds leader election configuration
type LeaderElectionConfig struct {
	Enabled           bool
	LockTTL           time.Duration
	HeartbeatInterval time.Duration
	RetryInterval     time.Duration
}

// Load loads configuration from environment variables
func Load() (*Config, error) {
	cfg := &Config{
		App: AppConfig{
			Environment:     getEnv("APP_ENVIRONMENT", "development"),
			Version:         getEnv("APP_VERSION", "0.1.0"),
			JWTSecret:       getEnv("APP_JWT_SECRET", "change-me-in-production"),
			GeoLiteDBPath:   getEnv("APP_GEOLITE_DB_PATH", ""),
			BannedCountries: getEnvAsSlice("APP_BANNED_COUNTRIES", []string{}),
		},
		Server: ServerConfig{
			Host: getEnv("SERVER_HOST", "0.0.0.0"),
			Port: getEnvAsInt("SERVER_PORT", 8000),
		},
		HTTP: HTTPConfig{
			MaxRequestBodySize: getEnvAsInt64("HTTP_MAX_REQUEST_BODY_SIZE", 10*1024*1024), // 10 MB default
			ReadTimeout:        getEnvAsDuration("HTTP_READ_TIMEOUT", 30*time.Second),
			WriteTimeout:       getEnvAsDuration("HTTP_WRITE_TIMEOUT", 30*time.Second),
			IdleTimeout:        getEnvAsDuration("HTTP_IDLE_TIMEOUT", 60*time.Second),
		},
		Security: SecurityConfig{
			// CWE-348: Trusted proxies for X-Forwarded-For validation
			// Example: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" for private networks
			// Leave empty to disable X-Forwarded-For trust (most secure for direct connections)
			TrustedProxies: getEnvAsSlice("SECURITY_TRUSTED_PROXIES", []string{}),
			// CWE-359: IP encryption key for GDPR compliance
			// Must be 32 hex characters (16 bytes). Generate with: openssl rand -hex 16
			IPEncryptionKey: getEnv("SECURITY_IP_ENCRYPTION_KEY", "00112233445566778899aabbccddeeff"),
			// CORS allowed origins (comma-separated)
			// Example: "https://getmaplepress.com,https://www.getmaplepress.com"
			// In development, localhost origins are automatically added
			AllowedOrigins: getEnvAsSlice("SECURITY_CORS_ALLOWED_ORIGINS", []string{}),
		},
		Database: DatabaseConfig{
			Hosts:          getEnvAsSlice("DATABASE_HOSTS", []string{"localhost"}),
			Keyspace:       getEnv("DATABASE_KEYSPACE", "maplepress"),
			Consistency:    getEnv("DATABASE_CONSISTENCY", "QUORUM"),
			Replication:    getEnvAsInt("DATABASE_REPLICATION", 3),
			MigrationsPath: getEnv("DATABASE_MIGRATIONS_PATH", "file://migrations"),
		},
		Cache: CacheConfig{
			Host:     getEnv("CACHE_HOST", "localhost"),
			Port:     getEnvAsInt("CACHE_PORT", 6379),
			Password: getEnv("CACHE_PASSWORD", ""),
			DB:       getEnvAsInt("CACHE_DB", 0),
		},
		AWS: AWSConfig{
			AccessKey:  getEnv("AWS_ACCESS_KEY", ""),
			SecretKey:  getEnv("AWS_SECRET_KEY", ""),
			Endpoint:   getEnv("AWS_ENDPOINT", ""),
			Region:     getEnv("AWS_REGION", "us-east-1"),
			BucketName: getEnv("AWS_BUCKET_NAME", ""),
		},
		Logger: LoggerConfig{
			Level:  getEnv("LOGGER_LEVEL", "info"),
			Format: getEnv("LOGGER_FORMAT", "json"),
		},
		Mailgun: MailgunConfig{
			APIKey:           getEnv("MAILGUN_API_KEY", ""),
			Domain:           getEnv("MAILGUN_DOMAIN", ""),
			APIBase:          getEnv("MAILGUN_API_BASE", "https://api.mailgun.net/v3"),
			SenderEmail:      getEnv("MAILGUN_SENDER_EMAIL", "noreply@maplepress.app"),
			MaintenanceEmail: getEnv("MAILGUN_MAINTENANCE_EMAIL", "admin@maplepress.app"),
			FrontendDomain:   getEnv("MAILGUN_FRONTEND_DOMAIN", "https://maplepress.app"),
			BackendDomain:    getEnv("MAILGUN_BACKEND_DOMAIN", "https://api.maplepress.app"),
		},
		Meilisearch: MeilisearchConfig{
			Host:        getEnv("MEILISEARCH_HOST", "http://localhost:7700"),
			APIKey:      getEnv("MEILISEARCH_API_KEY", ""),
			IndexPrefix: getEnv("MEILISEARCH_INDEX_PREFIX", "site_"),
		},
		Scheduler: SchedulerConfig{
			QuotaResetEnabled:  getEnvAsBool("SCHEDULER_QUOTA_RESET_ENABLED", true),
			QuotaResetSchedule: getEnv("SCHEDULER_QUOTA_RESET_SCHEDULE", "0 0 1 * *"), // 1st of month at midnight
			IPCleanupEnabled:   getEnvAsBool("SCHEDULER_IP_CLEANUP_ENABLED", true),    // CWE-359: GDPR compliance
			IPCleanupSchedule:  getEnv("SCHEDULER_IP_CLEANUP_SCHEDULE", "0 2 * * *"),  // Daily at 2 AM
		},
		RateLimit: RateLimitConfig{
			// Registration rate limiting (CWE-307)
			RegistrationEnabled:     getEnvAsBool("RATELIMIT_REGISTRATION_ENABLED", true),
			RegistrationMaxRequests: getEnvAsInt("RATELIMIT_REGISTRATION_MAX_REQUESTS", 5),
			RegistrationWindow:      getEnvAsDuration("RATELIMIT_REGISTRATION_WINDOW", time.Hour),

			// Login rate limiting (CWE-307)
			LoginEnabled:                   getEnvAsBool("RATELIMIT_LOGIN_ENABLED", true),
			LoginMaxAttemptsPerIP:          getEnvAsInt("RATELIMIT_LOGIN_MAX_ATTEMPTS_PER_IP", 10),
			LoginIPWindow:                  getEnvAsDuration("RATELIMIT_LOGIN_IP_WINDOW", 15*time.Minute),
			LoginMaxFailedAttemptsPerAccount: getEnvAsInt("RATELIMIT_LOGIN_MAX_FAILED_ATTEMPTS_PER_ACCOUNT", 10),
			LoginAccountLockoutDuration:    getEnvAsDuration("RATELIMIT_LOGIN_ACCOUNT_LOCKOUT_DURATION", 30*time.Minute),

			// Generic CRUD endpoints rate limiting (CWE-770)
			GenericEnabled:     getEnvAsBool("RATELIMIT_GENERIC_ENABLED", true),
			GenericMaxRequests: getEnvAsInt("RATELIMIT_GENERIC_MAX_REQUESTS", 100),
			GenericWindow:      getEnvAsDuration("RATELIMIT_GENERIC_WINDOW", time.Hour),

			// Plugin API endpoints rate limiting (CWE-770) - Anti-abuse only
			// Generous limits for usage-based billing (no hard quotas)
			PluginAPIEnabled:     getEnvAsBool("RATELIMIT_PLUGIN_API_ENABLED", true),
			PluginAPIMaxRequests: getEnvAsInt("RATELIMIT_PLUGIN_API_MAX_REQUESTS", 10000),
			PluginAPIWindow:      getEnvAsDuration("RATELIMIT_PLUGIN_API_WINDOW", time.Hour),
		},
		LeaderElection: LeaderElectionConfig{
			Enabled:           getEnvAsBool("LEADER_ELECTION_ENABLED", true),
			LockTTL:           getEnvAsDuration("LEADER_ELECTION_LOCK_TTL", 10*time.Second),
			HeartbeatInterval: getEnvAsDuration("LEADER_ELECTION_HEARTBEAT_INTERVAL", 3*time.Second),
			RetryInterval:     getEnvAsDuration("LEADER_ELECTION_RETRY_INTERVAL", 2*time.Second),
		},
	}

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

	return cfg, nil
}

// GetSchedulerConfig returns scheduler configuration values
func (c *Config) GetSchedulerConfig() (enabled bool, schedule string) {
	return c.Scheduler.QuotaResetEnabled, c.Scheduler.QuotaResetSchedule
}

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

	if c.Database.Keyspace == "" {
		return fmt.Errorf("database keyspace is required")
	}

	if len(c.Database.Hosts) == 0 {
		return fmt.Errorf("at least one database host is required")
	}

	if c.App.JWTSecret == "" {
		return fmt.Errorf("APP_JWT_SECRET is required")
	}

	// Security validation for credentials (CWE-798: Use of Hard-coded Credentials)
	if err := c.validateSecurityCredentials(); err != nil {
		return err
	}

	return nil
}

// validateSecurityCredentials performs security validation on credentials
// This addresses CWE-798 (Use of Hard-coded Credentials)
func (c *Config) validateSecurityCredentials() error {
	// Check if JWT secret is using the default hard-coded value
	if strings.Contains(strings.ToLower(c.App.JWTSecret), "change-me") ||
		strings.Contains(strings.ToLower(c.App.JWTSecret), "changeme") {

		if c.App.Environment == "production" {
			return fmt.Errorf(
				"SECURITY ERROR: JWT secret is using default/placeholder value in production. " +
					"Generate a secure secret with: openssl rand -base64 64",
			)
		}

		// Warn in development
		log.Printf(
			"[WARNING] JWT secret is using default/placeholder value. " +
				"This is acceptable for development but MUST be changed for production. " +
				"Generate a secure secret with: openssl rand -base64 64",
		)
	}

	// Validate IP encryption key format (CWE-359: GDPR compliance)
	if c.Security.IPEncryptionKey != "" {
		if len(c.Security.IPEncryptionKey) != 32 {
			return fmt.Errorf(
				"SECURITY ERROR: IP encryption key must be exactly 32 hex characters (16 bytes). " +
					"Generate with: openssl rand -hex 16",
			)
		}
		// Check if valid hex
		for _, char := range c.Security.IPEncryptionKey {
			if !((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f') || (char >= 'A' && char <= 'F')) {
				return fmt.Errorf(
					"SECURITY ERROR: IP encryption key must contain only hex characters (0-9, a-f). " +
						"Generate with: openssl rand -hex 16",
				)
			}
		}
	}

	// In production, enforce additional security checks
	if c.App.Environment == "production" {
		// Check IP encryption key is not using default value
		if c.Security.IPEncryptionKey == "00112233445566778899aabbccddeeff" {
			return fmt.Errorf(
				"SECURITY ERROR: IP encryption key is using default value in production. " +
					"Generate a secure key with: openssl rand -hex 16",
			)
		}

		// Check JWT secret minimum length
		if len(c.App.JWTSecret) < 32 {
			return fmt.Errorf(
				"SECURITY ERROR: JWT secret is too short for production (%d characters). "+
					"Minimum required: 32 characters (256 bits). "+
					"Generate a secure secret with: openssl rand -base64 64",
				len(c.App.JWTSecret),
			)
		}

		// Check for common weak secrets
		weakSecrets := []string{"secret", "password", "12345", "admin", "test", "default"}
		secretLower := strings.ToLower(c.App.JWTSecret)
		for _, weak := range weakSecrets {
			if secretLower == weak {
				return fmt.Errorf(
					"SECURITY ERROR: JWT secret is using a common weak value: '%s'. "+
						"Generate a secure secret with: openssl rand -base64 64",
					weak,
				)
			}
		}

		// Check Meilisearch API key in production
		if c.Meilisearch.APIKey == "" {
			return fmt.Errorf("SECURITY ERROR: Meilisearch API key must be set in production")
		}

		meilisearchKeyLower := strings.ToLower(c.Meilisearch.APIKey)
		if strings.Contains(meilisearchKeyLower, "change") ||
			strings.Contains(meilisearchKeyLower, "dev") ||
			strings.Contains(meilisearchKeyLower, "test") {
			return fmt.Errorf(
				"SECURITY ERROR: Meilisearch API key appears to be a development/placeholder value",
			)
		}

		// Check database hosts are not using localhost in production
		for _, host := range c.Database.Hosts {
			hostLower := strings.ToLower(host)
			if strings.Contains(hostLower, "localhost") || host == "127.0.0.1" {
				return fmt.Errorf(
					"SECURITY ERROR: Database hosts should not use localhost in production. Found: %s",
					host,
				)
			}
		}

		// Check cache host is not localhost in production
		cacheLower := strings.ToLower(c.Cache.Host)
		if strings.Contains(cacheLower, "localhost") || c.Cache.Host == "127.0.0.1" {
			return fmt.Errorf(
				"SECURITY ERROR: Cache host should not use localhost in production. Found: %s",
				c.Cache.Host,
			)
		}
	}

	return nil
}

// Helper functions to get environment variables

func getEnv(key, defaultValue string) string {
	value := os.Getenv(key)
	if value == "" {
		return defaultValue
	}
	return value
}

func getEnvAsInt(key string, defaultValue int) int {
	valueStr := os.Getenv(key)
	if valueStr == "" {
		return defaultValue
	}

	value, err := strconv.Atoi(valueStr)
	if err != nil {
		return defaultValue
	}

	return value
}

func getEnvAsInt64(key string, defaultValue int64) int64 {
	valueStr := os.Getenv(key)
	if valueStr == "" {
		return defaultValue
	}

	value, err := strconv.ParseInt(valueStr, 10, 64)
	if err != nil {
		return defaultValue
	}

	return value
}

func getEnvAsBool(key string, defaultValue bool) bool {
	valueStr := os.Getenv(key)
	if valueStr == "" {
		return defaultValue
	}

	value, err := strconv.ParseBool(valueStr)
	if err != nil {
		return defaultValue
	}

	return value
}

func getEnvAsSlice(key string, defaultValue []string) []string {
	valueStr := os.Getenv(key)
	if valueStr == "" {
		return defaultValue
	}

	// Simple comma-separated parsing
	// For production, consider using a proper CSV parser
	var result []string
	current := ""
	for _, char := range valueStr {
		if char == ',' {
			if current != "" {
				result = append(result, current)
				current = ""
			}
		} else {
			current += string(char)
		}
	}
	if current != "" {
		result = append(result, current)
	}

	if len(result) == 0 {
		return defaultValue
	}

	return result
}

func getEnvAsDuration(key string, defaultValue time.Duration) time.Duration {
	valueStr := os.Getenv(key)
	if valueStr == "" {
		return defaultValue
	}

	value, err := time.ParseDuration(valueStr)
	if err != nil {
		return defaultValue
	}

	return value
}
