package sdlc

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"testing"
	"time"

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

// mockWorkQueue implements port.WorkQueue for testing.
type mockWorkQueue struct {
	tasks map[string]*domain.WorkTask
	err   error
}

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

func (m *mockWorkQueue) Enqueue(ctx context.Context, task *domain.WorkTask) (string, error) {
	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(ctx context.Context, workerID string) (*domain.WorkTask, error) {
	return nil, nil
}

func (m *mockWorkQueue) Complete(ctx context.Context, taskID string, result *domain.WorkResult) error {
	if m.err != nil {
		return m.err
	}
	task, ok := m.tasks[taskID]
	if !ok {
		return domain.ErrWorkTaskNotFound
	}
	task.Status = domain.WorkTaskStatusCompleted
	task.Result = result
	return nil
}

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

func (m *mockWorkQueue) FailWithCode(ctx context.Context, taskID string, errMsg string, code domain.WorkErrorCode) error {
	if m.err != nil {
		return m.err
	}
	task, ok := m.tasks[taskID]
	if !ok {
		return domain.ErrWorkTaskNotFound
	}
	task.Status = domain.WorkTaskStatusFailed
	task.Error = errMsg
	task.ErrorCode = code
	return nil
}

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

func (m *mockWorkQueue) GetTask(ctx context.Context, taskID string) (*domain.WorkTask, error) {
	task, ok := m.tasks[taskID]
	if !ok {
		return nil, domain.ErrWorkTaskNotFound
	}
	return task, nil
}

func (m *mockWorkQueue) List(ctx context.Context, status *domain.WorkTaskStatus, opts domain.WorkListOptions) (*domain.WorkListResult, error) {
	return &domain.WorkListResult{}, nil
}

func (m *mockWorkQueue) ListByProject(ctx context.Context, projectID string, status *domain.WorkTaskStatus, opts domain.WorkListOptions) (*domain.WorkListResult, error) {
	return &domain.WorkListResult{}, nil
}

func (m *mockWorkQueue) GetStats(ctx context.Context) (*domain.WorkQueueStats, error) {
	return &domain.WorkQueueStats{}, nil
}

func (m *mockWorkQueue) CleanupOld(ctx context.Context, olderThan time.Duration) (int64, error) {
	return 0, nil
}

func (m *mockWorkQueue) RequeueStale(ctx context.Context, timeout time.Duration) (int64, error) {
	return 0, nil
}

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

func TestWorkerSDLCExecutor_EnqueueTask(t *testing.T) {
	queue := newMockWorkQueue()
	exec := NewWorkerSDLCExecutor(WorkerSDLCExecutorConfig{
		WorkQueue: queue,
		DB:        nil, // No DB for this test
		Timeout:   2 * time.Second,
	})

	// Test that enqueue builds the correct spec
	spec := domain.SDLCTaskSpec{
		Command:     "feature-create",
		Args:        []string{"auth-flow", "--title", "Auth Flow"},
		GitCloneURL: "https://git.example.com/owner/repo.git",
		AutoCommit:  true,
		AutoPush:    true,
	}

	// Start the completion goroutine before enqueueing
	// Use a channel to synchronize
	done := make(chan struct{})
	go func() {
		// Wait a bit for the task to be enqueued
		time.Sleep(100 * time.Millisecond)
		for i := 0; i < 10; i++ {
			if len(queue.tasks) > 0 {
				break
			}
			time.Sleep(50 * time.Millisecond)
		}
		for _, task := range queue.tasks {
			task.Status = domain.WorkTaskStatusCompleted
			featureJSON, _ := json.Marshal(map[string]string{
				"slug":  "auth-flow",
				"title": "Auth Flow",
			})
			task.Result = &domain.WorkResult{Output: string(featureJSON)}
		}
		close(done)
	}()

	output, err := exec.enqueueAndWait(context.Background(), "project-1", spec)
	<-done // Wait for completion goroutine
	if err != nil {
		t.Fatalf("enqueueAndWait() error = %v", err)
	}
	if output == "" {
		t.Error("expected output")
	}

	// Verify task was enqueued with correct spec
	if len(queue.tasks) != 1 {
		t.Fatalf("expected 1 task, got %d", len(queue.tasks))
	}
	for _, task := range queue.tasks {
		if task.Type != domain.WorkTaskTypeSDLC {
			t.Errorf("got task type %q, want %q", task.Type, domain.WorkTaskTypeSDLC)
		}
		if cmd, _ := task.Spec["command"].(string); cmd != "feature-create" {
			t.Errorf("got command %q, want %q", cmd, "feature-create")
		}
	}
}

func TestWorkerSDLCExecutor_Timeout(t *testing.T) {
	queue := newMockWorkQueue()
	exec := NewWorkerSDLCExecutor(WorkerSDLCExecutorConfig{
		WorkQueue: queue,
		DB:        nil,
		Timeout:   100 * time.Millisecond, // Short timeout
	})

	spec := domain.SDLCTaskSpec{
		Command:     "feature-create",
		Args:        []string{"auth-flow"},
		GitCloneURL: "https://git.example.com/owner/repo.git",
	}

	// Don't complete the task - it should timeout
	_, err := exec.enqueueAndWait(context.Background(), "project-1", spec)
	if err == nil {
		t.Error("expected timeout error")
	}
}

func TestWorkerSDLCExecutor_TaskFailed(t *testing.T) {
	queue := newMockWorkQueue()
	exec := NewWorkerSDLCExecutor(WorkerSDLCExecutorConfig{
		WorkQueue: queue,
		DB:        nil,
		Timeout:   500 * time.Millisecond,
	})

	spec := domain.SDLCTaskSpec{
		Command:     "feature-create",
		Args:        []string{"auth-flow"},
		GitCloneURL: "https://git.example.com/owner/repo.git",
	}

	// Simulate task failure
	go func() {
		time.Sleep(50 * time.Millisecond)
		for _, task := range queue.tasks {
			task.Status = domain.WorkTaskStatusFailed
			task.Error = "sdlc command failed: feature already exists"
		}
	}()

	_, err := exec.enqueueAndWait(context.Background(), "project-1", spec)
	if err == nil {
		t.Error("expected error for failed task")
	}
}

func TestWorkerSDLCExecutor_ContextCancelled(t *testing.T) {
	queue := newMockWorkQueue()
	exec := NewWorkerSDLCExecutor(WorkerSDLCExecutorConfig{
		WorkQueue: queue,
		DB:        nil,
		Timeout:   5 * time.Second,
	})

	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		time.Sleep(50 * time.Millisecond)
		cancel()
	}()

	spec := domain.SDLCTaskSpec{
		Command:     "feature-create",
		Args:        []string{"auth-flow"},
		GitCloneURL: "https://git.example.com/owner/repo.git",
	}

	_, err := exec.enqueueAndWait(ctx, "project-1", spec)
	if err == nil {
		t.Error("expected context cancelled error")
	}
}

func TestWorkerSDLCExecutor_NoGitURL(t *testing.T) {
	queue := newMockWorkQueue()
	exec := NewWorkerSDLCExecutor(WorkerSDLCExecutorConfig{
		WorkQueue: queue,
		DB:        nil, // No DB - can't get git URL
		Timeout:   500 * time.Millisecond,
	})

	// This should fail because we can't get the git URL without a database
	_, err := exec.GetState(context.Background(), "project-1")
	if err == nil {
		t.Error("expected error when DB is nil")
	}
}

func TestWorkerSDLCExecutor_InterfaceCompliance(t *testing.T) {
	// Verify that WorkerSDLCExecutor implements port.SDLCExecutor at compile time
	// This is already done in the source file, but we test it here too
	exec := NewWorkerSDLCExecutor(WorkerSDLCExecutorConfig{
		WorkQueue: newMockWorkQueue(),
		DB:        (*sql.DB)(nil),
	})

	// Just ensure the executor exists and has the right methods
	if exec == nil {
		t.Error("expected non-nil executor")
	}
}
