package config

import (
	"testing"
	"time"
)

func TestLoadFileManagerUploadPolicyConfigFromEnv_Defaults(t *testing.T) {
	t.Setenv("FILE_MANAGER_UPLOAD_MAX_FILE_SIZE_BYTES", "")
	t.Setenv("FILE_MANAGER_UPLOAD_ALLOWED_MIME_TYPES", "")
	t.Setenv("FILE_MANAGER_UPLOAD_ALLOWED_EXTENSIONS", "")
	t.Setenv("FILE_MANAGER_UPLOAD_ALLOW_EMPTY_FILE", "")
	t.Setenv("FILE_MANAGER_UPLOAD_MAX_FILENAME_LENGTH", "")

	cfg := LoadFileManagerUploadPolicyConfigFromEnv()

	if cfg.MaxFileSizeBytes != 4*1024*1024 {
		t.Fatalf("expected default max file size 4194304, got %d", cfg.MaxFileSizeBytes)
	}
	if cfg.AllowEmptyFile {
		t.Fatalf("expected default allow empty file false")
	}
	if cfg.MaxFilenameLength != 255 {
		t.Fatalf("expected default max filename length 255, got %d", cfg.MaxFilenameLength)
	}
	if len(cfg.AllowedMimeTypes) != 0 {
		t.Fatalf("expected empty default allowed mime types, got %v", cfg.AllowedMimeTypes)
	}
	if len(cfg.AllowedExtensions) != 0 {
		t.Fatalf("expected empty default allowed extensions, got %v", cfg.AllowedExtensions)
	}
}

func TestLoadFileManagerUploadPolicyConfigFromEnv_Normalize(t *testing.T) {
	t.Setenv("FILE_MANAGER_UPLOAD_MAX_FILE_SIZE_BYTES", "1048576")
	t.Setenv("FILE_MANAGER_UPLOAD_ALLOWED_MIME_TYPES", " image/PNG ; charset=utf-8,application/pdf,IMAGE/PNG")
	t.Setenv("FILE_MANAGER_UPLOAD_ALLOWED_EXTENSIONS", " .PDF, png ,PNG")
	t.Setenv("FILE_MANAGER_UPLOAD_ALLOW_EMPTY_FILE", "true")
	t.Setenv("FILE_MANAGER_UPLOAD_MAX_FILENAME_LENGTH", "120")

	cfg := LoadFileManagerUploadPolicyConfigFromEnv()

	if cfg.MaxFileSizeBytes != 1048576 {
		t.Fatalf("expected max file size 1048576, got %d", cfg.MaxFileSizeBytes)
	}
	if !cfg.AllowEmptyFile {
		t.Fatalf("expected allow empty file true")
	}
	if cfg.MaxFilenameLength != 120 {
		t.Fatalf("expected max filename length 120, got %d", cfg.MaxFilenameLength)
	}

	if len(cfg.AllowedMimeTypes) != 2 || cfg.AllowedMimeTypes[0] != "image/png" || cfg.AllowedMimeTypes[1] != "application/pdf" {
		t.Fatalf("unexpected normalized mime types: %v", cfg.AllowedMimeTypes)
	}
	if len(cfg.AllowedExtensions) != 2 || cfg.AllowedExtensions[0] != "pdf" || cfg.AllowedExtensions[1] != "png" {
		t.Fatalf("unexpected normalized extensions: %v", cfg.AllowedExtensions)
	}
}

func TestLoadFileManagerTrashPolicyConfigFromEnv_Defaults(t *testing.T) {
	t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_ENABLED", "")
	t.Setenv("FILE_MANAGER_TRASH_RETENTION", "")
	t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_INTERVAL", "")
	t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_BATCH_SIZE", "")

	cfg := LoadFileManagerTrashPolicyConfigFromEnv()
	if !cfg.AutoPurgeEnabled {
		t.Fatalf("expected default auto purge enabled")
	}
	if cfg.Retention != 30*24*time.Hour {
		t.Fatalf("expected default retention 720h, got %s", cfg.Retention)
	}
	if cfg.AutoPurgeInterval != time.Hour {
		t.Fatalf("expected default auto purge interval 1h, got %s", cfg.AutoPurgeInterval)
	}
	if cfg.AutoPurgeBatchSize != 200 {
		t.Fatalf("expected default batch size 200, got %d", cfg.AutoPurgeBatchSize)
	}
}

