package config

import (
	env_utils "databasus-backend/internal/util/env"
	"databasus-backend/internal/util/logger"
	"databasus-backend/internal/util/tools"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/ilyakaznacheev/cleanenv"
	"github.com/joho/godotenv"
)

var log = logger.GetLogger()

const (
	AppModeWeb        = "web"
	AppModeBackground = "background"
)

type EnvVariables struct {
	IsTesting            bool
	EnvMode              env_utils.EnvMode `env:"ENV_MODE"             required:"true"`
	PostgresesInstallDir string            `env:"POSTGRES_INSTALL_DIR"`
	MysqlInstallDir      string            `env:"MYSQL_INSTALL_DIR"`
	MariadbInstallDir    string            `env:"MARIADB_INSTALL_DIR"`
	MongodbInstallDir    string            `env:"MONGODB_INSTALL_DIR"`

	// Internal database
	DatabaseDsn string `env:"DATABASE_DSN"    required:"true"`
	// Internal Valkey
	ValkeyHost     string `env:"VALKEY_HOST"     required:"true"`
	ValkeyPort     string `env:"VALKEY_PORT"     required:"true"`
	ValkeyUsername string `env:"VALKEY_USERNAME" required:"true"`
	ValkeyPassword string `env:"VALKEY_PASSWORD" required:"true"`
	ValkeyIsSsl    bool   `env:"VALKEY_IS_SSL"   required:"true"`

	IsCloud       bool   `env:"IS_CLOUD"`
	TestLocalhost string `env:"TEST_LOCALHOST"`

	ShowDbInstallationVerificationLogs bool `env:"SHOW_DB_INSTALLATION_VERIFICATION_LOGS"`
	IsSkipExternalResourcesTests       bool `env:"IS_SKIP_EXTERNAL_RESOURCES_TESTS"`

	IsManyNodesMode          bool `env:"IS_MANY_NODES_MODE"`
	IsPrimaryNode            bool `env:"IS_PRIMARY_NODE"`
	IsProcessingNode         bool `env:"IS_PROCESSING_NODE"`
	NodeNetworkThroughputMBs int  `env:"NODE_NETWORK_THROUGHPUT_MBPS"`

	DataFolder    string
	TempFolder    string
	SecretKeyPath string

	TestGoogleDriveClientID     string `env:"TEST_GOOGLE_DRIVE_CLIENT_ID"`
	TestGoogleDriveClientSecret string `env:"TEST_GOOGLE_DRIVE_CLIENT_SECRET"`
	TestGoogleDriveTokenJSON    string `env:"TEST_GOOGLE_DRIVE_TOKEN_JSON"`

	TestPostgres12Port string `env:"TEST_POSTGRES_12_PORT"`
	TestPostgres13Port string `env:"TEST_POSTGRES_13_PORT"`
	TestPostgres14Port string `env:"TEST_POSTGRES_14_PORT"`
	TestPostgres15Port string `env:"TEST_POSTGRES_15_PORT"`
	TestPostgres16Port string `env:"TEST_POSTGRES_16_PORT"`
	TestPostgres17Port string `env:"TEST_POSTGRES_17_PORT"`
	TestPostgres18Port string `env:"TEST_POSTGRES_18_PORT"`

	TestMinioPort        string `env:"TEST_MINIO_PORT"`
	TestMinioConsolePort string `env:"TEST_MINIO_CONSOLE_PORT"`

	TestAzuriteBlobPort string `env:"TEST_AZURITE_BLOB_PORT"`

	TestNASPort  string `env:"TEST_NAS_PORT"`
	TestFTPPort  string `env:"TEST_FTP_PORT"`
	TestSFTPPort string `env:"TEST_SFTP_PORT"`

	TestMysql57Port string `env:"TEST_MYSQL_57_PORT"`
	TestMysql80Port string `env:"TEST_MYSQL_80_PORT"`
	TestMysql84Port string `env:"TEST_MYSQL_84_PORT"`
	TestMysql90Port string `env:"TEST_MYSQL_90_PORT"`

	TestMariadb55Port   string `env:"TEST_MARIADB_55_PORT"`
	TestMariadb101Port  string `env:"TEST_MARIADB_101_PORT"`
	TestMariadb102Port  string `env:"TEST_MARIADB_102_PORT"`
	TestMariadb103Port  string `env:"TEST_MARIADB_103_PORT"`
	TestMariadb104Port  string `env:"TEST_MARIADB_104_PORT"`
	TestMariadb105Port  string `env:"TEST_MARIADB_105_PORT"`
	TestMariadb106Port  string `env:"TEST_MARIADB_106_PORT"`
	TestMariadb1011Port string `env:"TEST_MARIADB_1011_PORT"`
	TestMariadb114Port  string `env:"TEST_MARIADB_114_PORT"`
	TestMariadb118Port  string `env:"TEST_MARIADB_118_PORT"`
	TestMariadb120Port  string `env:"TEST_MARIADB_120_PORT"`

	TestMongodb40Port string `env:"TEST_MONGODB_40_PORT"`
	TestMongodb42Port string `env:"TEST_MONGODB_42_PORT"`
	TestMongodb44Port string `env:"TEST_MONGODB_44_PORT"`
	TestMongodb50Port string `env:"TEST_MONGODB_50_PORT"`
	TestMongodb60Port string `env:"TEST_MONGODB_60_PORT"`
	TestMongodb70Port string `env:"TEST_MONGODB_70_PORT"`
	TestMongodb82Port string `env:"TEST_MONGODB_82_PORT"`

	// oauth
	GitHubClientID     string `env:"GITHUB_CLIENT_ID"`
	GitHubClientSecret string `env:"GITHUB_CLIENT_SECRET"`
	GoogleClientID     string `env:"GOOGLE_CLIENT_ID"`
	GoogleClientSecret string `env:"GOOGLE_CLIENT_SECRET"`

	// testing Telegram
	TestTelegramBotToken string `env:"TEST_TELEGRAM_BOT_TOKEN"`
	TestTelegramChatID   string `env:"TEST_TELEGRAM_CHAT_ID"`

	// testing Supabase
	TestSupabaseHost     string `env:"TEST_SUPABASE_HOST"`
	TestSupabasePort     string `env:"TEST_SUPABASE_PORT"`
	TestSupabaseUsername string `env:"TEST_SUPABASE_USERNAME"`
	TestSupabasePassword string `env:"TEST_SUPABASE_PASSWORD"`
	TestSupabaseDatabase string `env:"TEST_SUPABASE_DATABASE"`

	// SMTP configuration (optional)
	SMTPHost     string `env:"SMTP_HOST"`
	SMTPPort     int    `env:"SMTP_PORT"`
	SMTPUser     string `env:"SMTP_USER"`
	SMTPPassword string `env:"SMTP_PASSWORD"`

	// Application URL (optional) - used for email links
	DatabasusURL string `env:"DATABASUS_URL"`
}

