package config

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/joho/godotenv"
)

const (
	EnvDevelop = "develop"
	EnvProd    = "prod"

	DBDriverSQLite   = "sqlite"
	DBDriverPostgres = "postgres"
)

type Config struct {
	AppName   string
	Env       string
	Port      string
	BaseURL   string
	BuildHash string

	DBDriver    string
	SQLitePath  string
	PostgresDSN string

	CORS CORSConfig

	SessionKey string

	SMTP SMTPConfig

	EmailSinkDir string

	AutoMigrate bool
	SeedEnabled bool
}

type CORSConfig struct {
	Origins     []string
	Headers     []string
	Methods     []string
	Credentials bool
}

type SMTPConfig struct {
	Host     string
	Port     int
	Username string
	Password string
	From     string
	FromName string
}

func Load() (*Config, error) {
	if err := godotenv.Load(); err != nil && !errors.Is(err, os.ErrNotExist) {
		return nil, fmt.Errorf("load .env: %w", err)
	}

	cfg := &Config{
		AppName:     envOrDefault("APP_NAME", "trustcontact"),
		Env:         envOrDefault("APP_ENV", EnvDevelop),
		Port:        envOrDefault("APP_PORT", "3000"),
		BaseURL:     envOrDefault("APP_BASE_URL", "http://localhost:3000"),
		BuildHash:   envOrDefault("APP_BUILD_HASH", "dev"),
		DBDriver:    envOrDefault("DB_DRIVER", DBDriverSQLite),
		SQLitePath:  envOrDefault("DB_SQLITE_PATH", "data/app.sqlite3"),
		PostgresDSN: envFirstNonEmpty("DB_POSTGRES_DSN", "DB_PG_DSN"),
		CORS: CORSConfig{
			Origins:     envListOrDefault("CORS_ORIGINS", []string{"http://localhost:3000"}),
			Headers:     envListOrDefault("CORS_HEADERS", []string{"Origin", "Content-Type", "Accept", "Authorization", "HX-Request"}),
			Methods:     envListOrDefault("CORS_METHODS", []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
			Credentials: envBoolOrDefault("CORS_CREDENTIALS", true),
		},
		SessionKey:   envOrDefault("SESSION_KEY", "change-me-in-prod"),
		EmailSinkDir: envOrDefault("EMAIL_SINK_DIR", "data/emails"),
		AutoMigrate:  envBoolOrDefault("AUTO_MIGRATE", true),
		SeedEnabled:  envBoolOrDefault("SEED_ENABLED", false),
		SMTP: SMTPConfig{
			Host:     envOrDefault("SMTP_HOST", "localhost"),
			Port:     envIntOrDefault("SMTP_PORT", 1025),
			Username: strings.TrimSpace(os.Getenv("SMTP_USERNAME")),
			Password: strings.TrimSpace(os.Getenv("SMTP_PASSWORD")),
			From:     envOrDefault("SMTP_FROM", "noreply@example.test"),
			FromName: envOrDefault("SMTP_FROM_NAME", "Trustcontact"),
		},
	}

	if err := cfg.Validate(); err != nil {
		return nil, err
	}

	return cfg, nil
}

func (c *Config) Validate() error {
	if c.AppName == "" {
		return errors.New("APP_NAME is required")
	}

	switch c.Env {
	case EnvDevelop, EnvProd:
	default:
		return fmt.Errorf("APP_ENV must be one of [%s,%s]", EnvDevelop, EnvProd)
	}

	if strings.TrimSpace(c.Port) == "" {
		return errors.New("APP_PORT is required")
	}

	switch c.DBDriver {
	case DBDriverSQLite:
		if strings.TrimSpace(c.SQLitePath) == "" {
			return errors.New("DB_SQLITE_PATH is required when DB_DRIVER=sqlite")
		}
	case DBDriverPostgres:
		if strings.TrimSpace(c.PostgresDSN) == "" {
			return errors.New("DB_POSTGRES_DSN is required when DB_DRIVER=postgres")
		}
	default:
		return fmt.Errorf("DB_DRIVER must be one of [%s,%s]", DBDriverSQLite, DBDriverPostgres)
	}

	if strings.TrimSpace(c.SessionKey) == "" {
		return errors.New("SESSION_KEY is required")
	}

	if c.SMTP.Port <= 0 {
		return errors.New("SMTP_PORT must be > 0")
	}

	if strings.TrimSpace(c.SMTP.From) == "" {
		return errors.New("SMTP_FROM is required")
	}

	if strings.TrimSpace(c.EmailSinkDir) == "" {
		return errors.New("EMAIL_SINK_DIR is required")
	}

	return nil
}

func envOrDefault(key, fallback string) string {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}
	return value
}

func envBoolOrDefault(key string, fallback bool) bool {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}

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

	return parsed
}

func envIntOrDefault(key string, fallback int) int {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}

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

	return parsed
}

func envListOrDefault(key string, fallback []string) []string {
	value := strings.TrimSpace(os.Getenv(key))
	if value == "" {
		return fallback
	}

	parts := strings.Split(value, ",")
	out := make([]string, 0, len(parts))
	for _, part := range parts {
		clean := strings.TrimSpace(part)
		if clean != "" {
			out = append(out, clean)
		}
	}

	if len(out) == 0 {
		return fallback
	}

	return out
}

func envFirstNonEmpty(keys ...string) string {
	for _, key := range keys {
		value := strings.TrimSpace(os.Getenv(key))
		if value != "" {
			return value
		}
	}
	return ""
}
