package codeagent

import (
	"context"
	"testing"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
)

// mockAgent is a test implementation of port.CodeAgent.
type mockAgent struct {
	provider  domain.AgentProvider
	name      string
	available bool
}

func (m *mockAgent) Name() string                       { return m.name }
func (m *mockAgent) Provider() domain.AgentProvider     { return m.provider }
func (m *mockAgent) Available(ctx context.Context) bool { return m.available }

func (m *mockAgent) Execute(ctx context.Context, req *domain.AgentRequest, handler domain.AgentEventHandler) (*domain.AgentResult, error) {
	return &domain.AgentResult{ExitCode: 0}, nil
}

func (m *mockAgent) Cancel(ctx context.Context, sessionID string) error {
	return nil
}

func (m *mockAgent) Capabilities() domain.AgentCapabilities {
	return domain.AgentCapabilities{Provider: m.provider}
}

var _ port.CodeAgent = (*mockAgent)(nil)

func TestRegistry_RegisterAndGet(t *testing.T) {
	reg := NewRegistry()

	agent := &mockAgent{
		provider:  domain.AgentProviderClaudeCode,
		name:      "Claude Code",
		available: true,
	}

	reg.Register(agent)

	got := reg.Get(domain.AgentProviderClaudeCode)
	if got == nil {
		t.Fatal("expected agent, got nil")
	}
	if got.Name() != "Claude Code" {
		t.Errorf("expected name 'Claude Code', got %q", got.Name())
	}
}

func TestRegistry_GetUnregistered(t *testing.T) {
	reg := NewRegistry()

	got := reg.Get(domain.AgentProviderOpenCode)
	if got != nil {
		t.Errorf("expected nil for unregistered provider, got %v", got)
	}
}

func TestRegistry_DefaultFirstRegistered(t *testing.T) {
	reg := NewRegistry()

	claude := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude", available: true}
	opencode := &mockAgent{provider: domain.AgentProviderOpenCode, name: "OpenCode", available: true}

	reg.Register(claude)
	reg.Register(opencode)

	def := reg.Default()
	if def == nil {
		t.Fatal("expected default agent, got nil")
	}
	if def.Provider() != domain.AgentProviderClaudeCode {
		t.Errorf("expected first registered (claudecode) as default, got %v", def.Provider())
	}
}

func TestRegistry_SetDefault(t *testing.T) {
	reg := NewRegistry()

	claude := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude", available: true}
	opencode := &mockAgent{provider: domain.AgentProviderOpenCode, name: "OpenCode", available: true}

	reg.Register(claude)
	reg.Register(opencode)

	err := reg.SetDefault(domain.AgentProviderOpenCode)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	def := reg.Default()
	if def.Provider() != domain.AgentProviderOpenCode {
		t.Errorf("expected opencode as default, got %v", def.Provider())
	}
}

func TestRegistry_SetDefaultUnregistered(t *testing.T) {
	reg := NewRegistry()

	claude := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude", available: true}
	reg.Register(claude)

	err := reg.SetDefault(domain.AgentProviderOpenCode)
	if err == nil {
		t.Error("expected error setting unregistered default")
	}
}

func TestRegistry_Available(t *testing.T) {
	reg := NewRegistry()

	claude := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude", available: true}
	opencode := &mockAgent{provider: domain.AgentProviderOpenCode, name: "OpenCode", available: true}

	reg.Register(claude)
	reg.Register(opencode)

	providers := reg.Available()
	if len(providers) != 2 {
		t.Errorf("expected 2 providers, got %d", len(providers))
	}

	// Check both are present (order not guaranteed)
	found := make(map[domain.AgentProvider]bool)
	for _, p := range providers {
		found[p] = true
	}
	if !found[domain.AgentProviderClaudeCode] || !found[domain.AgentProviderOpenCode] {
		t.Errorf("expected both providers in list, got %v", providers)
	}
}

func TestRegistry_AvailableAgents(t *testing.T) {
	reg := NewRegistry()

	claude := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude", available: true}
	opencode := &mockAgent{provider: domain.AgentProviderOpenCode, name: "OpenCode", available: false}

	reg.Register(claude)
	reg.Register(opencode)

	ctx := context.Background()
	agents := reg.AvailableAgents(ctx)

	if len(agents) != 1 {
		t.Errorf("expected 1 available agent, got %d", len(agents))
	}
	if agents[0].Provider() != domain.AgentProviderClaudeCode {
		t.Errorf("expected claude as available, got %v", agents[0].Provider())
	}
}

func TestRegistry_DefaultEmpty(t *testing.T) {
	reg := NewRegistry()

	def := reg.Default()
	if def != nil {
		t.Errorf("expected nil default for empty registry, got %v", def)
	}
}

func TestRegistry_Count(t *testing.T) {
	reg := NewRegistry()

	if reg.Count() != 0 {
		t.Errorf("expected 0 count, got %d", reg.Count())
	}

	reg.Register(&mockAgent{provider: domain.AgentProviderClaudeCode, available: true})
	if reg.Count() != 1 {
		t.Errorf("expected 1 count, got %d", reg.Count())
	}

	reg.Register(&mockAgent{provider: domain.AgentProviderOpenCode, available: true})
	if reg.Count() != 2 {
		t.Errorf("expected 2 count, got %d", reg.Count())
	}
}

func TestRegistry_DefaultProvider(t *testing.T) {
	reg := NewRegistry()

	// Empty registry
	if p := reg.DefaultProvider(); p != "" {
		t.Errorf("expected empty default provider, got %q", p)
	}

	reg.Register(&mockAgent{provider: domain.AgentProviderClaudeCode, available: true})
	if p := reg.DefaultProvider(); p != domain.AgentProviderClaudeCode {
		t.Errorf("expected claudecode, got %q", p)
	}
}

func TestRegistry_ConcurrentAccess(t *testing.T) {
	reg := NewRegistry()

	// Pre-register one agent
	reg.Register(&mockAgent{provider: domain.AgentProviderClaudeCode, available: true})

	done := make(chan struct{})
	const goroutines = 10
	const iterations = 100

	// Concurrent reads and writes
	for i := 0; i < goroutines; i++ {
		go func(id int) {
			defer func() { done <- struct{}{} }()
			for j := 0; j < iterations; j++ {
				// Mix of operations
				switch j % 5 {
				case 0:
					reg.Get(domain.AgentProviderClaudeCode)
				case 1:
					reg.Default()
				case 2:
					reg.Available()
				case 3:
					reg.Count()
				case 4:
					reg.AvailableAgents(context.Background())
				}
			}
		}(i)
	}

	// Wait for all goroutines
	for i := 0; i < goroutines; i++ {
		<-done
	}

	// Verify state is consistent
	if reg.Count() != 1 {
		t.Errorf("expected count 1 after concurrent access, got %d", reg.Count())
	}
}

func TestRegistry_ReRegisterOverwrites(t *testing.T) {
	reg := NewRegistry()

	agent1 := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude v1", available: true}
	agent2 := &mockAgent{provider: domain.AgentProviderClaudeCode, name: "Claude v2", available: true}

	reg.Register(agent1)
	reg.Register(agent2) // Should overwrite

	got := reg.Get(domain.AgentProviderClaudeCode)
	if got.Name() != "Claude v2" {
		t.Errorf("expected 'Claude v2' after re-register, got %q", got.Name())
	}

	// Count should still be 1
	if reg.Count() != 1 {
		t.Errorf("expected count 1 after re-register, got %d", reg.Count())
	}
}