func TestLoadFileManagerTrashPolicyConfigFromEnv_Custom(t *testing.T) {
	t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_ENABLED", "false")
	t.Setenv("FILE_MANAGER_TRASH_RETENTION", "168h")
	t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_INTERVAL", "15m")
	t.Setenv("FILE_MANAGER_TRASH_AUTO_PURGE_BATCH_SIZE", "50")

	cfg := LoadFileManagerTrashPolicyConfigFromEnv()
	if cfg.AutoPurgeEnabled {
		t.Fatalf("expected auto purge disabled")
	}
	if cfg.Retention != 168*time.Hour {
		t.Fatalf("expected retention 168h, got %s", cfg.Retention)
	}
	if cfg.AutoPurgeInterval != 15*time.Minute {
		t.Fatalf("expected auto purge interval 15m, got %s", cfg.AutoPurgeInterval)
	}
	if cfg.AutoPurgeBatchSize != 50 {
		t.Fatalf("expected batch size 50, got %d", cfg.AutoPurgeBatchSize)
	}
}

func TestLoadFileManagerLifecycleConfigFromEnv_Defaults(t *testing.T) {
	t.Setenv("ENABLE_S3_TAGGING", "")
	t.Setenv("ENABLE_FILE_CLEANUP_JOB", "")
	t.Setenv("TEMP_UPLOAD_GRACE_HOURS", "")
	t.Setenv("ORPHAN_GRACE_PERIOD_DAYS", "")
	t.Setenv("FILE_CLEANUP_BATCH_SIZE", "")
	t.Setenv("FILE_CLEANUP_DRY_RUN", "")

	cfg := LoadFileManagerLifecycleConfigFromEnv()
	if !cfg.EnableS3Tagging {
		t.Fatalf("expected s3 tagging enabled by default")
	}
	if cfg.EnableCleanupJob {
		t.Fatalf("expected cleanup job disabled by default")
	}
	if cfg.TempUploadGraceHours != 2 {
		t.Fatalf("expected temp upload grace 2 hours, got %d", cfg.TempUploadGraceHours)
	}
	if cfg.OrphanGraceDays != 30 {
		t.Fatalf("expected orphan grace 30 days, got %d", cfg.OrphanGraceDays)
	}
	if cfg.CleanupBatchSize != 100 {
		t.Fatalf("expected cleanup batch size 100, got %d", cfg.CleanupBatchSize)
	}
	if !cfg.CleanupDryRun {
		t.Fatalf("expected cleanup dry run enabled by default")
	}
}

func TestLoadFileManagerLifecycleConfigFromEnv_Custom(t *testing.T) {
	t.Setenv("ENABLE_S3_TAGGING", "false")
	t.Setenv("ENABLE_FILE_CLEANUP_JOB", "true")
	t.Setenv("TEMP_UPLOAD_GRACE_HOURS", "6")
	t.Setenv("ORPHAN_GRACE_PERIOD_DAYS", "45")
	t.Setenv("FILE_CLEANUP_BATCH_SIZE", "250")
	t.Setenv("FILE_CLEANUP_DRY_RUN", "false")

	cfg := LoadFileManagerLifecycleConfigFromEnv()
	if cfg.EnableS3Tagging {
		t.Fatalf("expected s3 tagging disabled")
	}
	if !cfg.EnableCleanupJob {
		t.Fatalf("expected cleanup job enabled")
	}
	if cfg.TempUploadGraceHours != 6 {
		t.Fatalf("expected temp upload grace 6 hours, got %d", cfg.TempUploadGraceHours)
	}
	if cfg.OrphanGraceDays != 45 {
		t.Fatalf("expected orphan grace 45 days, got %d", cfg.OrphanGraceDays)
	}
	if cfg.CleanupBatchSize != 250 {
		t.Fatalf("expected cleanup batch size 250, got %d", cfg.CleanupBatchSize)
	}
	if cfg.CleanupDryRun {
		t.Fatalf("expected cleanup dry run disabled")
	}
}

