package config

import (
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"dbbackup/internal/cpu"
)

// Config holds all configuration options
type Config struct {
	// Version information
	Version   string
	BuildTime string
	GitCommit string

	// Database connection
	Host         string
	Port         int
	User         string
	Database     string
	Password     string
	DatabaseType string // "postgres" or "mysql"
	SSLMode      string
	Insecure     bool

	// Backup options
	BackupDir        string
	CompressionLevel int
	Jobs             int
	DumpJobs         int
	MaxCores         int
	AutoDetectCores  bool
	CPUWorkloadType  string // "cpu-intensive", "io-intensive", "balanced"

	// CPU detection
	CPUDetector *cpu.Detector
	CPUInfo     *cpu.CPUInfo

	// Sample backup options
	SampleStrategy string // "ratio", "percent", "count"
	SampleValue    int

	// Output options
	NoColor      bool
	Debug        bool
	LogLevel     string
	LogFormat    string
	OutputLength int

	// Single database backup/restore
	SingleDBName  string
	RestoreDBName string
}

// New creates a new configuration with default values
func New() *Config {
	// Get default backup directory
	backupDir := getEnvString("BACKUP_DIR", getDefaultBackupDir())

	// Initialize CPU detector
	cpuDetector := cpu.NewDetector()
	cpuInfo, _ := cpuDetector.DetectCPU()

	dbTypeRaw := getEnvString("DB_TYPE", "postgres")
	canonicalType, ok := canonicalDatabaseType(dbTypeRaw)
	if !ok {
		canonicalType = "postgres"
	}

	host := getEnvString("PG_HOST", "localhost")
	port := getEnvInt("PG_PORT", postgresDefaultPort)
	user := getEnvString("PG_USER", getCurrentUser())
	databaseName := getEnvString("PG_DATABASE", "postgres")
	password := getEnvString("PGPASSWORD", "")
	sslMode := getEnvString("PG_SSLMODE", "prefer")

	if canonicalType == "mysql" {
		host = getEnvString("MYSQL_HOST", host)
		port = getEnvInt("MYSQL_PORT", mysqlDefaultPort)
		user = getEnvString("MYSQL_USER", user)
		if db := getEnvString("MYSQL_DATABASE", ""); db != "" {
			databaseName = db
		}
		if pwd := getEnvString("MYSQL_PWD", ""); pwd != "" {
			password = pwd
		}
		sslMode = ""
	}

	cfg := &Config{
		// Database defaults
		Host:         host,
		Port:         port,
		User:         user,
		Database:     databaseName,
		Password:     password,
		DatabaseType: canonicalType,
		SSLMode:      sslMode,
		Insecure:     getEnvBool("INSECURE", false),

		// Backup defaults
		BackupDir:        backupDir,
		CompressionLevel: getEnvInt("COMPRESS_LEVEL", 6),
		Jobs:             getEnvInt("JOBS", getDefaultJobs(cpuInfo)),
		DumpJobs:         getEnvInt("DUMP_JOBS", getDefaultDumpJobs(cpuInfo)),
		MaxCores:         getEnvInt("MAX_CORES", getDefaultMaxCores(cpuInfo)),
		AutoDetectCores:  getEnvBool("AUTO_DETECT_CORES", true),
		CPUWorkloadType:  getEnvString("CPU_WORKLOAD_TYPE", "balanced"),

		// CPU detection
		CPUDetector: cpuDetector,
		CPUInfo:     cpuInfo,

		// Sample backup defaults
		SampleStrategy: getEnvString("SAMPLE_STRATEGY", "ratio"),
		SampleValue:    getEnvInt("SAMPLE_VALUE", 10),

		// Output defaults
		NoColor:      getEnvBool("NO_COLOR", false),
		Debug:        getEnvBool("DEBUG", false),
		LogLevel:     getEnvString("LOG_LEVEL", "info"),
		LogFormat:    getEnvString("LOG_FORMAT", "text"),
		OutputLength: getEnvInt("OUTPUT_LENGTH", 0),

		// Single database options
		SingleDBName:  getEnvString("SINGLE_DB_NAME", ""),
		RestoreDBName: getEnvString("RESTORE_DB_NAME", ""),
	}

	// Ensure canonical defaults are enforced
	if err := cfg.SetDatabaseType(cfg.DatabaseType); err != nil {
		cfg.DatabaseType = "postgres"
		cfg.Port = postgresDefaultPort
		cfg.SSLMode = "prefer"
	}

	return cfg
}

