package config

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

func TestIsLoopbackAddr(t *testing.T) {
	cases := []struct {
		addr string
		want bool
	}{
		{"127.0.0.1:8080", true},
		{"localhost:8080", true},
		{"[::1]:8080", true},
		{"127.0.0.5:80", true}, // any 127/8 is loopback
		{"0.0.0.0:8080", false},
		{":8443", false}, // bare port = all interfaces
		{"10.0.0.1:80", false},
		{"example.com:443", false},
		{"[2001:db8::1]:80", false},
		{"", false},
		{"not-an-addr", false}, // SplitHostPort fails
	}
	for _, tc := range cases {
		t.Run(tc.addr, func(t *testing.T) {
			if got := isLoopbackAddr(tc.addr); got != tc.want {
				t.Errorf("isLoopbackAddr(%q) = %v, want %v", tc.addr, got, tc.want)
			}
		})
	}
}

func TestLoad(t *testing.T) {
	root := t.TempDir()
	// Drop a placeholder .zddc so subtests using this root don't trip the
	// "no root .zddc → refuse to start" safety check. Tests that explicitly
	// exercise the missing-.zddc path use a dedicated tmpdir without one.
	if err := os.WriteFile(filepath.Join(root, ".zddc"), []byte("admins: [test@example.com]\n"), 0o644); err != nil {
		t.Fatalf("seed root .zddc: %v", err)
	}

	// Pre-set the env so each subtest can override what it needs.
	type envSet map[string]string
	clearAll := func() {
		for _, k := range []string{"ZDDC_ROOT", "ZDDC_ADDR", "ZDDC_TLS_CERT", "ZDDC_TLS_KEY", "ZDDC_INSECURE_DIRECT", "ZDDC_INSECURE", "ZDDC_LOG_LEVEL", "ZDDC_INDEX_PATH", "ZDDC_EMAIL_HEADER", "ZDDC_CORS_ORIGIN"} {
			os.Unsetenv(k)
		}
	}
	apply := func(env envSet) {
		clearAll()
		for k, v := range env {
			os.Setenv(k, v)
		}
	}

	cases := []struct {
		name        string
		env         envSet
		wantErr     bool
		errContains string
		check       func(*testing.T, Config)
	}{
		{
			name: "missing root defaults to CWD",
			// ZDDC_INSECURE=1 because the package's CWD has no .zddc; this test
			// is specifically about Root resolution falling back to CWD, not
			// about the .zddc safety check.
			env: envSet{"ZDDC_INSECURE": "1"},
			// ZDDC_ROOT not set → Load falls back to os.Getwd().
			check: func(t *testing.T, cfg Config) {
				cwd, err := os.Getwd()
				if err != nil {
					t.Fatalf("Getwd: %v", err)
				}
				// os.Stat resolves symlinks; so does Load via filepath behavior, so
				// just compare the resolved values.
				if cfg.Root != cwd {
					t.Errorf("Root = %q, want CWD %q", cfg.Root, cwd)
				}
			},
		},
		{
			name:        "root not a directory",
			env:         envSet{"ZDDC_ROOT": filepath.Join(root, "does-not-exist")},
			wantErr:     true,
			errContains: "not accessible",
		},
		{
			name: "defaults applied with TLS self-signed",
			env:  envSet{"ZDDC_ROOT": root},
			check: func(t *testing.T, cfg Config) {
				if cfg.Addr != ":8443" {
					t.Errorf("Addr = %q, want :8443", cfg.Addr)
				}
				if cfg.TLSMode != "selfsigned" {
					t.Errorf("TLSMode = %q, want selfsigned", cfg.TLSMode)
				}
				if cfg.IndexPath != ".archive" {
					t.Errorf("IndexPath = %q, want .archive", cfg.IndexPath)
				}
				if cfg.EmailHeader != "X-Auth-Request-Email" {
					t.Errorf("EmailHeader = %q, want X-Auth-Request-Email", cfg.EmailHeader)
				}
				if len(cfg.CORSOrigins) != 0 {
					t.Errorf("CORSOrigins = %v, want empty (CORS disabled by default)", cfg.CORSOrigins)
				}
			},
		},
		{
			name: "CORS single origin override",
			env: envSet{
				"ZDDC_ROOT":        root,
				"ZDDC_CORS_ORIGIN": "https://tools.acme.com",
			},
			check: func(t *testing.T, cfg Config) {
				if len(cfg.CORSOrigins) != 1 || cfg.CORSOrigins[0] != "https://tools.acme.com" {
					t.Errorf("CORSOrigins = %v, want [https://tools.acme.com]", cfg.CORSOrigins)
				}
			},
		},
		{
			name: "CORS multi-origin allowlist",
			env: envSet{
				"ZDDC_ROOT":        root,
				"ZDDC_CORS_ORIGIN": "https://a.example, https://b.example ,https://c.example",
			},
			check: func(t *testing.T, cfg Config) {
				want := []string{"https://a.example", "https://b.example", "https://c.example"}
				if len(cfg.CORSOrigins) != len(want) {
					t.Fatalf("CORSOrigins = %v, want %v", cfg.CORSOrigins, want)
				}
				for i, w := range want {
					if cfg.CORSOrigins[i] != w {
						t.Errorf("CORSOrigins[%d] = %q, want %q", i, cfg.CORSOrigins[i], w)
					}
				}
			},
		},
		{
			name: "CORS disabled with empty value",
			env: envSet{
				"ZDDC_ROOT":        root,
				"ZDDC_CORS_ORIGIN": "",
			},
			check: func(t *testing.T, cfg Config) {
				if len(cfg.CORSOrigins) != 0 {
					t.Errorf("CORSOrigins = %v, want empty (CORS disabled)", cfg.CORSOrigins)
				}
			},
		},
		{
			name: "TLS provided requires both cert and key",
			env: envSet{
				"ZDDC_ROOT":     root,
				"ZDDC_TLS_CERT": "/some/cert.pem",
				// missing key
			},
			wantErr:     true,
			errContains: "both be set or both be empty",
		},
		{
			name: "plain HTTP on all interfaces without insecure flag is rejected",
			env: envSet{
				"ZDDC_ROOT":     root,
				"ZDDC_TLS_CERT": "none",
				"ZDDC_ADDR":     ":8080",
			},
			wantErr:     true,
			errContains: "--insecure-direct",
		},
		{
			name: "plain HTTP on 0.0.0.0 without insecure flag is rejected",
			env: envSet{
				"ZDDC_ROOT":     root,
				"ZDDC_TLS_CERT": "none",
				"ZDDC_ADDR":     "0.0.0.0:8080",
			},
			wantErr:     true,
			errContains: "--insecure-direct",
		},
		{
			name: "plain HTTP on loopback is allowed",
			env: envSet{
				"ZDDC_ROOT":     root,
				"ZDDC_TLS_CERT": "none",
				"ZDDC_ADDR":     "127.0.0.1:8080",
			},
			check: func(t *testing.T, cfg Config) {
				if cfg.TLSMode != "none" {
					t.Errorf("TLSMode = %q, want none", cfg.TLSMode)
				}
			},
		},
		{
			name: "plain HTTP on non-loopback with explicit opt-in is allowed",
			env: envSet{
				"ZDDC_ROOT":            root,
				"ZDDC_TLS_CERT":        "none",
				"ZDDC_ADDR":            ":8080",
				"ZDDC_INSECURE_DIRECT": "1",
			},
			check: func(t *testing.T, cfg Config) {
				if cfg.TLSMode != "none" {
					t.Errorf("TLSMode = %q, want none", cfg.TLSMode)
				}
			},
		},
		{
			name: "ZDDC_INSECURE_DIRECT set to non-1 is not opt-in",
			env: envSet{
				"ZDDC_ROOT":            root,
				"ZDDC_TLS_CERT":        "none",
				"ZDDC_ADDR":            ":8080",
				"ZDDC_INSECURE_DIRECT": "true", // must be exactly "1"
			},
			wantErr:     true,
			errContains: "--insecure-direct",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			apply(tc.env)
			defer clearAll()

			cfg, err := Load([]string{})
			if tc.wantErr {
				if err == nil {
					t.Fatalf("Load() = nil error, want error containing %q", tc.errContains)
				}
				if tc.errContains != "" && !strings.Contains(err.Error(), tc.errContains) {
					t.Errorf("Load() error = %v, want substring %q", err, tc.errContains)
				}
				return
			}
			if err != nil {
				t.Fatalf("Load() unexpected error: %v", err)
			}
			if tc.check != nil {
				tc.check(t, cfg)
			}
		})
	}
}

