package worker

import (
	"context"
	"fmt"
	"sync"
	"time"

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

// =============================================================================
// Mock implementations for worker package tests
// =============================================================================

type mockWorkQueue struct {
	mu    sync.Mutex
	tasks map[string]*domain.WorkTask
	err   error
}

func newMockWorkQueue() *mockWorkQueue {
	return &mockWorkQueue{tasks: make(map[string]*domain.WorkTask)}
}

func (m *mockWorkQueue) Enqueue(_ context.Context, task *domain.WorkTask) (string, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.err != nil {
		return "", m.err
	}
	id := fmt.Sprintf("task-%d", len(m.tasks)+1)
	task.ID = id
	task.Status = domain.WorkTaskStatusPending
	task.CreatedAt = time.Now()
	m.tasks[id] = task
	return id, nil
}

func (m *mockWorkQueue) Dequeue(_ context.Context, workerID string) (*domain.WorkTask, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.err != nil {
		return nil, m.err
	}
	for _, task := range m.tasks {
		if task.Status == domain.WorkTaskStatusPending {
			task.Status = domain.WorkTaskStatusRunning
			task.WorkerID = workerID
			now := time.Now()
			task.StartedAt = &now
			return task, nil
		}
	}
	return nil, nil
}

func (m *mockWorkQueue) Complete(_ context.Context, taskID string, result *domain.WorkResult) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	task, ok := m.tasks[taskID]
	if !ok {
		return domain.ErrWorkTaskNotFound
	}
	task.Status = domain.WorkTaskStatusCompleted
	task.Result = result
	now := time.Now()
	task.CompletedAt = &now
	return nil
}

func (m *mockWorkQueue) Fail(_ context.Context, taskID string, errMsg string) error {
	return m.FailWithCode(context.Background(), taskID, errMsg, domain.WorkErrorCodeNone)
}

func (m *mockWorkQueue) FailWithCode(_ context.Context, taskID string, errMsg string, code domain.WorkErrorCode) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	task, ok := m.tasks[taskID]
	if !ok {
		return domain.ErrWorkTaskNotFound
	}
	task.RetryCount++
	if task.RetryCount >= task.MaxRetries {
		task.Status = domain.WorkTaskStatusFailed
		task.Error = errMsg
		task.ErrorCode = code
	} else {
		task.Status = domain.WorkTaskStatusPending
		task.WorkerID = ""
	}
	return nil
}

func (m *mockWorkQueue) Cancel(_ context.Context, taskID string) error { return nil }
func (m *mockWorkQueue) GetTask(_ context.Context, taskID string) (*domain.WorkTask, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	task, ok := m.tasks[taskID]
	if !ok {
		return nil, domain.ErrWorkTaskNotFound
	}
	return task, nil
}
func (m *mockWorkQueue) List(_ context.Context, _ *domain.WorkTaskStatus, _ domain.WorkListOptions) (*domain.WorkListResult, error) {
	return &domain.WorkListResult{}, nil
}
func (m *mockWorkQueue) ListByProject(_ context.Context, _ string, _ *domain.WorkTaskStatus, _ domain.WorkListOptions) (*domain.WorkListResult, error) {
	return &domain.WorkListResult{}, nil
}
func (m *mockWorkQueue) GetStats(_ context.Context) (*domain.WorkQueueStats, error) {
	return &domain.WorkQueueStats{}, nil
}
func (m *mockWorkQueue) CleanupOld(_ context.Context, _ time.Duration) (int64, error) {
	return 0, nil
}
func (m *mockWorkQueue) RequeueStale(_ context.Context, _ time.Duration) (int64, error) {
	return 0, nil
}

func (m *mockWorkQueue) RequeueStaleWithIDs(_ context.Context, _ time.Duration) ([]string, error) {
	return nil, nil
}

type mockWorkerRegistry struct {
	mu      sync.Mutex
	workers map[string]*domain.Worker
	err     error
}

func newMockWorkerRegistry() *mockWorkerRegistry {
	return &mockWorkerRegistry{workers: make(map[string]*domain.Worker)}
}

func (m *mockWorkerRegistry) Register(_ context.Context, worker *domain.Worker) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.err != nil {
		return m.err
	}
	m.workers[worker.ID] = worker
	return nil
}

func (m *mockWorkerRegistry) Heartbeat(_ context.Context, workerID string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	w, ok := m.workers[workerID]
	if !ok {
		return domain.ErrWorkerNotFound
	}
	w.LastHeartbeat = time.Now()
	return nil
}

func (m *mockWorkerRegistry) UpdateStatus(_ context.Context, workerID string, status domain.WorkerStatus, taskID string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	w, ok := m.workers[workerID]
	if !ok {
		return domain.ErrWorkerNotFound
	}
	w.Status = status
	w.CurrentTask = taskID
	return nil
}

func (m *mockWorkerRegistry) Deregister(_ context.Context, workerID string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	delete(m.workers, workerID)
	return nil
}

func (m *mockWorkerRegistry) Get(_ context.Context, workerID string) (*domain.Worker, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	w, ok := m.workers[workerID]
	if !ok {
		return nil, domain.ErrWorkerNotFound
	}
	return w, nil
}

func (m *mockWorkerRegistry) List(_ context.Context, filter port.WorkerFilter) ([]*domain.Worker, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	var result []*domain.Worker
	for _, w := range m.workers {
		if filter.Status != nil && w.Status != *filter.Status {
			continue
		}
		result = append(result, w)
	}
	return result, nil
}

func (m *mockWorkerRegistry) MarkStaleOffline(_ context.Context, _ time.Duration) (int, error) {
	return 0, nil
}

type mockBuildAudit struct {
	mu      sync.Mutex
	entries map[string]*domain.BuildAuditEntry
}

