package config

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

	"github.com/spf13/cobra"
)

func TestEnvOverridesYAML(t *testing.T) {
	tempDir := t.TempDir()
	configPath := filepath.Join(tempDir, "config.yaml")
	content := `
pg:
  host: localhost
  port: 5433
backup:
  retention_days: 90
`
	if err := os.WriteFile(configPath, []byte(content), 0o644); err != nil {
		t.Fatalf("failed to write test config: %v", err)
	}

	t.Setenv("APP_CONFIG_LOCATION", configPath)
	t.Setenv("APP_PG_HOST", "remote")

	cmd := &cobra.Command{}
	RegisterFlags(cmd)

	var buf bytes.Buffer
	oldOutput := outputWriter
	outputWriter = &buf
	defer func() { outputWriter = oldOutput }()

	cfg, err := Load(cmd)
	if err != nil {
		t.Fatalf("Load failed: %v", err)
	}

	if cfg.PG.Host != "remote" {
		t.Errorf("PG.Host = %q, want %q (env should override YAML)", cfg.PG.Host, "remote")
	}
	if cfg.PG.Port != 5433 {
		t.Errorf("PG.Port = %d, want %d (YAML value should be preserved when env not set)", cfg.PG.Port, 5433)
	}
	if cfg.Backup.RetentionDays != 90 {
		t.Errorf("Backup.RetentionDays = %d, want %d (from YAML)", cfg.Backup.RetentionDays, 90)
	}

	logOutput := buf.String()
	if !strings.Contains(logOutput, configPath) {
		t.Errorf("log output did not contain config path %q: %s", configPath, logOutput)
	}
}

func TestDefaults(t *testing.T) {
	cmd := &cobra.Command{}
	RegisterFlags(cmd)

	var buf bytes.Buffer
	oldOutput := outputWriter
	outputWriter = &buf
	defer func() { outputWriter = oldOutput }()

	cfg, err := Load(cmd)
	if err != nil {
		t.Fatalf("Load failed: %v", err)
	}

	if cfg.PG.Port != 5432 {
		t.Errorf("PG.Port = %d, want %d", cfg.PG.Port, 5432)
	}
	if cfg.PG.SSLMode != "prefer" {
		t.Errorf("PG.SSLMode = %q, want %q", cfg.PG.SSLMode, "prefer")
	}
	if len(cfg.PG.ExcludeTables) != 1 || cfg.PG.ExcludeTables[0] != "e2e_one_time_keys_json" {
		t.Errorf("PG.ExcludeTables = %v, want [e2e_one_time_keys_json]", cfg.PG.ExcludeTables)
	}
	if cfg.Backup.RetentionDays != 180 {
		t.Errorf("Backup.RetentionDays = %d, want %d", cfg.Backup.RetentionDays, 180)
	}
	if cfg.Backup.Cron != "0 0 3 * * *" {
		t.Errorf("Backup.Cron = %q, want %q", cfg.Backup.Cron, "0 0 3 * * *")
	}
	if cfg.PQScheme != 0x0006 {
		t.Errorf("PQScheme = 0x%04x, want 0x%04x", cfg.PQScheme, 0x0006)
	}
	if cfg.ClassicalScheme != 0x0007 {
		t.Errorf("ClassicalScheme = 0x%04x, want 0x%04x", cfg.ClassicalScheme, 0x0007)
	}
	if cfg.Healthz.Port != 8080 {
		t.Errorf("Healthz.Port = %d, want %d", cfg.Healthz.Port, 8080)
	}
	if cfg.Log.Level != "info" {
		t.Errorf("Log.Level = %q, want %q", cfg.Log.Level, "info")
	}

	logOutput := buf.String()
	if !strings.Contains(logOutput, "Configuration file not found") {
		t.Errorf("log output did not contain 'Configuration file not found': %s", logOutput)
	}
}

func TestFlagsOverrideEnv(t *testing.T) {
	t.Setenv("APP_PG_HOST", "env-host")

	cmd := &cobra.Command{}
	RegisterFlags(cmd)
	if err := cmd.ParseFlags([]string{"--pg-host", "flag-host"}); err != nil {
		t.Fatalf("ParseFlags failed: %v", err)
	}

	var buf bytes.Buffer
	oldOutput := outputWriter
	outputWriter = &buf
	defer func() { outputWriter = oldOutput }()

	cfg, err := Load(cmd)
	if err != nil {
		t.Fatalf("Load failed: %v", err)
	}

	if cfg.PG.Host != "flag-host" {
		t.Errorf("PG.Host = %q, want %q (flag should override env)", cfg.PG.Host, "flag-host")
	}
}

