package replica

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

// BinlogConfig holds the configuration for connecting to MySQL/MariaDB binlog
type BinlogConfig struct {
	Host     string
	Port     uint16
	User     string
	Password string
	ServerID uint32
	Name     string // Instance name for logging
}

// SecondaryConfig holds the configuration for a secondary database
type SecondaryConfig struct {
	Name     string // Friendly name for logging
	Host     string
	Port     uint16
	User     string
	Password string
	DSN      string // Pre-built DSN for convenience
}

// GraylogConfig holds the configuration for Graylog integration
type GraylogConfig struct {
	Enabled     bool
	Endpoint    string
	Protocol    string
	Timeout     time.Duration
	Source      string
	ExtraFields map[string]interface{}
}

// AppConfig holds the complete application configuration
type AppConfig struct {
	// Primary configuration
	Primary BinlogConfig

	// Secondary configurations
	Secondaries []SecondaryConfig

	// Transfer settings
	BatchSize      int
	ExcludeSchemas []string

	// Graylog configuration
	Graylog GraylogConfig
}

// LoadEnvConfig loads configuration from environment variables
func LoadEnvConfig() (*AppConfig, error) {
	cfg := &AppConfig{
		BatchSize:      1000,
		ExcludeSchemas: []string{"information_schema", "performance_schema", "mysql", "sys"},
	}

	// Primary configuration
	cfg.Primary.Host = getEnv("MARIA_PRIMARY_HOST", "localhost")
	cfg.Primary.Port = uint16(getEnvInt("MARIA_PRIMARY_PORT", 3306))
	cfg.Primary.User = getEnv("MARIA_USER", "replica")
	cfg.Primary.Password = getEnv("MARIA_PASS", "replica")
	cfg.Primary.ServerID = uint32(getEnvInt("MARIA_SERVER_ID", 100))
	cfg.Primary.Name = getEnv("MARIA_PRIMARY_NAME", "mariadb-primary")

	// Parse secondary hosts (comma-separated)
	secondaryHosts := getEnv("MARIA_SECONDARY_HOSTS", "")
	if secondaryHosts == "" {
		// Fallback to single secondary for backward compatibility
		secondaryHosts = getEnv("MARIA_SECONDARY_HOST", "localhost")
	}

	// Parse secondary ports (comma-separated, must match number of hosts or be single value)
	secondaryPortsStr := getEnv("MARIA_SECONDARY_PORTS", "")
	secondaryPorts := parseUint16List(secondaryPortsStr, 3307)

	// Parse secondary users (comma-separated, optional)
	secondaryUsers := getEnv("MARIA_SECONDARY_USERS", "")
	users := parseStringList(secondaryUsers, cfg.Primary.User)

	// Parse secondary passwords (comma-separated, optional)
	secondaryPasswords := getEnv("MARIA_SECONDARY_PASSWORDS", "")
	passwords := parseStringList(secondaryPasswords, cfg.Primary.Password)

	// Parse secondary names (comma-separated, optional)
	secondaryNames := getEnv("MARIA_SECONDARY_NAMES", "")

	hosts := parseStringList(secondaryHosts, "")
	names := parseStringList(secondaryNames, "")
	portMap := makePortsMap(hosts, secondaryPorts, 3307)
	userMap := makeStringMap(hosts, users, cfg.Primary.User)
	passMap := makeStringMap(hosts, passwords, cfg.Primary.Password)

	// Build secondary configurations
	for i, host := range hosts {
		name := strconv.Itoa(i + 1)
		if i < len(names) && names[i] != "" {
			name = names[i]
		}

		port := uint16(3307)
		if p, ok := portMap[host]; ok {
			port = p
		}

		user := cfg.Primary.User
		if u, ok := userMap[host]; ok {
			user = u
		}

		pass := cfg.Primary.Password
		if p, ok := passMap[host]; ok {
			pass = p
		}

		dsn := buildDSN(user, pass, host, int(port), "replica")

		cfg.Secondaries = append(cfg.Secondaries, SecondaryConfig{
			Name:     name,
			Host:     host,
			Port:     port,
			User:     user,
			Password: pass,
			DSN:      dsn,
		})
	}

	// Batch size override
	if batchSize := getEnvInt("TRANSFER_BATCH_SIZE", 0); batchSize > 0 {
		cfg.BatchSize = batchSize
	}

	// Graylog configuration
	cfg.Graylog.Enabled = getEnvBool("GRAYLOG_ENABLED", false)
	cfg.Graylog.Endpoint = getEnv("GRAYLOG_ENDPOINT", "localhost:12201")
	cfg.Graylog.Protocol = getEnv("GRAYLOG_PROTOCOL", "udp")
	cfg.Graylog.Source = getEnv("GRAYLOG_SOURCE", "binlog-sync")

	// Parse timeout
	if timeout := getEnv("GRAYLOG_TIMEOUT", "5s"); timeout != "" {
		d, err := time.ParseDuration(timeout)
		if err == nil {
			cfg.Graylog.Timeout = d
		} else {
			cfg.Graylog.Timeout = 5 * time.Second
		}
	}

	return cfg, nil
}

