package config

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

const (
	EnvListenAddr             = "SUB2API_CRM_LISTEN_ADDR"
	EnvSQLiteDSN              = "SUB2API_CRM_SQLITE_DSN"
	EnvAdminToken             = "SUB2API_CRM_ADMIN_TOKEN"
	EnvAdminUsername          = "SUB2API_CRM_ADMIN_USERNAME"
	EnvAdminPassword          = "SUB2API_CRM_ADMIN_PASSWORD"
	EnvAdminSessionTTL        = "SUB2API_CRM_ADMIN_SESSION_TTL"
	EnvRepoRoot               = "SUB2API_CRM_REPO_ROOT"
	EnvReconcileWorkerEnabled = "SUB2API_CRM_RECONCILE_WORKER_ENABLED"
	EnvReconcilePollInterval  = "SUB2API_CRM_RECONCILE_POLL_INTERVAL"
	EnvRouteRuntimeBackend    = "SUB2API_CRM_ROUTE_RUNTIME_BACKEND"
	EnvRedisAddr              = "SUB2API_CRM_REDIS_ADDR"
	EnvRedisPassword          = "SUB2API_CRM_REDIS_PASSWORD"
	EnvRedisDB                = "SUB2API_CRM_REDIS_DB"

	DefaultListenAddr            = ":8080"
	DefaultSQLiteDSN             = "file:sub2api-cn-relay-manager.db?_foreign_keys=on&_busy_timeout=5000"
	DefaultAdminUsername         = "admin"
	DefaultAdminSessionTTL       = 12 * time.Hour
	DefaultReconcilePollInterval = 10 * time.Minute
	DefaultRouteRuntimeBackend   = "memory"
)

type ServerConfig struct {
	ListenAddr string
}

type DatabaseConfig struct {
	SQLiteDSN string
}

type RedisRuntimeConfig struct {
	Addr     string
	Password string
	DB       int
}

type RouteRuntimeConfig struct {
	Backend string
	Redis   RedisRuntimeConfig
}

type ReconcileConfig struct {
	WorkerEnabled bool
	PollInterval  time.Duration
}

type RepositoryConfig struct {
	RepoRoot string
}

type StartupConfig struct {
	Server       ServerConfig
	Database     DatabaseConfig
	Repository   RepositoryConfig
	RouteRuntime RouteRuntimeConfig
	Reconcile    ReconcileConfig
}

type AdminSessionConfig struct {
	Username   string
	Password   string
	SessionTTL time.Duration
}

func LoadStartupFromEnv() (StartupConfig, error) {
	return loadStartupFromLookupEnv(os.LookupEnv)
}

func loadStartupFromLookupEnv(lookup func(string) (string, bool)) (StartupConfig, error) {
	reconcilePollInterval, err := readOptionalDurationEnv(lookup, EnvReconcilePollInterval, DefaultReconcilePollInterval)
	if err != nil {
		return StartupConfig{}, err
	}
	redisDB, err := readOptionalIntEnv(lookup, EnvRedisDB, 0)
	if err != nil {
		return StartupConfig{}, err
	}
	cfg := StartupConfig{
		Server: ServerConfig{
			ListenAddr: readOptionalEnv(lookup, EnvListenAddr, DefaultListenAddr),
		},
		Database: DatabaseConfig{
			SQLiteDSN: readOptionalEnv(lookup, EnvSQLiteDSN, DefaultSQLiteDSN),
		},
		Repository: RepositoryConfig{
			RepoRoot: readOptionalEnv(lookup, EnvRepoRoot, ""),
		},
		RouteRuntime: RouteRuntimeConfig{
			Backend: readOptionalEnv(lookup, EnvRouteRuntimeBackend, DefaultRouteRuntimeBackend),
			Redis: RedisRuntimeConfig{
				Addr:     readOptionalEnv(lookup, EnvRedisAddr, ""),
				Password: readOptionalEnv(lookup, EnvRedisPassword, ""),
				DB:       redisDB,
			},
		},
		Reconcile: ReconcileConfig{
			WorkerEnabled: readOptionalBoolEnv(lookup, EnvReconcileWorkerEnabled, false),
			PollInterval:  reconcilePollInterval,
		},
	}

	return cfg, nil
}

func LoadAdminTokenFromEnv() (string, error) {
	return loadAdminTokenFromLookupEnv(os.LookupEnv)
}

func LoadAdminSessionFromEnv() (AdminSessionConfig, error) {
	return loadAdminSessionFromLookupEnv(os.LookupEnv)
}

func loadAdminTokenFromLookupEnv(lookup func(string) (string, bool)) (string, error) {
	token := strings.TrimSpace(readRequiredEnv(lookup, EnvAdminToken))
	if token == "" {
		return "", fmt.Errorf("%s is required", EnvAdminToken)
	}

	return token, nil
}

func loadAdminSessionFromLookupEnv(lookup func(string) (string, bool)) (AdminSessionConfig, error) {
	ttl, err := readOptionalDurationEnv(lookup, EnvAdminSessionTTL, DefaultAdminSessionTTL)
	if err != nil {
		return AdminSessionConfig{}, err
	}
	return AdminSessionConfig{
		Username:   readOptionalEnv(lookup, EnvAdminUsername, DefaultAdminUsername),
		Password:   strings.TrimSpace(readOptionalEnv(lookup, EnvAdminPassword, "")),
		SessionTTL: ttl,
	}, nil
}

func readOptionalEnv(lookup func(string) (string, bool), key string, defaultValue string) string {
	value, ok := lookup(key)
	if !ok {
		return defaultValue
	}

	value = strings.TrimSpace(value)
	if value == "" {
		return defaultValue
	}

	return value
}

func readRequiredEnv(lookup func(string) (string, bool), key string) string {
	value, ok := lookup(key)
	if !ok {
		return ""
	}

	return value
}

func readOptionalBoolEnv(lookup func(string) (string, bool), key string, defaultValue bool) bool {
	value, ok := lookup(key)
	if !ok {
		return defaultValue
	}
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "1", "true", "yes", "on":
		return true
	case "0", "false", "no", "off", "":
		return false
	default:
		return defaultValue
	}
}

func readOptionalDurationEnv(lookup func(string) (string, bool), key string, defaultValue time.Duration) (time.Duration, error) {
	value, ok := lookup(key)
	if !ok || strings.TrimSpace(value) == "" {
		return defaultValue, nil
	}
	duration, err := time.ParseDuration(strings.TrimSpace(value))
	if err != nil {
		return 0, fmt.Errorf("%s: parse duration: %w", key, err)
	}
	if duration <= 0 {
		return 0, fmt.Errorf("%s: duration must be positive", key)
	}
	return duration, nil
}

func readOptionalIntEnv(lookup func(string) (string, bool), key string, defaultValue int) (int, error) {
	value, ok := lookup(key)
	if !ok || strings.TrimSpace(value) == "" {
		return defaultValue, nil
	}
	number, err := strconv.Atoi(strings.TrimSpace(value))
	if err != nil {
		return 0, fmt.Errorf("%s: parse int: %w", key, err)
	}
	if number < 0 {
		return 0, fmt.Errorf("%s: value must be >= 0", key)
	}
	return number, nil
}
