package config

import (
	"errors"
	"log/slog"
	"net/url"
	"os"
	"strconv"
	"strings"
)

const (
	DefaultLocalGeneratedStorageDir = "data/static/generated"
	DefaultLocalUploadedStorageDir  = "data/static/uploaded"
)

type Config struct {
	AppEnv                                        string
	HTTPAddr                                      string
	DatabaseURL                                   string
	IdentityMode                                  string
	JWTSecret                                     string
	ServerMainBaseURL                             string
	ServerMainInternalToken                       string
	ServerMainInternalKey                         string
	ServerMainInternalSecret                      string
	IdentitySecretStore                           string
	IdentitySecretDir                             string
	IdentityKubernetesNamespace                   string
	IdentityKubernetesSecretName                  string
	IdentityKubernetesAPIServer                   string
	IdentityKubernetesTokenFile                   string
	IdentityKubernetesCAFile                      string
	IdentitySecurityEventHeartbeatIntervalSeconds int
	IdentitySecurityEventStaleAfterSeconds        int
	IdentitySecurityEventClockSkewSeconds         int
	PublicBaseURL                                 string
	WebBaseURL                                    string
	LocalGeneratedStorageDir                      string
	LocalUploadedStorageDir                       string
	LocalTempAssetTTLHours                        int
	LocalResultTTLHours                           int
	LocalResultMinFreeBytes                       int64
	LocalResultMaxBytes                           int64
	LocalResultMaxTaskBytes                       int64
	TaskProgressCallbackEnabled                   bool
	TaskProgressCallbackURL                       string
	TaskProgressCallbackTimeoutMS                 int
	TaskProgressCallbackMaxAttempts               int
	TaskCleanupEnabled                            bool
	TaskRetentionDays                             int
	TaskAnalysisRetentionDays                     int
	TaskCleanupIntervalSeconds                    int
	TaskCleanupBatchSize                          int
	CORSAllowedOrigin                             string
	GlobalHTTPProxy                               string
	GlobalHTTPProxySource                         string
	LogLevel                                      slog.Level
	BillingEngineMode                             string
	ProcessRole                                   string
	DatabaseMaxConns                              int
	AsyncQueueWorkerEnabled                       bool
	AsyncWorkerHardLimit                          int
	AsyncWorkerRefreshIntervalSeconds             int
}

