package actuator

import (
	"testing"
	"time"
)

func TestNewCircuitBreakerDefaults(t *testing.T) {
	cb := newCircuitBreaker(0, 0)
	if cb.threshold != 3 {
		t.Errorf("threshold = %d, want 3", cb.threshold)
	}
	if cb.cooldownS != 300 {
		t.Errorf("cooldownS = %d, want 300", cb.cooldownS)
	}

	cb = newCircuitBreaker(5, 60)
	if cb.threshold != 5 {
		t.Errorf("threshold = %d, want 5", cb.threshold)
	}
	if cb.cooldownS != 60 {
		t.Errorf("cooldownS = %d, want 60", cb.cooldownS)
	}
}

func TestCircuitBreakerIsOpenFresh(t *testing.T) {
	cb := newCircuitBreaker(3, 60)
	if cb.isOpen("host:A") {
		t.Errorf("fresh circuit should be closed, got open")
	}
}

func TestCircuitBreakerOpensAtThreshold(t *testing.T) {
	cb := newCircuitBreaker(3, 60)
	// threshold-1 failures → still closed
	cb.recordFailure("host:A")
	cb.recordFailure("host:A")
	if cb.isOpen("host:A") {
		t.Fatalf("circuit should be closed after threshold-1 failures")
	}
	// one more → open
	cb.recordFailure("host:A")
	if !cb.isOpen("host:A") {
		t.Fatalf("circuit should be open after threshold failures")
	}
}

func TestCircuitBreakerClosesAfterCooldown(t *testing.T) {
	cb := newCircuitBreaker(1, 60)
	// Force open
	cb.recordFailure("host:A")
	if !cb.isOpen("host:A") {
		t.Fatalf("circuit should be open")
	}
	// Manipulate the cooldown timestamp to the past to simulate expiry.
	cb.mu.Lock()
	cb.cooldowns["host:A"] = time.Now().Add(-1 * time.Second)
	cb.mu.Unlock()

	if cb.isOpen("host:A") {
		t.Fatalf("circuit should be closed after cooldown expired")
	}
	// Failure count should have been reset by isOpen.
	cb.mu.Lock()
	got := cb.failures["host:A"]
	cb.mu.Unlock()
	if got != 0 {
		t.Errorf("failure count after cooldown reset = %d, want 0", got)
	}
}

func TestCircuitBreakerRecordSuccessResets(t *testing.T) {
	cb := newCircuitBreaker(3, 60)
	cb.recordFailure("host:A")
	cb.recordFailure("host:A")

	cb.recordSuccess("host:A")

	cb.mu.Lock()
	got := cb.failures["host:A"]
	cb.mu.Unlock()
	if got != 0 {
		t.Errorf("failure count after success = %d, want 0", got)
	}
	if cb.isOpen("host:A") {
		t.Errorf("circuit should be closed after success reset")
	}
}

func TestCircuitBreakerPerTargetIsolation(t *testing.T) {
	cb := newCircuitBreaker(2, 60)
	cb.recordFailure("host:A")
	cb.recordFailure("host:A") // host:A now at threshold → open

	if !cb.isOpen("host:A") {
		t.Fatalf("host:A should be open")
	}
	if cb.isOpen("host:B") {
		t.Errorf("host:B should be closed (isolated from host:A)")
	}

	// host:B has no failures recorded
	cb.mu.Lock()
	gotB := cb.failures["host:B"]
	cb.mu.Unlock()
	if gotB != 0 {
		t.Errorf("host:B failure count = %d, want 0", gotB)
	}
}