var (
	env  EnvVariables
	once sync.Once
)

func GetEnv() EnvVariables {
	once.Do(loadEnvVariables)
	return env
}

func loadEnvVariables() {
	// Get current working directory
	cwd, err := os.Getwd()
	if err != nil {
		log.Warn("could not get current working directory", "error", err)
		cwd = "."
	}

	backendRoot := cwd
	for {
		if _, err := os.Stat(filepath.Join(backendRoot, "go.mod")); err == nil {
			break
		}

		parent := filepath.Dir(backendRoot)
		if parent == backendRoot {
			break
		}

		backendRoot = parent
	}

	envPaths := []string{
		filepath.Join(cwd, ".env"),
		filepath.Join(backendRoot, ".env"),
	}

	var loaded bool
	for _, path := range envPaths {
		log.Info("Trying to load .env", "path", path)
		if err := godotenv.Load(path); err == nil {
			log.Info("Successfully loaded .env", "path", path)
			loaded = true
			break
		}
	}

	if !loaded {
		log.Error("Error loading .env file: could not find .env in any location")
		os.Exit(1)
	}

	err = cleanenv.ReadEnv(&env)
	if err != nil {
		log.Error("Configuration could not be loaded", "error", err)
		os.Exit(1)
	}

	// Set default value for ShowDbInstallationVerificationLogs if not defined
	if os.Getenv("SHOW_DB_INSTALLATION_VERIFICATION_LOGS") == "" {
		env.ShowDbInstallationVerificationLogs = true
	}

	// Set default value for IsSkipExternalTests if not defined
	if os.Getenv("IS_SKIP_EXTERNAL_RESOURCES_TESTS") == "" {
		env.IsSkipExternalResourcesTests = false
	}

	// Set default value for IsCloud if not defined
	if os.Getenv("IS_CLOUD") == "" {
		env.IsCloud = false
	}

	for _, arg := range os.Args {
		if strings.Contains(arg, "test") {
			env.IsTesting = true
			break
		}
	}

	// Check for external database override
	if externalDsn := os.Getenv("DANGEROUS_EXTERNAL_DATABASE_DSN"); externalDsn != "" {
		log.Warn(
			"Using DANGEROUS_EXTERNAL_DATABASE_DSN - connecting to external database instead of internal PostgreSQL",
		)
		env.DatabaseDsn = externalDsn
	}

	if env.DatabaseDsn == "" {
		log.Error("DATABASE_DSN is empty")
		os.Exit(1)
	}

	if env.EnvMode == "" {
		log.Error("ENV_MODE is empty")
		os.Exit(1)
	}
	if env.EnvMode != "development" && env.EnvMode != "production" {
		log.Error("ENV_MODE is invalid", "mode", env.EnvMode)
		os.Exit(1)
	}
	log.Info("ENV_MODE loaded", "mode", env.EnvMode)

	env.PostgresesInstallDir = filepath.Join(backendRoot, "tools", "postgresql")
	tools.VerifyPostgresesInstallation(
		log,
		env.EnvMode,
		env.PostgresesInstallDir,
		env.ShowDbInstallationVerificationLogs,
	)

	env.MysqlInstallDir = filepath.Join(backendRoot, "tools", "mysql")
	tools.VerifyMysqlInstallation(
		log,
		env.EnvMode,
		env.MysqlInstallDir,
		env.ShowDbInstallationVerificationLogs,
	)

	env.MariadbInstallDir = filepath.Join(backendRoot, "tools", "mariadb")
	tools.VerifyMariadbInstallation(
		log,
		env.EnvMode,
		env.MariadbInstallDir,
		env.ShowDbInstallationVerificationLogs,
	)

	env.MongodbInstallDir = filepath.Join(backendRoot, "tools", "mongodb")
	tools.VerifyMongodbInstallation(
		log,
		env.EnvMode,
		env.MongodbInstallDir,
		env.ShowDbInstallationVerificationLogs,
	)

	if env.NodeNetworkThroughputMBs == 0 {
		env.NodeNetworkThroughputMBs = 125 // 1 Gbit/s
	}

	if !env.IsManyNodesMode {
		env.IsPrimaryNode = true
		env.IsProcessingNode = true
	}

	if env.TestLocalhost == "" {
		env.TestLocalhost = "localhost"
	}

	// Valkey
	if env.ValkeyHost == "" {
		log.Error("VALKEY_HOST is empty")
		os.Exit(1)
	}
	if env.ValkeyPort == "" {
		log.Error("VALKEY_PORT is empty")
		os.Exit(1)
	}

	// Check for external Valkey override
	if externalValkeyHost := os.Getenv("DANGEROUS_VALKEY_HOST"); externalValkeyHost != "" {
		log.Warn(
			"Using DANGEROUS_VALKEY_* variables - connecting to external Valkey instead of internal instance",
		)
		env.ValkeyHost = externalValkeyHost

		if externalValkeyPort := os.Getenv("DANGEROUS_VALKEY_PORT"); externalValkeyPort != "" {
			env.ValkeyPort = externalValkeyPort
		}
		if externalValkeyUsername := os.Getenv("DANGEROUS_VALKEY_USERNAME"); externalValkeyUsername != "" {
			env.ValkeyUsername = externalValkeyUsername
		}
		if externalValkeyPassword := os.Getenv("DANGEROUS_VALKEY_PASSWORD"); externalValkeyPassword != "" {
			env.ValkeyPassword = externalValkeyPassword
		}
		if externalValkeyIsSsl := os.Getenv("DANGEROUS_VALKEY_IS_SSL"); externalValkeyIsSsl != "" {
			env.ValkeyIsSsl = externalValkeyIsSsl == "true"
		}
	}

	// Store the data and temp folders one level below the root
	// (projectRoot/databasus-data -> /databasus-data)
	env.DataFolder = filepath.Join(filepath.Dir(backendRoot), "databasus-data", "backups")
	env.TempFolder = filepath.Join(filepath.Dir(backendRoot), "databasus-data", "temp")
	env.SecretKeyPath = filepath.Join(filepath.Dir(backendRoot), "databasus-data", "secret.key")

	if env.IsTesting {
		if env.TestPostgres12Port == "" {
			log.Error("TEST_POSTGRES_12_PORT is empty")
			os.Exit(1)
		}
		if env.TestPostgres13Port == "" {
			log.Error("TEST_POSTGRES_13_PORT is empty")
			os.Exit(1)
		}
		if env.TestPostgres14Port == "" {
			log.Error("TEST_POSTGRES_14_PORT is empty")
			os.Exit(1)
		}
		if env.TestPostgres15Port == "" {
			log.Error("TEST_POSTGRES_15_PORT is empty")
			os.Exit(1)
		}
		if env.TestPostgres16Port == "" {
			log.Error("TEST_POSTGRES_16_PORT is empty")
			os.Exit(1)
		}
		if env.TestPostgres17Port == "" {
			log.Error("TEST_POSTGRES_17_PORT is empty")
			os.Exit(1)
		}
		if env.TestPostgres18Port == "" {
			log.Error("TEST_POSTGRES_18_PORT is empty")
			os.Exit(1)
		}

		if env.TestMinioPort == "" {
			log.Error("TEST_MINIO_PORT is empty")
			os.Exit(1)
		}
		if env.TestMinioConsolePort == "" {
			log.Error("TEST_MINIO_CONSOLE_PORT is empty")
			os.Exit(1)
		}

		if env.TestAzuriteBlobPort == "" {
			log.Error("TEST_AZURITE_BLOB_PORT is empty")
			os.Exit(1)
		}

		if env.TestNASPort == "" {
			log.Error("TEST_NAS_PORT is empty")
			os.Exit(1)
		}

		if env.TestTelegramBotToken == "" {
			log.Error("TEST_TELEGRAM_BOT_TOKEN is empty")
			os.Exit(1)
		}

		if env.TestTelegramChatID == "" {
			log.Error("TEST_TELEGRAM_CHAT_ID is empty")
			os.Exit(1)
		}
	}

	log.Info("Environment variables loaded successfully!")
}
