package common

import (
	"errors"
	"fmt"
	"log"
	"net/url"

	"github.com/caarlos0/env/v11"
	_ "github.com/joho/godotenv/autoload"
)

type DbProvider string

const (
	// TracerName should be passed to otel.Tracer, trace.SpanFromContext when creating custom spans.
	TracerName = "github.com/pocket-id/pocket-id/backend/tracing"
	// MeterName should be passed to otel.Meter when create custom metrics.
	MeterName = "github.com/pocket-id/pocket-id/backend/metrics"
)

const (
	DbProviderSqlite        DbProvider = "sqlite"
	DbProviderPostgres      DbProvider = "postgres"
	MaxMindGeoLiteCityUrl   string     = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz"
	defaultSqliteConnString string     = "file:data/pocket-id.db?_pragma=journal_mode(WAL)&_pragma=busy_timeout(2500)&_txlock=immediate"
)

type EnvConfigSchema struct {
	AppEnv             string     `env:"APP_ENV"`
	AppURL             string     `env:"APP_URL"`
	DbProvider         DbProvider `env:"DB_PROVIDER"`
	DbConnectionString string     `env:"DB_CONNECTION_STRING"`
	UploadPath         string     `env:"UPLOAD_PATH"`
	KeysPath           string     `env:"KEYS_PATH"`
	KeysStorage        string     `env:"KEYS_STORAGE"`
	EncryptionKey      string     `env:"ENCRYPTION_KEY"`
	EncryptionKeyFile  string     `env:"ENCRYPTION_KEY_FILE"`
	Port               string     `env:"PORT"`
	Host               string     `env:"HOST"`
	UnixSocket         string     `env:"UNIX_SOCKET"`
	UnixSocketMode     string     `env:"UNIX_SOCKET_MODE"`
	MaxMindLicenseKey  string     `env:"MAXMIND_LICENSE_KEY"`
	GeoLiteDBPath      string     `env:"GEOLITE_DB_PATH"`
	GeoLiteDBUrl       string     `env:"GEOLITE_DB_URL"`
	LocalIPv6Ranges    string     `env:"LOCAL_IPV6_RANGES"`
	UiConfigDisabled   bool       `env:"UI_CONFIG_DISABLED"`
	MetricsEnabled     bool       `env:"METRICS_ENABLED"`
	TracingEnabled     bool       `env:"TRACING_ENABLED"`
	TrustProxy         bool       `env:"TRUST_PROXY"`
	AnalyticsDisabled  bool       `env:"ANALYTICS_DISABLED"`
}

var EnvConfig = defaultConfig()

func init() {
	err := parseEnvConfig()
	if err != nil {
		log.Fatalf("Configuration error: %v", err)
	}
}

func defaultConfig() EnvConfigSchema {
	return EnvConfigSchema{
		AppEnv:             "production",
		DbProvider:         "sqlite",
		DbConnectionString: "",
		UploadPath:         "data/uploads",
		KeysPath:           "data/keys",
		KeysStorage:        "", // "database" or "file"
		EncryptionKey:      "",
		AppURL:             "http://localhost:1411",
		Port:               "1411",
		Host:               "0.0.0.0",
		UnixSocket:         "",
		UnixSocketMode:     "",
		MaxMindLicenseKey:  "",
		GeoLiteDBPath:      "data/GeoLite2-City.mmdb",
		GeoLiteDBUrl:       MaxMindGeoLiteCityUrl,
		LocalIPv6Ranges:    "",
		UiConfigDisabled:   false,
		MetricsEnabled:     false,
		TracingEnabled:     false,
		TrustProxy:         false,
		AnalyticsDisabled:  false,
	}
}

func parseEnvConfig() error {
	err := env.ParseWithOptions(&EnvConfig, env.Options{})
	if err != nil {
		return fmt.Errorf("error parsing env config: %w", err)
	}

	// Validate the environment variables
	switch EnvConfig.DbProvider {
	case DbProviderSqlite:
		if EnvConfig.DbConnectionString == "" {
			EnvConfig.DbConnectionString = defaultSqliteConnString
		}
	case DbProviderPostgres:
		if EnvConfig.DbConnectionString == "" {
			return errors.New("missing required env var 'DB_CONNECTION_STRING' for Postgres database")
		}
	default:
		return errors.New("invalid DB_PROVIDER value. Must be 'sqlite' or 'postgres'")
	}

	parsedAppUrl, err := url.Parse(EnvConfig.AppURL)
	if err != nil {
		return errors.New("APP_URL is not a valid URL")
	}
	if parsedAppUrl.Path != "" {
		return errors.New("APP_URL must not contain a path")
	}

	switch EnvConfig.KeysStorage {
	// KeysStorage defaults to "file" if empty
	case "":
		EnvConfig.KeysStorage = "file"
	case "database":
		// If KeysStorage is "database", a key must be specified
		if EnvConfig.EncryptionKey == "" && EnvConfig.EncryptionKeyFile == "" {
			return errors.New("ENCRYPTION_KEY or ENCRYPTION_KEY_FILE must be non-empty when KEYS_STORAGE is database")
		}
	case "file":
		// All good, these are valid values
	default:
		return fmt.Errorf("invalid value for KEYS_STORAGE: %s", EnvConfig.KeysStorage)
	}

	return nil
}