func Load() Config {
	globalProxy := LoadGlobalHTTPProxyStatus()
	appEnv := env("APP_ENV", "development")
	return Config{
		AppEnv:       appEnv,
		HTTPAddr:     env("HTTP_ADDR", ":8088"),
		DatabaseURL:  gatewayDatabaseURL(),
		IdentityMode: env("IDENTITY_MODE", "hybrid"),
		JWTSecret:    env("CONFIG_JWT_SECRET", "this is a very secret secret"),
		ServerMainBaseURL: strings.TrimRight(
			env("SERVER_MAIN_BASE_URL", "http://localhost:3000"),
			"/",
		),
		ServerMainInternalToken:                       env("SERVER_MAIN_INTERNAL_TOKEN", ""),
		ServerMainInternalKey:                         env("SERVER_MAIN_INTERNAL_KEY", "gateway"),
		ServerMainInternalSecret:                      env("SERVER_MAIN_INTERNAL_SECRET", env("SERVER_MAIN_INTERNAL_TOKEN", "")),
		IdentitySecretStore:                           env("IDENTITY_SECRET_STORE", "file"),
		IdentitySecretDir:                             env("IDENTITY_SECRET_DIR", ".local-secrets/identity"),
		IdentityKubernetesNamespace:                   env("IDENTITY_KUBERNETES_NAMESPACE", env("POD_NAMESPACE", "")),
		IdentityKubernetesSecretName:                  env("IDENTITY_KUBERNETES_SECRET_NAME", "easyai-gateway-identity"),
		IdentityKubernetesAPIServer:                   env("IDENTITY_KUBERNETES_API_SERVER", "https://kubernetes.default.svc"),
		IdentityKubernetesTokenFile:                   env("IDENTITY_KUBERNETES_TOKEN_FILE", "/var/run/secrets/kubernetes.io/serviceaccount/token"),
		IdentityKubernetesCAFile:                      env("IDENTITY_KUBERNETES_CA_FILE", "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"),
		IdentitySecurityEventHeartbeatIntervalSeconds: envInt("IDENTITY_SECURITY_EVENTS_HEARTBEAT_INTERVAL_SECONDS", 60),
		IdentitySecurityEventStaleAfterSeconds:        envInt("IDENTITY_SECURITY_EVENTS_STALE_AFTER_SECONDS", 180),
		IdentitySecurityEventClockSkewSeconds:         envInt("IDENTITY_SECURITY_EVENTS_CLOCK_SKEW_SECONDS", 60),
		PublicBaseURL:                                 strings.TrimRight(env("AI_GATEWAY_PUBLIC_BASE_URL", env("PUBLIC_BASE_URL", "")), "/"),
		WebBaseURL:                                    strings.TrimRight(env("AI_GATEWAY_WEB_BASE_URL", env("GATEWAY_WEB_BASE_URL", env("PUBLIC_WEB_BASE_URL", ""))), "/"),
		LocalGeneratedStorageDir:                      env("AI_GATEWAY_GENERATED_STORAGE_DIR", env("LOCAL_GENERATED_STORAGE_DIR", env("AI_GATEWAY_STATIC_STORAGE_DIR", DefaultLocalGeneratedStorageDir))),
		LocalUploadedStorageDir:                       env("AI_GATEWAY_UPLOADED_STORAGE_DIR", env("LOCAL_UPLOADED_STORAGE_DIR", DefaultLocalUploadedStorageDir)),
		LocalTempAssetTTLHours:                        envInt("AI_GATEWAY_LOCAL_TEMP_ASSET_TTL_HOURS", 24),
		LocalResultTTLHours:                           envIntValidated("AI_GATEWAY_LOCAL_RESULT_TTL_HOURS", 24),
		LocalResultMinFreeBytes:                       envInt64Validated("AI_GATEWAY_LOCAL_RESULT_MIN_FREE_BYTES", 10*1024*1024*1024),
		LocalResultMaxBytes:                           envInt64Validated("AI_GATEWAY_LOCAL_RESULT_MAX_BYTES", 256*1024*1024),
		LocalResultMaxTaskBytes:                       envInt64Validated("AI_GATEWAY_LOCAL_RESULT_MAX_TASK_BYTES", 512*1024*1024),
		TaskProgressCallbackEnabled:                   env("TASK_PROGRESS_CALLBACK_ENABLED", "true") == "true",
		TaskProgressCallbackURL: env("TASK_PROGRESS_CALLBACK_URL",
			strings.TrimRight(env("SERVER_MAIN_BASE_URL", "http://localhost:3000"), "/")+"/internal/platform/task-progress-callbacks",
		),
		TaskProgressCallbackTimeoutMS:     envIntValidated("TASK_PROGRESS_CALLBACK_TIMEOUT_MS", 5000),
		TaskProgressCallbackMaxAttempts:   envIntValidated("TASK_PROGRESS_CALLBACK_MAX_ATTEMPTS", 10),
		TaskCleanupEnabled:                env("AI_GATEWAY_TASK_CLEANUP_ENABLED", "false") == "true",
		TaskRetentionDays:                 envIntValidated("AI_GATEWAY_TASK_RETENTION_DAYS", 30),
		TaskAnalysisRetentionDays:         envIntValidated("AI_GATEWAY_TASK_ANALYSIS_RETENTION_DAYS", 7),
		TaskCleanupIntervalSeconds:        envIntValidated("AI_GATEWAY_TASK_CLEANUP_INTERVAL_SECONDS", 300),
		TaskCleanupBatchSize:              envIntValidated("AI_GATEWAY_TASK_CLEANUP_BATCH_SIZE", 1000),
		CORSAllowedOrigin:                 env("CORS_ALLOWED_ORIGIN", "http://localhost:5178,http://127.0.0.1:5178"),
		GlobalHTTPProxy:                   globalProxy.HTTPProxy,
		GlobalHTTPProxySource:             globalProxy.Source,
		LogLevel:                          logLevel(env("LOG_LEVEL", "info")),
		BillingEngineMode:                 strings.ToLower(env("BILLING_ENGINE_MODE", "observe")),
		ProcessRole:                       strings.ToLower(strings.TrimSpace(env("AI_GATEWAY_PROCESS_ROLE", "all"))),
		DatabaseMaxConns:                  envInt("AI_GATEWAY_DATABASE_MAX_CONNS", 0),
		AsyncQueueWorkerEnabled:           env("AI_GATEWAY_ASYNC_QUEUE_WORKER_ENABLED", "true") == "true",
		AsyncWorkerHardLimit:              envIntValidated("AI_GATEWAY_ASYNC_WORKER_HARD_LIMIT", 2048),
		AsyncWorkerRefreshIntervalSeconds: envIntValidated("AI_GATEWAY_ASYNC_WORKER_REFRESH_INTERVAL_SECONDS", 5),
	}
}

