package config

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestLoadJSONConfig(t *testing.T) {
	dir := t.TempDir()
	content := `{
		"listen": ":9090",
		"admin_password": "secret",
		"client_signature_key": "client-key",
		"package_encryption_key": "package-key",
		"timestamp_window_seconds": 90,
		"max_request_bytes": 123,
		"max_package_bytes": 456,
		"storage_dir": "./data",
		"database_path": "./data/feedback.sqlite",
		"mail": {
			"host": "smtp.example.com",
			"port": 587,
			"secure": "starttls",
			"username": "u",
			"password": "p",
			"from_address": "from@example.com",
			"from_name": "Feedback",
			"developer_address": "dev@example.com",
			"timeout_seconds": 7
		}
	}`
	if err := os.WriteFile(filepath.Join(dir, "config.json"), []byte(content), 0o600); err != nil {
		t.Fatal(err)
	}

	cfg, err := Load(dir)
	if err != nil {
		t.Fatal(err)
	}

	if cfg.Listen != ":9090" || cfg.AdminPassword != "secret" || cfg.ClientSignatureKey != "client-key" {
		t.Fatalf("unexpected top-level config: %+v", cfg)
	}
	if cfg.StorageDir != filepath.Join(dir, "data") {
		t.Fatalf("storage dir was not normalized: %q", cfg.StorageDir)
	}
	if cfg.Mail.Host != "smtp.example.com" || cfg.Mail.Port != 587 || cfg.Mail.Secure != "starttls" {
		t.Fatalf("unexpected mail config: %+v", cfg.Mail)
	}
}

func TestConfigTxtTakesPriorityOverJSON(t *testing.T) {
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "config.json"), []byte(`{"admin_password":"json-secret"}`), 0o600); err != nil {
		t.Fatal(err)
	}
	content := `<?php
$adminPassword = 'txt-secret';
return [
    'listen' => ':9191',
    'admin_password' => $adminPassword,
    'storage_dir' => __DIR__ . '/storage',
    'database_path' => __DIR__ . '/storage/feedback.sqlite',
];`
	if err := os.WriteFile(filepath.Join(dir, "config.txt"), []byte(content), 0o600); err != nil {
		t.Fatal(err)
	}

	cfg, err := Load(dir)
	if err != nil {
		t.Fatal(err)
	}
	if cfg.AdminPassword != "txt-secret" || cfg.Listen != ":9191" {
		t.Fatalf("config.txt should win over config.json, got password=%q listen=%q", cfg.AdminPassword, cfg.Listen)
	}
}

func TestLoadConfigTxtLegacyArrayConfig(t *testing.T) {
	dir := t.TempDir()
	content := `<?php
$adminPassword = 'legacy-secret';
return [
    'listen' => ':7070',
    'admin_password_hash' => '',
    'admin_password' => $adminPassword,
    'client_signature_key' => 'legacy-client',
    'package_encryption_key' => 'legacy-package',
    'timestamp_window_seconds' => 120,
    'max_request_bytes' => 12 * 1024 * 1024,
    'max_package_bytes' => 10 * 1024 * 1024,
    'storage_dir' => __DIR__ . '/storage',
    'database_path' => __DIR__ . '/storage/feedback.sqlite',
    'mail' => [
        'host' => 'mail.example.com',
        'port' => 465,
        'secure' => 'ssl',
        'username' => 'sender@example.com',
        'password' => 'mail-secret',
        'from_address' => 'sender@example.com',
        'from_name' => 'YMhut Box Feedback',
        'developer_address' => 'developer@example.com',
        'timeout_seconds' => 20,
    ],
];`
	if err := os.WriteFile(filepath.Join(dir, "config.txt"), []byte(content), 0o600); err != nil {
		t.Fatal(err)
	}

	cfg, err := Load(dir)
	if err != nil {
		t.Fatal(err)
	}

	if cfg.Listen != ":7070" || cfg.AdminPassword != "legacy-secret" || cfg.ClientSignatureKey != "legacy-client" || cfg.PackageEncryptionKey != "legacy-package" {
		t.Fatalf("unexpected legacy config: %+v", cfg)
	}
	if cfg.MaxRequestBytes != 12*1024*1024 || cfg.MaxPackageBytes != 10*1024*1024 {
		t.Fatalf("legacy integer expressions were not evaluated: %+v", cfg)
	}
	if cfg.StorageDir != filepath.Join(dir, "storage") || cfg.DatabasePath != filepath.Join(dir, "storage", "feedback.sqlite") {
		t.Fatalf("legacy __DIR__ paths were not normalized: %q %q", cfg.StorageDir, cfg.DatabasePath)
	}
	if cfg.Mail.Password != "mail-secret" || cfg.Mail.DeveloperAddress != "developer@example.com" {
		t.Fatalf("unexpected legacy mail config: %+v", cfg.Mail)
	}
}