func TestLoadFileQueueConfigFromEnv_Defaults(t *testing.T) {
	t.Setenv("FILE_QUEUE_MESSAGE_SCHEMA_VERSION", "")
	t.Setenv("FILE_QUEUE_IDEMPOTENCY_KEY_PREFIX", "")
	t.Setenv("FILE_QUEUE_WORKER_MONITOR_ADDR", "")
	t.Setenv("FILE_QUEUE_OUTBOX_ENABLED", "")
	t.Setenv("FILE_QUEUE_OUTBOX_POLL_INTERVAL", "")
	t.Setenv("FILE_QUEUE_OUTBOX_BATCH_SIZE", "")
	t.Setenv("FILE_QUEUE_OUTBOX_DISPATCH_TIMEOUT", "")
	t.Setenv("FILE_QUEUE_OUTBOX_LOCK_TTL", "")
	t.Setenv("FILE_QUEUE_OUTBOX_MAX_ATTEMPTS", "")

	cfg := LoadFileQueueConfigFromEnv()
	if cfg.MessageSchemaVersion != "v1" {
		t.Fatalf("expected default schema version v1, got %q", cfg.MessageSchemaVersion)
	}
	if cfg.IdempotencyKeyPrefix != "queue:idempotency:file:" {
		t.Fatalf("unexpected default idempotency prefix: %q", cfg.IdempotencyKeyPrefix)
	}
	if cfg.WorkerMonitorAddr != "127.0.0.1:9091" {
		t.Fatalf("unexpected default monitor addr: %q", cfg.WorkerMonitorAddr)
	}
	if !cfg.OutboxEnabled {
		t.Fatalf("expected default file outbox enabled")
	}
	if cfg.OutboxPollInterval != time.Second {
		t.Fatalf("expected default outbox poll interval 1s, got %s", cfg.OutboxPollInterval)
	}
	if cfg.OutboxBatchSize != 10 {
		t.Fatalf("expected default outbox batch size 10, got %d", cfg.OutboxBatchSize)
	}
	if cfg.OutboxDispatchTimeout != 10*time.Second {
		t.Fatalf("expected default outbox dispatch timeout 10s, got %s", cfg.OutboxDispatchTimeout)
	}
	if cfg.OutboxLockTTL != time.Minute {
		t.Fatalf("expected default outbox lock ttl 1m, got %s", cfg.OutboxLockTTL)
	}
	if cfg.OutboxMaxAttempts != 20 {
		t.Fatalf("expected default outbox max attempts 20, got %d", cfg.OutboxMaxAttempts)
	}
}

func TestLoadFileSQSConfigFromEnv(t *testing.T) {
	t.Setenv("AWS_REGION", "ap-southeast-1")
	t.Setenv("SQS_ENDPOINT", "http://localhost:4566")
	t.Setenv("SQS_ALLOW_INSECURE_ENDPOINT", "true")
	t.Setenv("FILE_SQS_REGION", "")
	t.Setenv("FILE_SQS_ENDPOINT", "")
	t.Setenv("FILE_SQS_QUEUE_URL", "http://localhost:4566/000000000000/app-file-queue")
	t.Setenv("FILE_SQS_QUEUE_TYPE", "standard")
	t.Setenv("FILE_SQS_MESSAGE_GROUP_ID", "file")
	t.Setenv("FILE_SQS_MAX_MESSAGES", "8")
	t.Setenv("FILE_SQS_WAIT_TIME_SECONDS", "15")
	t.Setenv("FILE_SQS_VISIBILITY_TIMEOUT", "90s")
	t.Setenv("FILE_SQS_ALLOW_INSECURE_ENDPOINT", "")

	cfg := LoadFileSQSConfigFromEnv()
	if cfg.Region != "ap-southeast-1" {
		t.Fatalf("unexpected region: %q", cfg.Region)
	}
	if cfg.Endpoint != "http://localhost:4566" {
		t.Fatalf("unexpected endpoint: %q", cfg.Endpoint)
	}
	if cfg.QueueURL == "" {
		t.Fatalf("expected queue url")
	}
	if cfg.MaxMessages != 8 {
		t.Fatalf("unexpected max messages: %d", cfg.MaxMessages)
	}
	if cfg.WaitTimeSeconds != 15 {
		t.Fatalf("unexpected wait time seconds: %d", cfg.WaitTimeSeconds)
	}
	if !cfg.AllowInsecureEndpoint {
		t.Fatalf("expected insecure endpoint allowed from fallback")
	}
}