func TestAllConfigFields(t *testing.T) {
	tempDir := t.TempDir()
	configPath := filepath.Join(tempDir, "config.yaml")
	content := `
pg:
  host: db.example.com
  port: 5432
  user: synapse
  password: secret
  database: synapse_db
  sslmode: require
  exclude_tables:
    - table1
    - table2
backup:
  dir: /backups
  retention_days: 30
  cron: "0 0 * * *"
pq_scheme: 0x0001
classical_scheme: 0x0002
pq_public_key_path: /keys/pq.pub
classical_public_key_path: /keys/classical.pub
healthz:
  port: 9090
log:
  level: debug
`
	if err := os.WriteFile(configPath, []byte(content), 0o644); err != nil {
		t.Fatalf("failed to write test config: %v", err)
	}

	t.Setenv("APP_CONFIG_LOCATION", configPath)

	cmd := &cobra.Command{}
	RegisterFlags(cmd)

	var buf bytes.Buffer
	oldOutput := outputWriter
	outputWriter = &buf
	defer func() { outputWriter = oldOutput }()

	cfg, err := Load(cmd)
	if err != nil {
		t.Fatalf("Load failed: %v", err)
	}

	if cfg.PG.Host != "db.example.com" {
		t.Errorf("PG.Host = %q, want %q", cfg.PG.Host, "db.example.com")
	}
	if cfg.PG.User != "synapse" {
		t.Errorf("PG.User = %q, want %q", cfg.PG.User, "synapse")
	}
	if cfg.PG.Password != "secret" {
		t.Errorf("PG.Password = %q, want %q", cfg.PG.Password, "secret")
	}
	if cfg.PG.Database != "synapse_db" {
		t.Errorf("PG.Database = %q, want %q", cfg.PG.Database, "synapse_db")
	}
	if cfg.PG.SSLMode != "require" {
		t.Errorf("PG.SSLMode = %q, want %q", cfg.PG.SSLMode, "require")
	}
	if len(cfg.PG.ExcludeTables) != 2 || cfg.PG.ExcludeTables[0] != "table1" {
		t.Errorf("PG.ExcludeTables = %v, want [table1 table2]", cfg.PG.ExcludeTables)
	}
	if cfg.Backup.Dir != "/backups" {
		t.Errorf("Backup.Dir = %q, want %q", cfg.Backup.Dir, "/backups")
	}
	if cfg.Backup.Cron != "0 0 * * *" {
		t.Errorf("Backup.Cron = %q, want %q", cfg.Backup.Cron, "0 0 * * *")
	}
	if cfg.PQScheme != 0x0001 {
		t.Errorf("PQScheme = 0x%04x, want 0x%04x", cfg.PQScheme, 0x0001)
	}
	if cfg.ClassicalScheme != 0x0002 {
		t.Errorf("ClassicalScheme = 0x%04x, want 0x%04x", cfg.ClassicalScheme, 0x0002)
	}
	if cfg.PQPublicKeyPath != "/keys/pq.pub" {
		t.Errorf("PQPublicKeyPath = %q, want %q", cfg.PQPublicKeyPath, "/keys/pq.pub")
	}
	if cfg.ClassicalPublicKeyPath != "/keys/classical.pub" {
		t.Errorf("ClassicalPublicKeyPath = %q, want %q", cfg.ClassicalPublicKeyPath, "/keys/classical.pub")
	}
	if cfg.Healthz.Port != 9090 {
		t.Errorf("Healthz.Port = %d, want %d", cfg.Healthz.Port, 9090)
	}
	if cfg.Log.Level != "debug" {
		t.Errorf("Log.Level = %q, want %q", cfg.Log.Level, "debug")
	}

	logOutput := buf.String()
	if !strings.Contains(logOutput, configPath) {
		t.Errorf("log output did not contain config path %q: %s", configPath, logOutput)
	}
}