// UpdateFromEnvironment updates configuration from environment variables
func (c *Config) UpdateFromEnvironment() {
	if password := os.Getenv("PGPASSWORD"); password != "" {
		c.Password = password
	}
	if password := os.Getenv("MYSQL_PWD"); password != "" && c.DatabaseType == "mysql" {
		c.Password = password
	}
}

// Validate validates the configuration
func (c *Config) Validate() error {
	if err := c.SetDatabaseType(c.DatabaseType); err != nil {
		return err
	}

	if c.CompressionLevel < 0 || c.CompressionLevel > 9 {
		return &ConfigError{Field: "compression", Value: string(rune(c.CompressionLevel)), Message: "must be between 0-9"}
	}

	if c.Jobs < 1 {
		return &ConfigError{Field: "jobs", Value: string(rune(c.Jobs)), Message: "must be at least 1"}
	}

	if c.DumpJobs < 1 {
		return &ConfigError{Field: "dump-jobs", Value: string(rune(c.DumpJobs)), Message: "must be at least 1"}
	}

	return nil
}

// IsPostgreSQL returns true if database type is PostgreSQL
func (c *Config) IsPostgreSQL() bool {
	return c.DatabaseType == "postgres"
}

// IsMySQL returns true if database type is MySQL
func (c *Config) IsMySQL() bool {
	return c.DatabaseType == "mysql"
}

// GetDefaultPort returns the default port for the database type
func (c *Config) GetDefaultPort() int {
	if c.IsMySQL() {
		return 3306
	}
	return 5432
}

// DisplayDatabaseType returns a human-friendly name for the database type
func (c *Config) DisplayDatabaseType() string {
	switch c.DatabaseType {
	case "postgres":
		return "PostgreSQL"
	case "mysql":
		return "MySQL/MariaDB"
	default:
		return c.DatabaseType
	}
}

// SetDatabaseType normalizes the database type and updates dependent defaults
func (c *Config) SetDatabaseType(dbType string) error {
	normalized, ok := canonicalDatabaseType(dbType)
	if !ok {
		return &ConfigError{Field: "database-type", Value: dbType, Message: "must be 'postgres' or 'mysql'"}
	}

	previous := c.DatabaseType
	previousPort := c.Port

	c.DatabaseType = normalized

	if c.Port == 0 {
		c.Port = defaultPortFor(normalized)
	}

	if normalized != previous {
		if previousPort == defaultPortFor(previous) || previousPort == 0 {
			c.Port = defaultPortFor(normalized)
		}
	}

	// Adjust SSL mode defaults when switching engines. Preserve explicit user choices.
	switch normalized {
	case "mysql":
		if strings.EqualFold(c.SSLMode, "prefer") || strings.EqualFold(c.SSLMode, "preferred") {
			c.SSLMode = ""
		}
	case "postgres":
		if c.SSLMode == "" {
			c.SSLMode = "prefer"
		}
	}

	return nil
}

// OptimizeForCPU optimizes job settings based on detected CPU
func (c *Config) OptimizeForCPU() error {
	if c.CPUDetector == nil {
		c.CPUDetector = cpu.NewDetector()
	}

	if c.CPUInfo == nil {
		info, err := c.CPUDetector.DetectCPU()
		if err != nil {
			return err
		}
		c.CPUInfo = info
	}

	if c.AutoDetectCores {
		// Optimize jobs based on workload type
		if jobs, err := c.CPUDetector.CalculateOptimalJobs(c.CPUWorkloadType, c.MaxCores); err == nil {
			c.Jobs = jobs
		}

		// Optimize dump jobs (more conservative for database dumps)
		if dumpJobs, err := c.CPUDetector.CalculateOptimalJobs("cpu-intensive", c.MaxCores/2); err == nil {
			c.DumpJobs = dumpJobs
			if c.DumpJobs > 8 {
				c.DumpJobs = 8 // Conservative limit for dumps
			}
		}
	}

	return nil
}

