package config

import (
	"fmt"
	"os"
	"slices"
	"strconv"
	"strings"
	"time"

	"github.com/joho/godotenv"
)

type Config struct {
	Env      string
	AppName  string
	HTTP     HTTPConfig
	CORS     CORSConfig
	Postgres PostgresConfig
	Cache    CacheConfig
	Auth     AuthConfig
	TMDB     TMDBConfig
	IGDB     IGDBConfig
}

type HTTPConfig struct {
	Host         string
	Port         int
	ReadTimeout  time.Duration
	WriteTimeout time.Duration
}

type CORSConfig struct {
	AllowedOrigins   []string
	AllowedMethods   []string
	AllowedHeaders   []string
	ExposedHeaders   []string
	AllowCredentials bool
	MaxAge           time.Duration
}

type PostgresConfig struct {
	URL      string
	MaxConns int32
	MinConns int32
}

type CacheConfig struct {
	Addr     string
	Password string
	DB       int
}

type AuthConfig struct {
	AccessTokenTTLMinutes int
	RefreshTokenTTLHours  int
	JWTSecret             string
}

type TMDBConfig struct {
	APIKey  string
	BaseURL string
}

type IGDBConfig struct {
	ClientID     string
	ClientSecret string
	BaseURL      string
	TokenURL     string
}

func Load() Config {
	_ = godotenv.Load()

	return Config{
		Env:     getString("SEEN_ENV", "development"),
		AppName: getString("SEEN_APP_NAME", "seen"),
		HTTP: HTTPConfig{
			Host:         getString("SEEN_HTTP_HOST", "0.0.0.0"),
			Port:         getInt("SEEN_HTTP_PORT", 8081),
			ReadTimeout:  getDuration("SEEN_HTTP_READ_TIMEOUT", 15*time.Second),
			WriteTimeout: getDuration("SEEN_HTTP_WRITE_TIMEOUT", 15*time.Second),
		},
		CORS: CORSConfig{
			AllowedOrigins:   getCSV("SEEN_CORS_ALLOWED_ORIGINS", []string{}),
			AllowedMethods:   getCSV("SEEN_CORS_ALLOWED_METHODS", []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}),
			AllowedHeaders:   getCSV("SEEN_CORS_ALLOWED_HEADERS", []string{"Accept", "Authorization", "Content-Type", "X-Request-ID"}),
			ExposedHeaders:   getCSV("SEEN_CORS_EXPOSED_HEADERS", []string{"X-Request-ID"}),
			AllowCredentials: getBool("SEEN_CORS_ALLOW_CREDENTIALS", false),
			MaxAge:           getDuration("SEEN_CORS_MAX_AGE", 24*time.Hour),
		},
		Postgres: PostgresConfig{
			URL:      getString("SEEN_POSTGRES_URL", "postgres://seen:seen@localhost:5432/seen?sslmode=disable"),
			MaxConns: int32(getInt("SEEN_POSTGRES_MAX_CONNS", 10)),
			MinConns: int32(getInt("SEEN_POSTGRES_MIN_CONNS", 2)),
		},
		Cache: CacheConfig{
			Addr:     getString("SEEN_CACHE_ADDR", "localhost:6379"),
			Password: getString("SEEN_CACHE_PASSWORD", ""),
			DB:       getInt("SEEN_CACHE_DB", 0),
		},
		Auth: AuthConfig{
			AccessTokenTTLMinutes: getInt("SEEN_AUTH_ACCESS_TOKEN_TTL_MINUTES", 30),
			RefreshTokenTTLHours:  getInt("SEEN_AUTH_REFRESH_TOKEN_TTL_HOURS", 24*30),
			JWTSecret:             getString("SEEN_AUTH_JWT_SECRET", "replace-in-production"),
		},
		TMDB: TMDBConfig{
			APIKey:  getString("SEEN_TMDB_API_KEY", ""),
			BaseURL: getString("SEEN_TMDB_BASE_URL", "https://api.themoviedb.org/3"),
		},
		IGDB: IGDBConfig{
			ClientID:     getString("SEEN_IGDB_CLIENT_ID", ""),
			ClientSecret: getString("SEEN_IGDB_CLIENT_SECRET", ""),
			BaseURL:      getString("SEEN_IGDB_BASE_URL", "https://api.igdb.com/v4"),
			TokenURL:     getString("SEEN_IGDB_TOKEN_URL", "https://id.twitch.tv/oauth2/token"),
		},
	}
}

func (c HTTPConfig) Addr() string {
	return fmt.Sprintf("%s:%d", c.Host, c.Port)
}

func getString(key string, fallback string) string {
	if value, ok := os.LookupEnv(key); ok {
		return value
	}
	return fallback
}

func getInt(key string, fallback int) int {
	value, ok := os.LookupEnv(key)
	if !ok {
		return fallback
	}

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

func getDuration(key string, fallback time.Duration) time.Duration {
	value, ok := os.LookupEnv(key)
	if !ok {
		return fallback
	}

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

func getBool(key string, fallback bool) bool {
	value, ok := os.LookupEnv(key)
	if !ok {
		return fallback
	}

	switch strings.ToLower(strings.TrimSpace(value)) {
	case "1", "true", "yes", "on":
		return true
	case "0", "false", "no", "off":
		return false
	default:
		return fallback
	}
}

func getCSV(key string, fallback []string) []string {
	value, ok := os.LookupEnv(key)
	if !ok {
		return slices.Clone(fallback)
	}

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

	if len(items) == 0 {
		return slices.Clone(fallback)
	}

	return items
}
