package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
)

// mockCodeAgent implements port.CodeAgent for testing.
type mockCodeAgent struct {
	name         string
	provider     domain.AgentProvider
	available    bool
	capabilities domain.AgentCapabilities
}

func (m *mockCodeAgent) Name() string                   { return m.name }
func (m *mockCodeAgent) Provider() domain.AgentProvider { return m.provider }
func (m *mockCodeAgent) Execute(ctx context.Context, req *domain.AgentRequest, handler domain.AgentEventHandler) (*domain.AgentResult, error) {
	return &domain.AgentResult{}, nil
}
func (m *mockCodeAgent) Cancel(ctx context.Context, sessionID string) error { return nil }
func (m *mockCodeAgent) Capabilities() domain.AgentCapabilities             { return m.capabilities }
func (m *mockCodeAgent) Available(ctx context.Context) bool                 { return m.available }

// mockAgentRegistry implements port.CodeAgentRegistry for testing.
type mockAgentRegistry struct {
	agents        map[domain.AgentProvider]*mockCodeAgent
	defaultAgent  domain.AgentProvider
	setDefaultErr error
}

func newMockAgentRegistry() *mockAgentRegistry {
	return &mockAgentRegistry{
		agents: make(map[domain.AgentProvider]*mockCodeAgent),
	}
}

func (m *mockAgentRegistry) Register(agent port.CodeAgent) {
	m.agents[agent.Provider()] = agent.(*mockCodeAgent)
	if m.defaultAgent == "" {
		m.defaultAgent = agent.Provider()
	}
}

// registerAgent is a helper for tests to directly add mockCodeAgents
func (m *mockAgentRegistry) registerAgent(agent *mockCodeAgent) {
	m.agents[agent.provider] = agent
	if m.defaultAgent == "" {
		m.defaultAgent = agent.provider
	}
}

func (m *mockAgentRegistry) Get(provider domain.AgentProvider) port.CodeAgent {
	agent, ok := m.agents[provider]
	if !ok {
		return nil
	}
	return agent
}

func (m *mockAgentRegistry) Default() port.CodeAgent {
	return m.Get(m.defaultAgent)
}

func (m *mockAgentRegistry) DefaultProvider() domain.AgentProvider {
	return m.defaultAgent
}

func (m *mockAgentRegistry) SetDefault(provider domain.AgentProvider) error {
	if m.setDefaultErr != nil {
		return m.setDefaultErr
	}
	if _, ok := m.agents[provider]; !ok {
		return fmt.Errorf("agent provider %q is not registered", provider)
	}
	m.defaultAgent = provider
	return nil
}

func (m *mockAgentRegistry) Available() []domain.AgentProvider {
	providers := make([]domain.AgentProvider, 0, len(m.agents))
	for p := range m.agents {
		providers = append(providers, p)
	}
	return providers
}

func (m *mockAgentRegistry) AvailableAgents(ctx context.Context) []port.CodeAgent {
	var available []port.CodeAgent
	for _, agent := range m.agents {
		if agent.available {
			available = append(available, agent)
		}
	}
	return available
}

func (m *mockAgentRegistry) Count() int {
	return len(m.agents)
}

func TestAgentsHandler_List(t *testing.T) {
	registry := newMockAgentRegistry()
	registry.registerAgent(&mockCodeAgent{
		name:      "Claude Code",
		provider:  domain.AgentProviderClaudeCode,
		available: true,
		capabilities: domain.AgentCapabilities{
			Provider:                    domain.AgentProviderClaudeCode,
			SupportsSessionContinuation: true,
			SupportedModels:             []string{"claude-sonnet-4-20250514"},
			DefaultModel:                "claude-sonnet-4-20250514",
		},
	})
	registry.registerAgent(&mockCodeAgent{
		name:      "OpenCode",
		provider:  domain.AgentProviderOpenCode,
		available: false,
		capabilities: domain.AgentCapabilities{
			Provider:                    domain.AgentProviderOpenCode,
			SupportsSessionContinuation: true,
			SupportsModelSelection:      true,
			SupportedModels:             []string{"gpt-4o", "claude-sonnet-4-20250514"},
			DefaultModel:                "claude-sonnet-4-20250514",
		},
	})

	handler := NewAgentsHandler(registry)

	req := httptest.NewRequest(http.MethodGet, "/agents", nil)
	w := httptest.NewRecorder()

	handler.List(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", w.Code)
	}

	// Unwrap from api.Response
	respBody := w.Body.Bytes()
	var apiResp struct {
		Data ListAgentsResponse `json:"data"`
	}
	if err := json.Unmarshal(respBody, &apiResp); err != nil {
		t.Fatalf("failed to decode api response: %v", err)
	}

	if apiResp.Data.TotalAgents != 2 {
		t.Errorf("expected 2 agents, got %d", apiResp.Data.TotalAgents)
	}
	if apiResp.Data.AvailableCount != 1 {
		t.Errorf("expected 1 available agent, got %d", apiResp.Data.AvailableCount)
	}
	if apiResp.Data.DefaultAgent != string(domain.AgentProviderClaudeCode) {
		t.Errorf("expected default agent to be claude-code, got %s", apiResp.Data.DefaultAgent)
	}
}

func TestAgentsHandler_List_NoRegistry(t *testing.T) {
	handler := NewAgentsHandler(nil)

	req := httptest.NewRequest(http.MethodGet, "/agents", nil)
	w := httptest.NewRecorder()

	handler.List(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", w.Code)
	}
}