func (c Config) Validate() error {
	switch strings.ToLower(strings.TrimSpace(c.ProcessRole)) {
	case "", "all", "api", "worker":
	default:
		return errors.New("AI_GATEWAY_PROCESS_ROLE must be all, api, or worker")
	}
	if c.DatabaseMaxConns < 0 || c.DatabaseMaxConns > 1000 {
		return errors.New("AI_GATEWAY_DATABASE_MAX_CONNS must be between 1 and 1000 when configured")
	}
	switch strings.ToLower(strings.TrimSpace(c.BillingEngineMode)) {
	case "", "observe", "enforce", "hold":
	default:
		return errors.New("BILLING_ENGINE_MODE must be observe, enforce, or hold")
	}
	if c.AsyncWorkerHardLimit < 1 || c.AsyncWorkerHardLimit > 10000 {
		return errors.New("AI_GATEWAY_ASYNC_WORKER_HARD_LIMIT must be between 1 and 10000")
	}
	if c.AsyncWorkerRefreshIntervalSeconds < 1 {
		return errors.New("AI_GATEWAY_ASYNC_WORKER_REFRESH_INTERVAL_SECONDS must be positive")
	}
	if c.TaskProgressCallbackTimeoutMS != 0 && (c.TaskProgressCallbackTimeoutMS < 100 || c.TaskProgressCallbackTimeoutMS > 60000) {
		return errors.New("TASK_PROGRESS_CALLBACK_TIMEOUT_MS must be between 100 and 60000")
	}
	if c.TaskProgressCallbackMaxAttempts != 0 && (c.TaskProgressCallbackMaxAttempts < 1 || c.TaskProgressCallbackMaxAttempts > 100) {
		return errors.New("TASK_PROGRESS_CALLBACK_MAX_ATTEMPTS must be between 1 and 100")
	}
	if c.TaskRetentionDays != 0 && (c.TaskRetentionDays < 1 || c.TaskRetentionDays > 3650) {
		return errors.New("AI_GATEWAY_TASK_RETENTION_DAYS must be between 1 and 3650")
	}
	if c.TaskAnalysisRetentionDays != 0 && (c.TaskAnalysisRetentionDays < 1 || (c.TaskRetentionDays != 0 && c.TaskAnalysisRetentionDays > c.TaskRetentionDays)) {
		return errors.New("AI_GATEWAY_TASK_ANALYSIS_RETENTION_DAYS must be between 1 and AI_GATEWAY_TASK_RETENTION_DAYS")
	}
	if c.TaskCleanupIntervalSeconds != 0 && c.TaskCleanupIntervalSeconds < 60 {
		return errors.New("AI_GATEWAY_TASK_CLEANUP_INTERVAL_SECONDS must be at least 60")
	}
	if c.TaskCleanupBatchSize != 0 && (c.TaskCleanupBatchSize < 100 || c.TaskCleanupBatchSize > 5000) {
		return errors.New("AI_GATEWAY_TASK_CLEANUP_BATCH_SIZE must be between 100 and 5000")
	}
	if c.LocalResultTTLHours != 0 && (c.LocalResultTTLHours < 1 || c.LocalResultTTLHours > 24*30) {
		return errors.New("AI_GATEWAY_LOCAL_RESULT_TTL_HOURS must be between 1 and 720")
	}
	if c.LocalResultMinFreeBytes < 0 {
		return errors.New("AI_GATEWAY_LOCAL_RESULT_MIN_FREE_BYTES must not be negative")
	}
	if c.LocalResultMaxBytes != 0 && c.LocalResultMaxBytes < 1024 {
		return errors.New("AI_GATEWAY_LOCAL_RESULT_MAX_BYTES must be at least 1024")
	}
	if c.LocalResultMaxTaskBytes != 0 && c.LocalResultMaxTaskBytes < c.LocalResultMaxBytes {
		return errors.New("AI_GATEWAY_LOCAL_RESULT_MAX_TASK_BYTES must be at least AI_GATEWAY_LOCAL_RESULT_MAX_BYTES")
	}
	switch strings.ToLower(strings.TrimSpace(c.IdentitySecretStore)) {
	case "":
	case "file":
		if strings.TrimSpace(c.IdentitySecretDir) == "" {
			return errors.New("IDENTITY_SECRET_DIR is required for the file SecretStore")
		}
	case "kubernetes":
		if strings.TrimSpace(c.IdentityKubernetesNamespace) == "" || strings.TrimSpace(c.IdentityKubernetesSecretName) == "" {
			return errors.New("Kubernetes identity SecretStore requires namespace and Secret name")
		}
	default:
		return errors.New("IDENTITY_SECRET_STORE must be file or kubernetes")
	}
	if c.IdentitySecurityEventHeartbeatIntervalSeconds != 0 || c.IdentitySecurityEventStaleAfterSeconds != 0 || c.IdentitySecurityEventClockSkewSeconds != 0 {
		if c.IdentitySecurityEventHeartbeatIntervalSeconds <= 0 ||
			c.IdentitySecurityEventStaleAfterSeconds < 2*c.IdentitySecurityEventHeartbeatIntervalSeconds ||
			c.IdentitySecurityEventClockSkewSeconds < 0 || c.IdentitySecurityEventClockSkewSeconds > 300 {
			return errors.New("identity security event heartbeat, stale threshold, or clock skew is invalid")
		}
	}
	return nil
}