func TestLoadConfigTxtEnhancedSettings(t *testing.T) {
	dir := t.TempDir()
	content := `<?php
return [
    'listen' => ':7071',
    'admin_password' => 'secret',
    'storage_dir' => __DIR__ . '/storage',
    'database_path' => __DIR__ . '/storage/feedback.sqlite',
    'submission_per_minute' => 9,
    'max_zip_files' => 12,
    'max_decompressed_bytes' => 1024 * 1024,
    'backup_dir' => __DIR__ . '/storage/backups',
    'webhooks' => [
        [
            'name' => 'ops',
            'url' => 'https://example.com/hook',
            'secret' => 'hook-secret',
            'enabled' => true,
            'events' => ['feedback.created', 'mail.failed'],
            'timeout_seconds' => 3,
            'max_retries' => 1,
        ],
    ],
];`
	if err := os.WriteFile(filepath.Join(dir, "config.txt"), []byte(content), 0o600); err != nil {
		t.Fatal(err)
	}
	cfg, err := Load(dir)
	if err != nil {
		t.Fatal(err)
	}
	if cfg.RateLimit.SubmissionPerMinute != 9 || cfg.UploadGuard.MaxZipFiles != 12 || cfg.UploadGuard.MaxDecompressedBytes != 1024*1024 {
		t.Fatalf("enhanced settings were not parsed: %+v", cfg)
	}
	if cfg.Backup.Dir != filepath.Join(dir, "storage", "backups") {
		t.Fatalf("backup dir was not normalized: %q", cfg.Backup.Dir)
	}
	if len(cfg.Webhooks) != 1 || cfg.Webhooks[0].Name != "ops" || cfg.Webhooks[0].Events[1] != "mail.failed" || cfg.Webhooks[0].MaxRetries != 1 {
		t.Fatalf("webhooks were not parsed: %+v", cfg.Webhooks)
	}
}

func TestLoadConfigTxtDatabaseSettings(t *testing.T) {
	dir := t.TempDir()
	content := `<?php
return [
    'storage_dir' => __DIR__ . '/storage',
    'database_path' => __DIR__ . '/storage/feedback.sqlite',
    'database_provider' => 'postgres',
    'database_host' => 'db.example.com',
    'database_port' => 5433,
    'database_name' => 'feedback',
    'database_user' => 'feedback_user',
    'database_password' => 'db-secret',
    'database_ssl_mode' => 'require',
    'database_failover_enabled' => true,
    'database_sync_enabled' => true,
    'database_sync_interval_seconds' => 60,
    'database_sync_batch_size' => 25,
];`
	if err := os.WriteFile(filepath.Join(dir, "config.txt"), []byte(content), 0o600); err != nil {
		t.Fatal(err)
	}
	cfg, err := Load(dir)
	if err != nil {
		t.Fatal(err)
	}
	if cfg.Database.Provider != "postgres" || cfg.Database.Host != "db.example.com" || cfg.Database.Port != 5433 || cfg.Database.Password != "db-secret" {
		t.Fatalf("database config was not parsed: %+v", cfg.Database)
	}
	if cfg.Database.SQLitePath != filepath.Join(dir, "storage", "feedback.sqlite") || cfg.DatabasePath != cfg.Database.SQLitePath {
		t.Fatalf("sqlite path compatibility failed: %q %q", cfg.Database.SQLitePath, cfg.DatabasePath)
	}
	if !cfg.Database.FailoverEnabled || !cfg.Database.Sync.Enabled || cfg.Database.Sync.IntervalSeconds != 60 || cfg.Database.Sync.BatchSize != 25 {
		t.Fatalf("database sync settings were not parsed: %+v", cfg.Database)
	}
}

func TestSaveConfigCreatesReloadableConfigTxt(t *testing.T) {
	dir := t.TempDir()
	cfg := Default(dir)
	cfg.Listen = ":9099"
	cfg.Database.Provider = "mysql"
	cfg.Database.Host = "mysql.example.com"
	cfg.Database.Name = "feedback"
	cfg.Database.User = "feedback_user"
	cfg.Database.Password = "mysql-secret"
	cfg.Webhooks = []WebhookConfig{{Name: "ops", URL: "https://example.com/hook", Secret: "hook-secret", Enabled: true, Events: []string{"feedback.created"}, TimeoutSeconds: 4, MaxRetries: 2}}

	if err := Save(cfg); err != nil {
		t.Fatal(err)
	}
	saved, err := os.ReadFile(filepath.Join(dir, "config.txt"))
	if err != nil {
		t.Fatal(err)
	}
	text := string(saved)
	if strings.Contains(text, dir) {
		t.Fatalf("saved config should not contain deploy-root absolute paths: %s", text)
	}
	for _, expected := range []string{
		"'storage_dir' => 'storage'",
		"'database_path' => 'storage/feedback.sqlite'",
		"'database_sqlite_path' => 'storage/feedback.sqlite'",
		"'backup_dir' => 'storage/backups'",
	} {
		if !strings.Contains(text, expected) {
			t.Fatalf("saved config is missing portable path %q: %s", expected, text)
		}
	}
	loaded, err := Load(dir)
	if err != nil {
		t.Fatal(err)
	}
	if loaded.Listen != ":9099" || loaded.Database.Provider != "mysql" || loaded.Database.Password != "mysql-secret" {
		t.Fatalf("saved config did not reload: %+v", loaded.Database)
	}
	if len(loaded.Webhooks) != 1 || loaded.Webhooks[0].Secret != "hook-secret" {
		t.Fatalf("saved webhooks did not reload: %+v", loaded.Webhooks)
	}
}