func TestValidateDistinctQueueURLs(t *testing.T) {
	t.Run("different queue urls", func(t *testing.T) {
		err := ValidateDistinctQueueURLs(
			SQSConfig{QueueURL: "http://localhost:4566/000000000000/app-queue"},
			SQSConfig{QueueURL: "http://localhost:4566/000000000000/app-file-queue"},
		)
		if err != nil {
			t.Fatalf("expected no error, got %v", err)
		}
	})

	t.Run("same queue urls with different case and slash", func(t *testing.T) {
		err := ValidateDistinctQueueURLs(
			SQSConfig{QueueURL: "HTTP://LOCALHOST:4566/000000000000/app-queue"},
			SQSConfig{QueueURL: "http://localhost:4566/000000000000/app-queue/"},
		)
		if err == nil {
			t.Fatalf("expected error for same queue urls")
		}
	})

	t.Run("skip when one queue url empty", func(t *testing.T) {
		err := ValidateDistinctQueueURLs(
			SQSConfig{QueueURL: "http://localhost:4566/000000000000/app-queue"},
			SQSConfig{QueueURL: ""},
		)
		if err != nil {
			t.Fatalf("expected no error when file queue url is empty, got %v", err)
		}
	})
}

func TestParseHTTPTimeoutOverrides(t *testing.T) {
	t.Run("invalid format ignored", func(t *testing.T) {
		parsed := parseHTTPTimeoutOverrides("GET /users/:id 100ms,foo")
		if len(parsed) != 0 {
			t.Fatalf("expected no parsed entries, got %v", parsed)
		}
	})

	t.Run("invalid duration ignored", func(t *testing.T) {
		parsed := parseHTTPTimeoutOverrides("GET /users/:id=not-a-duration")
		if len(parsed) != 0 {
			t.Fatalf("expected no parsed entries, got %v", parsed)
		}
	})

	t.Run("zero and negative duration ignored", func(t *testing.T) {
		parsed := parseHTTPTimeoutOverrides("GET /users/:id=0s,POST /users=-1s")
		if len(parsed) != 0 {
			t.Fatalf("expected no parsed entries, got %v", parsed)
		}
	})

	t.Run("valid entries parsed", func(t *testing.T) {
		parsed := parseHTTPTimeoutOverrides("GET /users/:id=120ms, POST /auth/login = 2s")
		if len(parsed) != 2 {
			t.Fatalf("expected 2 parsed entries, got %d", len(parsed))
		}
		if parsed["GET /users/:id"] != 120*time.Millisecond {
			t.Fatalf("unexpected GET /users/:id timeout: %s", parsed["GET /users/:id"])
		}
		if parsed["POST /auth/login"] != 2*time.Second {
			t.Fatalf("unexpected POST /auth/login timeout: %s", parsed["POST /auth/login"])
		}
	})
}

func TestParseHTTPEndpointRateLimitOverrides(t *testing.T) {
	t.Run("invalid entries ignored", func(t *testing.T) {
		parsed := parseHTTPEndpointRateLimitOverrides("POST /auth/login=abc,1m;foo;GET /x=1")
		if len(parsed) != 0 {
			t.Fatalf("expected no parsed entries, got %v", parsed)
		}
	})

	t.Run("zero or negative values ignored", func(t *testing.T) {
		parsed := parseHTTPEndpointRateLimitOverrides("POST /auth/login=0,1m;POST /auth/totp/verify=5,0s")
		if len(parsed) != 0 {
			t.Fatalf("expected no parsed entries, got %v", parsed)
		}
	})

	t.Run("valid entries parsed", func(t *testing.T) {
		parsed := parseHTTPEndpointRateLimitOverrides("POST /auth/login=5,1m; POST /auth/totp/verify = 3,30s")
		if len(parsed) != 2 {
			t.Fatalf("expected 2 parsed entries, got %d", len(parsed))
		}
		login, ok := parsed["POST /auth/login"]
		if !ok {
			t.Fatalf("expected POST /auth/login to be parsed")
		}
		if login.Max != 5 || login.Window != time.Minute {
			t.Fatalf("unexpected login rule: %+v", login)
		}
		totp, ok := parsed["POST /auth/totp/verify"]
		if !ok {
			t.Fatalf("expected POST /auth/totp/verify to be parsed")
		}
		if totp.Max != 3 || totp.Window != 30*time.Second {
			t.Fatalf("unexpected totp rule: %+v", totp)
		}
	})
}