func TestAgentsHandler_GetCapabilities(t *testing.T) {
	registry := newMockAgentRegistry()
	registry.registerAgent(&mockCodeAgent{
		name:     "Claude Code",
		provider: domain.AgentProviderClaudeCode,
		capabilities: domain.AgentCapabilities{
			Provider:                    domain.AgentProviderClaudeCode,
			SupportsSessionContinuation: true,
			SupportsStreaming:           true,
			SupportedModels:             []string{"claude-sonnet-4-20250514"},
			DefaultModel:                "claude-sonnet-4-20250514",
			MaxPromptLength:             100000,
		},
	})

	handler := NewAgentsHandler(registry)

	// Set up chi router for URL params
	r := chi.NewRouter()
	r.Get("/agents/{provider}", handler.GetCapabilities)

	req := httptest.NewRequest(http.MethodGet, "/agents/claudecode", nil)
	w := httptest.NewRecorder()

	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d: %s", w.Code, w.Body.String())
	}

	var apiResp struct {
		Data AgentCapabilitiesDTO `json:"data"`
	}
	if err := json.Unmarshal(w.Body.Bytes(), &apiResp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if apiResp.Data.Provider != string(domain.AgentProviderClaudeCode) {
		t.Errorf("expected provider claudecode, got %s", apiResp.Data.Provider)
	}
	if !apiResp.Data.SupportsSessionContinuation {
		t.Error("expected session continuation support")
	}
	if !apiResp.Data.SupportsStreaming {
		t.Error("expected streaming support")
	}
}

func TestAgentsHandler_GetCapabilities_NotFound(t *testing.T) {
	registry := newMockAgentRegistry()
	handler := NewAgentsHandler(registry)

	r := chi.NewRouter()
	r.Get("/agents/{provider}", handler.GetCapabilities)

	req := httptest.NewRequest(http.MethodGet, "/agents/unknown", nil)
	w := httptest.NewRecorder()

	r.ServeHTTP(w, req)

	if w.Code != http.StatusNotFound {
		t.Errorf("expected status 404, got %d", w.Code)
	}
}

func TestAgentsHandler_SetDefault(t *testing.T) {
	registry := newMockAgentRegistry()
	registry.registerAgent(&mockCodeAgent{
		name:     "Claude Code",
		provider: domain.AgentProviderClaudeCode,
	})
	registry.registerAgent(&mockCodeAgent{
		name:     "OpenCode",
		provider: domain.AgentProviderOpenCode,
	})

	handler := NewAgentsHandler(registry)

	body := SetDefaultRequest{Provider: string(domain.AgentProviderOpenCode)}
	bodyBytes, _ := json.Marshal(body)

	req := httptest.NewRequest(http.MethodPost, "/agents/default", bytes.NewReader(bodyBytes))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	handler.SetDefault(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d: %s", w.Code, w.Body.String())
	}

	// Verify default was changed
	if registry.defaultAgent != domain.AgentProviderOpenCode {
		t.Errorf("expected default to be opencode, got %s", registry.defaultAgent)
	}
}

func TestAgentsHandler_SetDefault_InvalidProvider(t *testing.T) {
	registry := newMockAgentRegistry()
	registry.registerAgent(&mockCodeAgent{
		name:     "Claude Code",
		provider: domain.AgentProviderClaudeCode,
	})

	handler := NewAgentsHandler(registry)

	body := SetDefaultRequest{Provider: "unknown"}
	bodyBytes, _ := json.Marshal(body)

	req := httptest.NewRequest(http.MethodPost, "/agents/default", bytes.NewReader(bodyBytes))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	handler.SetDefault(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d", w.Code)
	}
}

func TestAgentsHandler_SetDefault_EmptyProvider(t *testing.T) {
	registry := newMockAgentRegistry()
	handler := NewAgentsHandler(registry)

	body := SetDefaultRequest{Provider: ""}
	bodyBytes, _ := json.Marshal(body)

	req := httptest.NewRequest(http.MethodPost, "/agents/default", bytes.NewReader(bodyBytes))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()

	handler.SetDefault(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d", w.Code)
	}
}

func TestAgentsHandler_Health(t *testing.T) {
	registry := newMockAgentRegistry()
	registry.registerAgent(&mockCodeAgent{
		name:      "Claude Code",
		provider:  domain.AgentProviderClaudeCode,
		available: true,
	})
	registry.registerAgent(&mockCodeAgent{
		name:      "OpenCode",
		provider:  domain.AgentProviderOpenCode,
		available: false,
	})

	handler := NewAgentsHandler(registry)

	req := httptest.NewRequest(http.MethodGet, "/agents/health", nil)
	w := httptest.NewRecorder()

	handler.Health(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", w.Code)
	}

	var apiResp struct {
		Data AgentHealthResponse `json:"data"`
	}
	if err := json.Unmarshal(w.Body.Bytes(), &apiResp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if apiResp.Data.TotalCount != 2 {
		t.Errorf("expected 2 agents, got %d", apiResp.Data.TotalCount)
	}
	if apiResp.Data.HealthyCount != 1 {
		t.Errorf("expected 1 healthy agent, got %d", apiResp.Data.HealthyCount)
	}
	if !apiResp.Data.DefaultHealth {
		t.Error("expected default agent to be healthy")
	}
}

func TestAgentsHandler_Health_NoRegistry(t *testing.T) {
	handler := NewAgentsHandler(nil)

	req := httptest.NewRequest(http.MethodGet, "/agents/health", nil)
	w := httptest.NewRecorder()

	handler.Health(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected status 200, got %d", w.Code)
	}
}
