package pool

import (
	"testing"
	"time"
)

func TestEmptyPool(t *testing.T) {
	p := NewAccountPool(nil)
	if got := p.GetNextConfigDir(); got != "" {
		t.Fatalf("expected empty string for empty pool, got %q", got)
	}
	if p.Count() != 0 {
		t.Fatalf("expected count 0, got %d", p.Count())
	}
}

func TestSingleDir(t *testing.T) {
	p := NewAccountPool([]string{"/dir1"})
	if got := p.GetNextConfigDir(); got != "/dir1" {
		t.Fatalf("expected /dir1, got %q", got)
	}
	if got := p.GetNextConfigDir(); got != "/dir1" {
		t.Fatalf("expected /dir1 again, got %q", got)
	}
}

func TestRoundRobin(t *testing.T) {
	p := NewAccountPool([]string{"/a", "/b", "/c"})
	got := []string{
		p.GetNextConfigDir(),
		p.GetNextConfigDir(),
		p.GetNextConfigDir(),
		p.GetNextConfigDir(),
	}
	want := []string{"/a", "/b", "/c", "/a"}
	for i, w := range want {
		if got[i] != w {
			t.Fatalf("call %d: expected %q, got %q", i, w, got[i])
		}
	}
}

func TestLeastBusy(t *testing.T) {
	p := NewAccountPool([]string{"/dir1", "/dir2", "/dir3"})
	p.ReportRequestStart("/dir1")
	p.ReportRequestStart("/dir2")

	if got := p.GetNextConfigDir(); got != "/dir3" {
		t.Fatalf("expected /dir3 (least busy), got %q", got)
	}

	p.ReportRequestStart("/dir3")
	p.ReportRequestEnd("/dir1")

	if got := p.GetNextConfigDir(); got != "/dir1" {
		t.Fatalf("expected /dir1 after end, got %q", got)
	}
}

func TestSkipsRateLimited(t *testing.T) {
	p := NewAccountPool([]string{"/dir1", "/dir2"})
	p.ReportRateLimit("/dir1", 60000)

	if got := p.GetNextConfigDir(); got != "/dir2" {
		t.Fatalf("expected /dir2, got %q", got)
	}
	if got := p.GetNextConfigDir(); got != "/dir2" {
		t.Fatalf("expected /dir2 again, got %q", got)
	}
}

func TestFallbackToSoonestRecovery(t *testing.T) {
	p := NewAccountPool([]string{"/dir1", "/dir2"})
	p.ReportRateLimit("/dir1", 60000)
	p.ReportRateLimit("/dir2", 30000)

	// dir2 recovers sooner — should be selected
	if got := p.GetNextConfigDir(); got != "/dir2" {
		t.Fatalf("expected /dir2 (sooner recovery), got %q", got)
	}
}

func TestActiveRequestsDoesNotGoNegative(t *testing.T) {
	p := NewAccountPool([]string{"/dir1"})
	p.ReportRequestEnd("/dir1")
	p.ReportRequestEnd("/dir1")
	if got := p.GetNextConfigDir(); got != "/dir1" {
		t.Fatalf("pool should still work, got %q", got)
	}
}

func TestIgnoreUnknownConfigDir(t *testing.T) {
	p := NewAccountPool([]string{"/dir1"})
	p.ReportRequestStart("/nonexistent")
	p.ReportRequestEnd("/nonexistent")
	p.ReportRateLimit("/nonexistent", 60000)
	if got := p.GetNextConfigDir(); got != "/dir1" {
		t.Fatalf("expected /dir1, got %q", got)
	}
}

func TestRateLimitExpires(t *testing.T) {
	p := NewAccountPool([]string{"/dir1", "/dir2"})
	p.ReportRateLimit("/dir1", 50)

	if got := p.GetNextConfigDir(); got != "/dir2" {
		t.Fatalf("immediately expected /dir2, got %q", got)
	}

	time.Sleep(100 * time.Millisecond)

	if got := p.GetNextConfigDir(); got != "/dir1" {
		t.Fatalf("after expiry expected /dir1, got %q", got)
	}
}

func TestGlobalPool(t *testing.T) {
	InitAccountPool([]string{"/g1", "/g2"})
	if got := GetNextAccountConfigDir(); got != "/g1" {
		t.Fatalf("expected /g1, got %q", got)
	}
	if got := GetNextAccountConfigDir(); got != "/g2" {
		t.Fatalf("expected /g2, got %q", got)
	}
	if got := GetNextAccountConfigDir(); got != "/g1" {
		t.Fatalf("expected /g1 again, got %q", got)
	}
}

func TestGlobalPoolEmpty(t *testing.T) {
	InitAccountPool(nil)
	if got := GetNextAccountConfigDir(); got != "" {
		t.Fatalf("expected empty string for empty global pool, got %q", got)
	}
}

func TestGlobalPoolReinit(t *testing.T) {
	InitAccountPool([]string{"/old1", "/old2"})
	GetNextAccountConfigDir()
	InitAccountPool([]string{"/new1"})
	if got := GetNextAccountConfigDir(); got != "/new1" {
		t.Fatalf("expected /new1 after reinit, got %q", got)
	}
}

func TestGlobalPoolFunctionsNoopBeforeInit(t *testing.T) {
	InitAccountPool(nil)
	ReportRequestStart("/dir1")
	ReportRequestEnd("/dir1")
	ReportRateLimit("/dir1", 1000)
}
