package polecat

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

func TestNamePool_Allocate(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePool(tmpDir, "testrig")

	// First allocation should be first themed name (furiosa)
	name, err := pool.Allocate()
	if err != nil {
		t.Fatalf("Allocate error: %v", err)
	}
	if name != "furiosa" {
		t.Errorf("expected furiosa, got %s", name)
	}

	// Second allocation should be nux
	name, err = pool.Allocate()
	if err != nil {
		t.Fatalf("Allocate error: %v", err)
	}
	if name != "nux" {
		t.Errorf("expected nux, got %s", name)
	}
}

func TestNamePool_Release(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePool(tmpDir, "testrig")

	// Allocate first two
	name1, _ := pool.Allocate()
	name2, _ := pool.Allocate()

	if name1 != "furiosa" || name2 != "nux" {
		t.Fatalf("unexpected allocations: %s, %s", name1, name2)
	}

	// Release first one
	pool.Release("furiosa")

	// Next allocation should reuse furiosa
	name, _ := pool.Allocate()
	if name != "furiosa" {
		t.Errorf("expected furiosa to be reused, got %s", name)
	}
}

func TestNamePool_PrefersOrder(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePool(tmpDir, "testrig")

	// Allocate first 5
	for i := 0; i < 5; i++ {
		pool.Allocate()
	}

	// Release slit and furiosa
	pool.Release("slit")
	pool.Release("furiosa")

	// Next allocation should be furiosa (first in theme order)
	name, _ := pool.Allocate()
	if name != "furiosa" {
		t.Errorf("expected furiosa (first in order), got %s", name)
	}

	// Next should be slit
	name, _ = pool.Allocate()
	if name != "slit" {
		t.Errorf("expected slit, got %s", name)
	}
}

func TestNamePool_Overflow(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePoolWithConfig(tmpDir, "gastown", "mad-max", nil, 5)

	// Exhaust the small pool
	for i := 0; i < 5; i++ {
		pool.Allocate()
	}

	// Next allocation should be overflow format
	name, err := pool.Allocate()
	if err != nil {
		t.Fatalf("Allocate error: %v", err)
	}
	expected := "gastown-6"
	if name != expected {
		t.Errorf("expected overflow name %s, got %s", expected, name)
	}

	// Next overflow
	name, _ = pool.Allocate()
	if name != "gastown-7" {
		t.Errorf("expected gastown-7, got %s", name)
	}
}

func TestNamePool_OverflowNotReusable(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePoolWithConfig(tmpDir, "gastown", "mad-max", nil, 3)

	// Exhaust the pool
	for i := 0; i < 3; i++ {
		pool.Allocate()
	}

	// Get overflow name
	overflow1, _ := pool.Allocate()
	if overflow1 != "gastown-4" {
		t.Fatalf("expected gastown-4, got %s", overflow1)
	}

	// Release it - should not be reused
	pool.Release(overflow1)

	// Next allocation should be gastown-5, not gastown-4
	name, _ := pool.Allocate()
	if name != "gastown-5" {
		t.Errorf("expected gastown-5 (overflow increments), got %s", name)
	}
}

func TestNamePool_SaveLoad(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePool(tmpDir, "testrig")

	// Allocate some names
	pool.Allocate() // furiosa
	pool.Allocate() // nux
	pool.Allocate() // slit
	pool.Release("nux")

	// Save state
	if err := pool.Save(); err != nil {
		t.Fatalf("Save error: %v", err)
	}

	// Create new pool and load
	pool2 := NewNamePool(tmpDir, "testrig")
	if err := pool2.Load(); err != nil {
		t.Fatalf("Load error: %v", err)
	}

	// Should have furiosa and slit in use
	if pool2.ActiveCount() != 2 {
		t.Errorf("expected 2 active, got %d", pool2.ActiveCount())
	}

	// Next allocation should be nux (released slot)
	name, _ := pool2.Allocate()
	if name != "nux" {
		t.Errorf("expected nux, got %s", name)
	}
}

func TestNamePool_Reconcile(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePool(tmpDir, "testrig")

	// Simulate existing polecats from filesystem
	existing := []string{"slit", "valkyrie", "some-other-name"}

	pool.Reconcile(existing)

	if pool.ActiveCount() != 2 {
		t.Errorf("expected 2 active after reconcile, got %d", pool.ActiveCount())
	}

	// Should allocate furiosa first (not slit or valkyrie)
	name, _ := pool.Allocate()
	if name != "furiosa" {
		t.Errorf("expected furiosa, got %s", name)
	}
}