func (c Config) EffectiveProcessRole() string {
	role := strings.ToLower(strings.TrimSpace(c.ProcessRole))
	if role == "" {
		return "all"
	}
	return role
}

func (c Config) RunsPublicHTTP() bool {
	return c.EffectiveProcessRole() != "worker"
}

func (c Config) RunsAsyncExecutionWorker() bool {
	switch c.EffectiveProcessRole() {
	case "api":
		return false
	case "worker":
		return true
	default:
		return c.AsyncQueueWorkerEnabled
	}
}

func (c Config) RunsBackgroundWorkers() bool {
	return c.EffectiveProcessRole() != "api"
}

type GlobalHTTPProxyStatus struct {
	HTTPProxy string
	Source    string
}

func LoadGlobalHTTPProxyStatus() GlobalHTTPProxyStatus {
	for _, key := range []string{
		"AI_GATEWAY_GLOBAL_HTTP_PROXY",
		"GLOBAL_HTTP_PROXY",
		"HTTPS_PROXY",
		"https_proxy",
		"HTTP_PROXY",
		"http_proxy",
		"ALL_PROXY",
		"all_proxy",
	} {
		if value := envValue(key); value != "" {
			return GlobalHTTPProxyStatus{HTTPProxy: value, Source: key}
		}
	}
	return GlobalHTTPProxyStatus{}
}

func gatewayDatabaseURL() string {
	if value := envValue("AI_GATEWAY_DATABASE_URL"); value != "" {
		return normalizePostgresURL(value)
	}
	if value := envValue("DATABASE_URL"); value != "" {
		return normalizePostgresURL(value)
	}
	if memoryURL := envValue("MEMORY_DATABASE_URL"); memoryURL != "" {
		return normalizePostgresURL(withDatabase(memoryURL, env("AI_GATEWAY_DATABASE_NAME", "easyai_ai_gateway")))
	}
	return normalizePostgresURL("postgresql://easyai:easyai2025@localhost:5432/easyai_ai_gateway?sslmode=disable")
}

func normalizePostgresURL(raw string) string {
	parsed, err := url.Parse(raw)
	if err != nil {
		return raw
	}
	values := parsed.Query()
	schema := values.Get("schema")
	if schema == "" {
		return raw
	}
	values.Del("schema")
	if values.Get("search_path") == "" {
		values.Set("search_path", schema)
	}
	parsed.RawQuery = values.Encode()
	return parsed.String()
}

func withDatabase(raw string, databaseName string) string {
	parsed, err := url.Parse(raw)
	if err != nil || databaseName == "" {
		return raw
	}
	parsed.Path = "/" + databaseName
	return parsed.String()
}

func envValue(key string) string {
	return strings.TrimSpace(os.Getenv(key))
}

func env(key string, fallback string) string {
	if value := envValue(key); value != "" {
		return value
	}
	return fallback
}

func envInt(key string, fallback int) int {
	value := envValue(key)
	if value == "" {
		return fallback
	}
	parsed, err := strconv.Atoi(value)
	if err != nil {
		return fallback
	}
	return parsed
}

func envIntValidated(key string, fallback int) int {
	value := envValue(key)
	if value == "" {
		return fallback
	}
	parsed, err := strconv.Atoi(value)
	if err != nil {
		return 0
	}
	return parsed
}

func envInt64Validated(key string, fallback int64) int64 {
	value := envValue(key)
	if value == "" {
		return fallback
	}
	parsed, err := strconv.ParseInt(value, 10, 64)
	if err != nil {
		return 0
	}
	return parsed
}

func logLevel(value string) slog.Level {
	switch strings.ToLower(value) {
	case "debug":
		return slog.LevelDebug
	case "warn", "warning":
		return slog.LevelWarn
	case "error":
		return slog.LevelError
	default:
		return slog.LevelInfo
	}
}