// tmpRootWithZddc creates a temp dir and seeds a minimal .zddc so the
// post-Root-stat safety check (refuse to start with no root .zddc) does
// not fire. Tests that exercise the safety check explicitly use
// t.TempDir() directly without seeding.
func tmpRootWithZddc(t *testing.T) string {
	t.Helper()
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, ".zddc"), []byte("admins: [test@example.com]\n"), 0o644); err != nil {
		t.Fatalf("seed root .zddc: %v", err)
	}
	return dir
}

// TestLoadFlags_OverrideEnv: --root flag wins over ZDDC_ROOT env var.
func TestLoadFlags_OverrideEnv(t *testing.T) {
	envRoot := tmpRootWithZddc(t)
	flagRoot := tmpRootWithZddc(t)
	os.Setenv("ZDDC_ROOT", envRoot)
	defer os.Unsetenv("ZDDC_ROOT")

	cfg, err := Load([]string{"--root", flagRoot})
	if err != nil {
		t.Fatalf("Load: %v", err)
	}
	if cfg.Root != flagRoot {
		t.Errorf("Root = %q, want flag value %q", cfg.Root, flagRoot)
	}
}

// TestLoadFlags_AddrLogLevelFromFlags: arbitrary flags override env defaults.
func TestLoadFlags_AddrLogLevelFromFlags(t *testing.T) {
	root := tmpRootWithZddc(t)
	cfg, err := Load([]string{
		"--root", root,
		"--addr", "127.0.0.1:9999",
		"--log-level", "debug",
		"--index-path", ".myindex",
		"--email-header", "X-User-Email",
	})
	if err != nil {
		t.Fatalf("Load: %v", err)
	}
	if cfg.Addr != "127.0.0.1:9999" {
		t.Errorf("Addr=%q", cfg.Addr)
	}
	if cfg.LogLevel != "debug" {
		t.Errorf("LogLevel=%q", cfg.LogLevel)
	}
	if cfg.IndexPath != ".myindex" {
		t.Errorf("IndexPath=%q", cfg.IndexPath)
	}
	if cfg.EmailHeader != "X-User-Email" {
		t.Errorf("EmailHeader=%q", cfg.EmailHeader)
	}
}