func TestNamePool_IsPoolName(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePool(tmpDir, "testrig")

	tests := []struct {
		name     string
		expected bool
	}{
		{"furiosa", true},
		{"nux", true},
		{"max", true},
		{"gastown-51", false}, // overflow format
		{"random-name", false},
		{"polecat-01", false}, // old format
	}

	for _, tc := range tests {
		result := pool.IsPoolName(tc.name)
		if result != tc.expected {
			t.Errorf("IsPoolName(%q) = %v, expected %v", tc.name, result, tc.expected)
		}
	}
}

func TestNamePool_ActiveNames(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePool(tmpDir, "testrig")

	pool.Allocate() // furiosa
	pool.Allocate() // nux
	pool.Allocate() // slit
	pool.Release("nux")

	names := pool.ActiveNames()
	if len(names) != 2 {
		t.Errorf("expected 2 active names, got %d", len(names))
	}
	// Names are sorted
	if names[0] != "furiosa" || names[1] != "slit" {
		t.Errorf("expected [furiosa, slit], got %v", names)
	}
}

func TestNamePool_MarkInUse(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePool(tmpDir, "testrig")

	// Mark some slots as in use
	pool.MarkInUse("dementus")
	pool.MarkInUse("valkyrie")

	// Allocate should skip those
	name, _ := pool.Allocate()
	if name != "furiosa" {
		t.Errorf("expected furiosa, got %s", name)
	}

	// Verify count
	if pool.ActiveCount() != 3 { // furiosa, dementus, valkyrie
		t.Errorf("expected 3 active, got %d", pool.ActiveCount())
	}
}

func TestNamePool_StateFilePath(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePool(tmpDir, "testrig")
	pool.Allocate()
	if err := pool.Save(); err != nil {
		t.Fatalf("Save error: %v", err)
	}

	// Verify file was created in expected location
	expectedPath := filepath.Join(tmpDir, ".runtime", "namepool-state.json")
	if _, err := os.Stat(expectedPath); err != nil {
		t.Errorf("state file not found at expected path: %v", err)
	}
}

func TestNamePool_Themes(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	// Test minerals theme
	pool := NewNamePoolWithConfig(tmpDir, "testrig", "minerals", nil, 50)

	name, err := pool.Allocate()
	if err != nil {
		t.Fatalf("Allocate error: %v", err)
	}
	if name != "obsidian" {
		t.Errorf("expected obsidian (first mineral), got %s", name)
	}

	// Test theme switching
	if err := pool.SetTheme("wasteland"); err != nil {
		t.Fatalf("SetTheme error: %v", err)
	}

	// obsidian should be released (not in wasteland theme)
	name, _ = pool.Allocate()
	if name != "rust" {
		t.Errorf("expected rust (first wasteland name), got %s", name)
	}
}

func TestNamePool_CustomNames(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	custom := []string{"alpha", "beta", "gamma", "delta"}
	pool := NewNamePoolWithConfig(tmpDir, "testrig", "", custom, 4)

	name, _ := pool.Allocate()
	if name != "alpha" {
		t.Errorf("expected alpha, got %s", name)
	}

	name, _ = pool.Allocate()
	if name != "beta" {
		t.Errorf("expected beta, got %s", name)
	}
}

func TestListThemes(t *testing.T) {
	themes := ListThemes()
	if len(themes) != 3 {
		t.Errorf("expected 3 themes, got %d", len(themes))
	}

	// Check that all expected themes are present
	expected := map[string]bool{"mad-max": true, "minerals": true, "wasteland": true}
	for _, theme := range themes {
		if !expected[theme] {
			t.Errorf("unexpected theme: %s", theme)
		}
	}
}

func TestGetThemeNames(t *testing.T) {
	names, err := GetThemeNames("mad-max")
	if err != nil {
		t.Fatalf("GetThemeNames error: %v", err)
	}
	if len(names) != 50 {
		t.Errorf("expected 50 mad-max names, got %d", len(names))
	}
	if names[0] != "furiosa" {
		t.Errorf("expected first name to be furiosa, got %s", names[0])
	}

	// Test invalid theme
	_, err = GetThemeNames("invalid-theme")
	if err == nil {
		t.Error("expected error for invalid theme")
	}
}

func TestNamePool_Reset(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "namepool-test-*")
	if err != nil {
		t.Fatal(err)
	}
	defer func() { _ = os.RemoveAll(tmpDir) }()

	pool := NewNamePool(tmpDir, "testrig")

	// Allocate several names
	for i := 0; i < 10; i++ {
		pool.Allocate()
	}

	if pool.ActiveCount() != 10 {
		t.Errorf("expected 10 active, got %d", pool.ActiveCount())
	}

	// Reset
	pool.Reset()

	if pool.ActiveCount() != 0 {
		t.Errorf("expected 0 active after reset, got %d", pool.ActiveCount())
	}

	// Should allocate furiosa again
	name, _ := pool.Allocate()
	if name != "furiosa" {
		t.Errorf("expected furiosa after reset, got %s", name)
	}
}