// GetCPUInfo returns CPU information, detecting if necessary
func (c *Config) GetCPUInfo() (*cpu.CPUInfo, error) {
	if c.CPUInfo != nil {
		return c.CPUInfo, nil
	}

	if c.CPUDetector == nil {
		c.CPUDetector = cpu.NewDetector()
	}

	info, err := c.CPUDetector.DetectCPU()
	if err != nil {
		return nil, err
	}

	c.CPUInfo = info
	return info, nil
}

// ConfigError represents a configuration validation error
type ConfigError struct {
	Field   string
	Value   string
	Message string
}

func (e *ConfigError) Error() string {
	return "config error in field '" + e.Field + "' with value '" + e.Value + "': " + e.Message
}

const (
	postgresDefaultPort = 5432
	mysqlDefaultPort    = 3306
)

func canonicalDatabaseType(input string) (string, bool) {
	switch strings.ToLower(strings.TrimSpace(input)) {
	case "postgres", "postgresql", "pg":
		return "postgres", true
	case "mysql", "mariadb", "mariadb-server", "maria":
		return "mysql", true
	default:
		return "", false
	}
}

func defaultPortFor(dbType string) int {
	switch dbType {
	case "postgres":
		return postgresDefaultPort
	case "mysql":
		return mysqlDefaultPort
	default:
		return postgresDefaultPort
	}
}

// Helper functions
func getEnvString(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

func getEnvInt(key string, defaultValue int) int {
	if value := os.Getenv(key); value != "" {
		if i, err := strconv.Atoi(value); err == nil {
			return i
		}
	}
	return defaultValue
}

func getEnvBool(key string, defaultValue bool) bool {
	if value := os.Getenv(key); value != "" {
		if b, err := strconv.ParseBool(value); err == nil {
			return b
		}
	}
	return defaultValue
}

func getCurrentUser() string {
	if user := os.Getenv("USER"); user != "" {
		return user
	}
	if user := os.Getenv("USERNAME"); user != "" {
		return user
	}
	return "postgres"
}

func getDefaultBackupDir() string {
	// Try to create a sensible default backup directory
	homeDir, _ := os.UserHomeDir()
	if homeDir != "" {
		return filepath.Join(homeDir, "db_backups")
	}

	// Fallback based on OS
	if runtime.GOOS == "windows" {
		return "C:\\db_backups"
	}

	// For PostgreSQL user on Linux/Unix
	if getCurrentUser() == "postgres" {
		return "/var/lib/pgsql/pg_backups"
	}

	return "/tmp/db_backups"
}

// CPU-related helper functions
func getDefaultJobs(cpuInfo *cpu.CPUInfo) int {
	if cpuInfo == nil {
		return 1
	}
	// Default to logical cores for restore operations
	jobs := cpuInfo.LogicalCores
	if jobs < 1 {
		jobs = 1
	}
	if jobs > 16 {
		jobs = 16 // Safety limit
	}
	return jobs
}

func getDefaultDumpJobs(cpuInfo *cpu.CPUInfo) int {
	if cpuInfo == nil {
		return 1
	}
	// Use physical cores for dump operations (CPU intensive)
	jobs := cpuInfo.PhysicalCores
	if jobs < 1 {
		jobs = 1
	}
	if jobs > 8 {
		jobs = 8 // Conservative limit for dumps
	}
	return jobs
}

func getDefaultMaxCores(cpuInfo *cpu.CPUInfo) int {
	if cpuInfo == nil {
		return 16
	}
	// Set max cores to 2x logical cores, with reasonable upper limit
	maxCores := cpuInfo.LogicalCores * 2
	if maxCores < 4 {
		maxCores = 4
	}
	if maxCores > 64 {
		maxCores = 64
	}
	return maxCores
}