// TestLoadFlags_CORSExplicitEmptyDisables: --cors-origin="" explicitly disables CORS.
func TestLoadFlags_CORSExplicitEmptyDisables(t *testing.T) {
	root := tmpRootWithZddc(t)
	cfg, err := Load([]string{"--root", root, "--cors-origin", ""})
	if err != nil {
		t.Fatalf("Load: %v", err)
	}
	if len(cfg.CORSOrigins) != 0 {
		t.Errorf("CORSOrigins = %v, want empty (CORS disabled by explicit empty flag)", cfg.CORSOrigins)
	}
}

// TestLoadFlags_HelpRequested: --help returns the sentinel error.
func TestLoadFlags_HelpRequested(t *testing.T) {
	_, err := Load([]string{"--help"})
	if !strings.Contains(err.Error(), "help requested") && err != ErrHelpRequested {
		t.Errorf("got err=%v, want ErrHelpRequested", err)
	}
}

// TestLoadFlags_VersionRequested: --version returns the sentinel error.
func TestLoadFlags_VersionRequested(t *testing.T) {
	_, err := Load([]string{"--version"})
	if !strings.Contains(err.Error(), "version requested") && err != ErrVersionRequested {
		t.Errorf("got err=%v, want ErrVersionRequested", err)
	}
}

// TestLoadFlags_RootFlagDefaultsToCWD: with no --root and no ZDDC_ROOT, falls back to CWD.
func TestLoadFlags_RootFlagDefaultsToCWD(t *testing.T) {
	os.Unsetenv("ZDDC_ROOT")
	// ZDDC_INSECURE=1 because the package's CWD has no .zddc; this test is
	// specifically about the CWD fallback, not the .zddc safety check.
	os.Setenv("ZDDC_INSECURE", "1")
	defer os.Unsetenv("ZDDC_INSECURE")
	cfg, err := Load([]string{})
	if err != nil {
		t.Fatalf("Load: %v", err)
	}
	cwd, _ := os.Getwd()
	if cfg.Root != cwd {
		t.Errorf("Root=%q, want CWD=%q", cfg.Root, cwd)
	}
}

// TestLoad_MissingRootZddcRefusesStartByDefault: with no .zddc at root and no
// --insecure, Load refuses to start (the public-by-default footgun).
func TestLoad_MissingRootZddcRefusesStartByDefault(t *testing.T) {
	root := t.TempDir() // no .zddc seeded
	os.Setenv("ZDDC_ROOT", root)
	defer os.Unsetenv("ZDDC_ROOT")
	_, err := Load([]string{})
	if err == nil {
		t.Fatal("Load() = nil error, want error about missing root .zddc")
	}
	if !strings.Contains(err.Error(), ".zddc") || !strings.Contains(err.Error(), "publicly accessible") {
		t.Errorf("Load() error = %v, want substring about missing .zddc and public access", err)
	}
}

// TestLoad_MissingRootZddcAllowedWithInsecure: --insecure allows startup
// when the root .zddc is missing (acknowledges the public-tree shape).
func TestLoad_MissingRootZddcAllowedWithInsecure(t *testing.T) {
	root := t.TempDir() // no .zddc seeded
	os.Setenv("ZDDC_ROOT", root)
	os.Setenv("ZDDC_INSECURE", "1")
	defer os.Unsetenv("ZDDC_ROOT")
	defer os.Unsetenv("ZDDC_INSECURE")
	cfg, err := Load([]string{})
	if err != nil {
		t.Fatalf("Load() unexpected error: %v", err)
	}
	if !cfg.Insecure {
		t.Errorf("cfg.Insecure = false, want true (set via ZDDC_INSECURE=1)")
	}
}