func newMockBuildAudit() *mockBuildAudit {
	return &mockBuildAudit{entries: make(map[string]*domain.BuildAuditEntry)}
}

func (m *mockBuildAudit) Record(_ context.Context, entry *domain.BuildAuditEntry) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.entries[entry.TaskID] = entry
	return nil
}

func (m *mockBuildAudit) Update(_ context.Context, taskID string, result *domain.BuildResult) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	entry, ok := m.entries[taskID]
	if !ok {
		return domain.ErrBuildNotFound
	}
	entry.Result = result
	if result.Success {
		entry.Status = domain.BuildStatusCompleted
	} else {
		entry.Status = domain.BuildStatusFailed
	}
	return nil
}

func (m *mockBuildAudit) UpdateStatus(_ context.Context, taskID string, status domain.BuildStatus, workerID string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	entry, ok := m.entries[taskID]
	if !ok {
		return domain.ErrBuildNotFound
	}
	entry.Status = status
	entry.WorkerID = workerID
	return nil
}

func (m *mockBuildAudit) Get(_ context.Context, taskID string) (*domain.BuildAuditEntry, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	entry, ok := m.entries[taskID]
	if !ok {
		return nil, domain.ErrBuildNotFound
	}
	return entry, nil
}

func (m *mockBuildAudit) List(_ context.Context, _ port.BuildAuditFilter) ([]*domain.BuildAuditEntry, error) {
	return nil, nil
}

type mockCodeAgent struct {
	result *domain.AgentResult
	err    error
}

func (m *mockCodeAgent) Name() string                             { return "mock-agent" }
func (m *mockCodeAgent) Provider() domain.AgentProvider           { return "mock" }
func (m *mockCodeAgent) Cancel(_ context.Context, _ string) error { return nil }
func (m *mockCodeAgent) Capabilities() domain.AgentCapabilities {
	return domain.AgentCapabilities{Provider: "mock"}
}
func (m *mockCodeAgent) Available(_ context.Context) bool { return true }
func (m *mockCodeAgent) Execute(_ context.Context, req *domain.AgentRequest, handler domain.AgentEventHandler) (*domain.AgentResult, error) {
	if handler != nil {
		handler(domain.AgentEvent{
			Type:    domain.AgentEventOutput,
			Content: "mock output for: " + req.Prompt,
		})
	}
	if m.err != nil {
		return nil, m.err
	}
	return m.result, nil
}

type mockCodeAgentRegistry struct {
	agent port.CodeAgent
}

func (m *mockCodeAgentRegistry) Register(agent port.CodeAgent)             { m.agent = agent }
func (m *mockCodeAgentRegistry) Get(_ domain.AgentProvider) port.CodeAgent { return m.agent }
func (m *mockCodeAgentRegistry) Default() port.CodeAgent                   { return m.agent }
func (m *mockCodeAgentRegistry) DefaultProvider() domain.AgentProvider     { return "mock" }
func (m *mockCodeAgentRegistry) SetDefault(_ domain.AgentProvider) error   { return nil }
func (m *mockCodeAgentRegistry) Available() []domain.AgentProvider {
	return []domain.AgentProvider{"mock"}
}
func (m *mockCodeAgentRegistry) AvailableAgents(_ context.Context) []port.CodeAgent {
	return []port.CodeAgent{m.agent}
}
func (m *mockCodeAgentRegistry) Count() int { return 1 }

// =============================================================================
// Mock CommandExecutor for verify tests
// =============================================================================

type mockCommandExecutor struct {
	result      *domain.CommandResult
	err         error
	output      []domain.OutputLine
	podExists   bool
	podExistErr error
}

func newMockCommandExecutor() *mockCommandExecutor {
	return &mockCommandExecutor{
		result: &domain.CommandResult{
			ExitCode:   0,
			DurationMs: 100,
		},
		podExists: true,
	}
}

func (m *mockCommandExecutor) Execute(_ context.Context, _ *domain.Command, _ string, handler domain.OutputHandler) (*domain.CommandResult, error) {
	if m.err != nil {
		return nil, m.err
	}
	// Deliver output lines to handler
	for _, line := range m.output {
		handler(line)
	}
	return m.result, nil
}

func (m *mockCommandExecutor) Cancel(_ context.Context, _ domain.CommandID) error {
	return nil
}

func (m *mockCommandExecutor) PodExists(_ context.Context, _ string) (bool, error) {
	return m.podExists, m.podExistErr
}

func (m *mockCommandExecutor) CheckConnection(_ context.Context) error {
	return nil
}

// =============================================================================
// Helper to build test dependencies
// =============================================================================

type testDeps struct {
	queue    *mockWorkQueue
	registry *mockWorkerRegistry
	audit    *mockBuildAudit
	agent    *mockCodeAgent

	workerSvc *service.WorkerService
	workSvc   *service.WorkService
	buildExec *BuildExecutor
}

func newTestDeps() *testDeps {
	queue := newMockWorkQueue()
	registry := newMockWorkerRegistry()
	audit := newMockBuildAudit()
	agent := &mockCodeAgent{
		result: &domain.AgentResult{
			ExitCode:   0,
			DurationMs: 1000,
		},
	}
	agentRegistry := &mockCodeAgentRegistry{agent: agent}

	workerSvc := service.NewWorkerService(registry, queue).
		WithBuildAudit(audit)
	workSvc := service.NewWorkService(queue)

	buildExec := NewBuildExecutor(agentRegistry, nil, nil, nil)

	return &testDeps{
		queue:     queue,
		registry:  registry,
		audit:     audit,
		agent:     agent,
		workerSvc: workerSvc,
		workSvc:   workSvc,
		buildExec: buildExec,
	}
}