// getEnv gets an environment variable with a default value
func getEnv(key, defaultValue string) string {
	value := os.Getenv(key)
	if value == "" {
		return defaultValue
	}
	return value
}

// getEnvInt gets an environment variable as an integer with a default value
func getEnvInt(key string, defaultValue int) int {
	value := os.Getenv(key)
	if value == "" {
		return defaultValue
	}
	i, err := strconv.Atoi(value)
	if err != nil {
		return defaultValue
	}
	return i
}

// getEnvBool gets an environment variable as a boolean with a default value
func getEnvBool(key string, defaultValue bool) bool {
	value := os.Getenv(key)
	if value == "" {
		return defaultValue
	}
	b, err := strconv.ParseBool(value)
	if err != nil {
		return defaultValue
	}
	return b
}

// parseStringList parses a comma-separated string into a list
func parseStringList(s string, defaultValue string) []string {
	if strings.TrimSpace(s) == "" {
		if defaultValue == "" {
			return nil
		}
		return []string{defaultValue}
	}
	parts := strings.Split(s, ",")
	result := make([]string, len(parts))
	for i, p := range parts {
		result[i] = strings.TrimSpace(p)
	}
	return result
}

// parseUint16List parses a comma-separated string into a list of uint16
func parseUint16List(s string, defaultValue uint16) []uint16 {
	if strings.TrimSpace(s) == "" {
		return []uint16{defaultValue}
	}
	parts := strings.Split(s, ",")
	result := make([]uint16, len(parts))
	for i, p := range parts {
		p = strings.TrimSpace(p)
		val, err := strconv.ParseUint(p, 10, 16)
		if err != nil {
			result[i] = defaultValue
		} else {
			result[i] = uint16(val)
		}
	}
	return result
}

// makePortsMap creates a map of host to port
func makePortsMap(hosts []string, ports []uint16, defaultPort uint16) map[string]uint16 {
	result := make(map[string]uint16)
	for i, host := range hosts {
		if i < len(ports) {
			result[host] = ports[i]
		} else {
			result[host] = defaultPort
		}
	}
	return result
}

// makeStringMap creates a map of host to string value
func makeStringMap(hosts []string, values []string, defaultValue string) map[string]string {
	result := make(map[string]string)
	for i, host := range hosts {
		if i < len(values) && values[i] != "" {
			result[host] = values[i]
		} else {
			result[host] = defaultValue
		}
	}
	return result
}

// buildDSN builds a MySQL DSN string
func buildDSN(user, password, host string, port int, database string) string {
	return user + ":" + password + "@tcp(" + host + ":" + strconv.Itoa(port) + ")/" + database + "?multiStatements=true"
}