// TestLoad_MissingRootZddcAllowedWithInsecureFlag: same but via --insecure flag.
func TestLoad_MissingRootZddcAllowedWithInsecureFlag(t *testing.T) {
	root := t.TempDir() // no .zddc seeded
	cfg, err := Load([]string{"--root", root, "--insecure"})
	if err != nil {
		t.Fatalf("Load() unexpected error: %v", err)
	}
	if !cfg.Insecure {
		t.Errorf("cfg.Insecure = false, want true (set via --insecure)")
	}
}

// TestLoad_ClientModeAddrDefaultsToLoopback confirms that --upstream
// triggers the client-mode loopback default for --addr. The cache
// layer forwards the configured bearer to upstream without checking
// the local caller, so a non-loopback default would be a confused-
// deputy open proxy on whatever interface the host happens to bind.
func TestLoad_ClientModeAddrDefaultsToLoopback(t *testing.T) {
	root := t.TempDir()
	bearer := filepath.Join(root, "tok")
	if err := os.WriteFile(bearer, []byte("xyz"), 0o600); err != nil {
		t.Fatalf("seed bearer: %v", err)
	}
	cfg, err := Load([]string{
		"--root", root,
		"--upstream", "https://master.example.com",
		"--bearer-file", bearer,
	})
	if err != nil {
		t.Fatalf("Load: %v", err)
	}
	if cfg.Addr != "127.0.0.1:8443" {
		t.Errorf("cfg.Addr = %q, want 127.0.0.1:8443 (loopback default in client mode)", cfg.Addr)
	}
}

// TestLoad_ClientModeNonLoopbackBindRefusesWithoutInsecureDirect locks
// down the confused-deputy guard: a non-loopback bind in client mode
// with a configured bearer must require the operator to acknowledge
// the deployment shape via --insecure-direct.
func TestLoad_ClientModeNonLoopbackBindRefusesWithoutInsecureDirect(t *testing.T) {
	root := t.TempDir()
	bearer := filepath.Join(root, "tok")
	_ = os.WriteFile(bearer, []byte("xyz"), 0o600)
	_, err := Load([]string{
		"--root", root,
		"--upstream", "https://master.example.com",
		"--bearer-file", bearer,
		"--addr", "0.0.0.0:8444",
	})
	if err == nil {
		t.Fatal("Load() succeeded; want refusal for non-loopback client-mode bind with bearer")
	}
	if !strings.Contains(err.Error(), "--insecure-direct") {
		t.Errorf("error should mention --insecure-direct as the acknowledgement flag; got: %v", err)
	}
}

// TestLoad_ClientModeNonLoopbackBindAllowedWithInsecureDirect confirms
// the operator can opt out of the loopback guard via the existing flag
// (matches the helm chart path which sets ZDDC_INSECURE_DIRECT=1).
func TestLoad_ClientModeNonLoopbackBindAllowedWithInsecureDirect(t *testing.T) {
	root := t.TempDir()
	bearer := filepath.Join(root, "tok")
	_ = os.WriteFile(bearer, []byte("xyz"), 0o600)
	cfg, err := Load([]string{
		"--root", root,
		"--upstream", "https://master.example.com",
		"--bearer-file", bearer,
		"--addr", "0.0.0.0:8444",
		"--insecure-direct",
	})
	if err != nil {
		t.Fatalf("Load: %v", err)
	}
	if cfg.Addr != "0.0.0.0:8444" {
		t.Errorf("cfg.Addr = %q, want 0.0.0.0:8444", cfg.Addr)
	}
}

// TestLoad_ClientModeNoBearerSkipsGuard documents the narrower scope:
// without a bearer file, there's no credential to launder, so a non-
// loopback bind is the operator's call (the deployment still gets the
// X-ZDDC-Cache: hit/miss metadata-disclosure surface, but no credential
// forwarding). Refusing in this case would needlessly block proxy-mode
// deployments that don't authenticate to upstream at all.
func TestLoad_ClientModeNoBearerSkipsGuard(t *testing.T) {
	root := t.TempDir()
	cfg, err := Load([]string{
		"--root", root,
		"--upstream", "https://master.example.com",
		"--addr", "0.0.0.0:8444",
	})
	if err != nil {
		t.Fatalf("Load (client, non-loopback, no bearer): %v", err)
	}
	if cfg.Addr != "0.0.0.0:8444" {
		t.Errorf("Addr = %q", cfg.Addr)
	}
}
