package felhomsshd

import (
	"strings"
	"testing"
)

func TestRenderConfig_SafeTemplateAndByteStable(t *testing.T) {
	c, err := renderConfig(8822)
	if err != nil {
		t.Fatal(err)
	}
	for _, must := range []string{
		"Port 8822\n",
		"AuthorizedKeysFile /etc/felhom-sshd/authorized_keys/%u\n",
		"HostKey /etc/felhom-sshd/ssh_host_ed25519_key\n",
		"PasswordAuthentication no\n",
		"PermitRootLogin prohibit-password\n",
		"AllowUsers root felhom-op\n",
		"PidFile /run/felhom-sshd.pid\n",
	} {
		if !strings.Contains(c, must) {
			t.Errorf("config missing %q:\n%s", must, c)
		}
	}
	// [SF-1] the incident cause must NEVER appear.
	if strings.Contains(c, "RuntimeDirectory") {
		t.Fatal("config/unit must never mention RuntimeDirectory")
	}
	// byte-stable for a given port (conf-hash stability → no reload churn).
	c2, _ := renderConfig(8822)
	if c != c2 {
		t.Fatal("renderConfig not deterministic")
	}
}

func TestRenderConfig_RefusesPort22AndOutOfRange(t *testing.T) {
	if _, err := renderConfig(22); err == nil {
		t.Fatal("renderConfig(22) must be refused — never claim the stock/customer port")
	}
	if _, err := renderConfig(0); err == nil {
		t.Fatal("port 0 accepted")
	}
	if _, err := renderConfig(70000); err == nil {
		t.Fatal("out-of-range port accepted")
	}
}

// claim harness: a fake free-set + an in-memory persisted port.
func claimHarness(free map[int]bool, persisted int) (portProbe, func() (int, bool), func(int) error, *int) {
	stored := persisted
	isFree := func(p int) bool { return free[p] }
	read := func() (int, bool) {
		if stored == 0 {
			return 0, false
		}
		return stored, true
	}
	write := func(p int) error { stored = p; return nil }
	return isFree, read, write, &stored
}

func TestClaimPort_CleanContentionIdempotentExhaustion(t *testing.T) {
	cands := []int{8822, 2222, 8022, 62222}

	// clean → first candidate
	isFree, read, write, stored := claimHarness(map[int]bool{8822: true, 2222: true, 8022: true, 62222: true}, 0)
	if p, err := claimPort(cands, isFree, read, write); err != nil || p != 8822 {
		t.Fatalf("clean claim = %d / %v, want 8822", p, err)
	}
	if *stored != 8822 {
		t.Fatalf("clean claim not persisted, stored=%d", *stored)
	}

	// contention: 8822 busy → 2222
	isFree, read, write, _ = claimHarness(map[int]bool{8822: false, 2222: true, 8022: true, 62222: true}, 0)
	if p, err := claimPort(cands, isFree, read, write); err != nil || p != 2222 {
		t.Fatalf("contention claim = %d / %v, want 2222", p, err)
	}

	// idempotent: a persisted port is KEPT unconditionally (even when isFree(cur) reports busy —
	// that's felhom-sshd itself holding it on a continuous reconcile; re-probing would flip-flop).
	isFree, read, write, _ = claimHarness(map[int]bool{8822: true, 2222: false, 8022: true, 62222: true}, 2222)
	if p, err := claimPort(cands, isFree, read, write); err != nil || p != 2222 {
		t.Fatalf("persisted port must be kept even when isFree says busy (own daemon), got %d / %v", p, err)
	}

	// exhaustion: all busy → LOUD error, no fallback
	isFree, read, write, stored = claimHarness(map[int]bool{8822: false, 2222: false, 8022: false, 62222: false}, 0)
	p, err := claimPort(cands, isFree, read, write)
	if err != ErrPortsExhausted {
		t.Fatalf("exhaustion must return ErrPortsExhausted, got %d / %v", p, err)
	}
	if p == 22 || p != 0 {
		t.Fatalf("exhaustion must NOT yield a port (esp. :22), got %d", p)
	}
	if *stored != 0 {
		t.Fatalf("exhaustion must persist nothing, stored=%d", *stored)
	}

}
