package postgres

import (
	"context"
	"database/sql"
	"testing"
	"time"

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

func cleanupTestBuildAudit(t *testing.T, db *sql.DB) {
	t.Helper()
	_, err := db.Exec("DELETE FROM build_audit WHERE project_id LIKE 'test-%'")
	if err != nil {
		t.Logf("cleanup test build_audit: %v", err)
	}
}

func TestBuildAuditRepository_Record(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestBuildAudit(t, db) })

	repo := NewBuildAuditRepository(db)
	ctx := context.Background()

	t.Run("records new audit entry", func(t *testing.T) {
		entry := &domain.BuildAuditEntry{
			TaskID:    "test-task-audit-1",
			ProjectID: "test-project-1",
			Spec: domain.BuildSpec{
				Prompt:   "Build a landing page",
				Template: "nextjs",
			},
			Status:    domain.BuildStatusPending,
			StartedAt: time.Now(),
		}

		err := repo.Record(ctx, entry)
		if err != nil {
			t.Fatalf("Record() error = %v", err)
		}

		// Verify it was stored
		got, err := repo.Get(ctx, "test-task-audit-1")
		if err != nil {
			t.Fatalf("Get() error = %v", err)
		}
		if got.ProjectID != "test-project-1" {
			t.Errorf("got project_id %q, want %q", got.ProjectID, "test-project-1")
		}
		if got.Spec.Prompt != "Build a landing page" {
			t.Errorf("got prompt %q, want %q", got.Spec.Prompt, "Build a landing page")
		}
		if got.Status != domain.BuildStatusPending {
			t.Errorf("got status %q, want %q", got.Status, domain.BuildStatusPending)
		}
	})

	t.Run("records entry with worker ID", func(t *testing.T) {
		entry := &domain.BuildAuditEntry{
			TaskID:    "test-task-audit-2",
			ProjectID: "test-project-1",
			WorkerID:  "worker-1",
			Spec: domain.BuildSpec{
				Prompt: "Run tests",
			},
			Status:    domain.BuildStatusRunning,
			StartedAt: time.Now(),
		}

		err := repo.Record(ctx, entry)
		if err != nil {
			t.Fatalf("Record() error = %v", err)
		}

		got, err := repo.Get(ctx, "test-task-audit-2")
		if err != nil {
			t.Fatalf("Get() error = %v", err)
		}
		if got.WorkerID != "worker-1" {
			t.Errorf("got worker_id %q, want %q", got.WorkerID, "worker-1")
		}
	})
}

func TestBuildAuditRepository_Update(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestBuildAudit(t, db) })

	repo := NewBuildAuditRepository(db)
	ctx := context.Background()

	// Create initial entry
	entry := &domain.BuildAuditEntry{
		TaskID:    "test-task-update-1",
		ProjectID: "test-project-1",
		Spec:      domain.BuildSpec{Prompt: "Build"},
		Status:    domain.BuildStatusPending,
		StartedAt: time.Now(),
	}
	if err := repo.Record(ctx, entry); err != nil {
		t.Fatalf("Record() error = %v", err)
	}

	t.Run("updates with success result", func(t *testing.T) {
		result := &domain.BuildResult{
			Success:      true,
			Output:       "Build successful",
			CommitSHA:    "abc123",
			FilesChanged: []string{"index.html", "style.css"},
			DurationMs:   5000,
		}

		err := repo.Update(ctx, "test-task-update-1", result)
		if err != nil {
			t.Fatalf("Update() error = %v", err)
		}

		got, err := repo.Get(ctx, "test-task-update-1")
		if err != nil {
			t.Fatalf("Get() error = %v", err)
		}
		if got.Status != domain.BuildStatusCompleted {
			t.Errorf("got status %q, want %q", got.Status, domain.BuildStatusCompleted)
		}
		if got.Result == nil {
			t.Fatal("expected result to be set")
		}
		if !got.Result.Success {
			t.Error("expected result.Success = true")
		}
		if got.Result.CommitSHA != "abc123" {
			t.Errorf("got commit_sha %q, want %q", got.Result.CommitSHA, "abc123")
		}
		if got.CompletedAt == nil {
			t.Error("expected completed_at to be set")
		}
	})

	t.Run("updates with failure result", func(t *testing.T) {
		// Create a new entry
		entry := &domain.BuildAuditEntry{
			TaskID:    "test-task-update-2",
			ProjectID: "test-project-1",
			Spec:      domain.BuildSpec{Prompt: "Build"},
			Status:    domain.BuildStatusPending,
			StartedAt: time.Now(),
		}
		if err := repo.Record(ctx, entry); err != nil {
			t.Fatalf("Record() error = %v", err)
		}

		result := &domain.BuildResult{
			Success: false,
			Error:   "compilation error",
		}

		err := repo.Update(ctx, "test-task-update-2", result)
		if err != nil {
			t.Fatalf("Update() error = %v", err)
		}

		got, err := repo.Get(ctx, "test-task-update-2")
		if err != nil {
			t.Fatalf("Get() error = %v", err)
		}
		if got.Status != domain.BuildStatusFailed {
			t.Errorf("got status %q, want %q", got.Status, domain.BuildStatusFailed)
		}
	})

	t.Run("returns error for nonexistent task", func(t *testing.T) {
		result := &domain.BuildResult{Success: true}
		err := repo.Update(ctx, "test-task-nonexistent", result)
		if err == nil {
			t.Error("expected error for nonexistent task")
		}
	})
}

func TestBuildAuditRepository_UpdateStatus(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestBuildAudit(t, db) })

	repo := NewBuildAuditRepository(db)
	ctx := context.Background()

	// Create initial entry
	entry := &domain.BuildAuditEntry{
		TaskID:    "test-task-status-1",
		ProjectID: "test-project-1",
		Spec:      domain.BuildSpec{Prompt: "Build"},
		Status:    domain.BuildStatusPending,
		StartedAt: time.Now(),
	}
	if err := repo.Record(ctx, entry); err != nil {
		t.Fatalf("Record() error = %v", err)
	}

	t.Run("updates status and worker ID", func(t *testing.T) {
		err := repo.UpdateStatus(ctx, "test-task-status-1", domain.BuildStatusRunning, "worker-123")
		if err != nil {
			t.Fatalf("UpdateStatus() error = %v", err)
		}

		got, err := repo.Get(ctx, "test-task-status-1")
		if err != nil {
			t.Fatalf("Get() error = %v", err)
		}
		if got.Status != domain.BuildStatusRunning {
			t.Errorf("got status %q, want %q", got.Status, domain.BuildStatusRunning)
		}
		if got.WorkerID != "worker-123" {
			t.Errorf("got worker_id %q, want %q", got.WorkerID, "worker-123")
		}
	})

	t.Run("updates status with empty worker ID", func(t *testing.T) {
		// Create another entry
		entry := &domain.BuildAuditEntry{
			TaskID:    "test-task-status-2",
			ProjectID: "test-project-1",
			WorkerID:  "old-worker",
			Spec:      domain.BuildSpec{Prompt: "Build"},
			Status:    domain.BuildStatusRunning,
			StartedAt: time.Now(),
		}
		if err := repo.Record(ctx, entry); err != nil {
			t.Fatalf("Record() error = %v", err)
		}

		err := repo.UpdateStatus(ctx, "test-task-status-2", domain.BuildStatusCompleted, "")
		if err != nil {
			t.Fatalf("UpdateStatus() error = %v", err)
		}

		got, err := repo.Get(ctx, "test-task-status-2")
		if err != nil {
			t.Fatalf("Get() error = %v", err)
		}
		if got.Status != domain.BuildStatusCompleted {
			t.Errorf("got status %q, want %q", got.Status, domain.BuildStatusCompleted)
		}
		// WorkerID should be cleared when empty string is passed
		if got.WorkerID != "" {
			t.Errorf("got worker_id %q, want empty", got.WorkerID)
		}
	})

	t.Run("returns error for nonexistent task", func(t *testing.T) {
		err := repo.UpdateStatus(ctx, "test-task-nonexistent", domain.BuildStatusRunning, "worker-1")
		if err == nil {
			t.Error("expected error for nonexistent task")
		}
	})
}

func TestBuildAuditRepository_Get(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestBuildAudit(t, db) })

	repo := NewBuildAuditRepository(db)
	ctx := context.Background()

	t.Run("returns error for nonexistent entry", func(t *testing.T) {
		_, err := repo.Get(ctx, "test-task-nonexistent")
		if err == nil {
			t.Error("expected error for nonexistent entry")
		}
	})
}

func TestBuildAuditRepository_List(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestBuildAudit(t, db) })

	repo := NewBuildAuditRepository(db)
	ctx := context.Background()

	// Create entries for different projects
	entries := []*domain.BuildAuditEntry{
		{TaskID: "test-task-list-1", ProjectID: "test-project-a", Spec: domain.BuildSpec{Prompt: "Build 1"}, Status: domain.BuildStatusCompleted, StartedAt: time.Now()},
		{TaskID: "test-task-list-2", ProjectID: "test-project-a", Spec: domain.BuildSpec{Prompt: "Build 2"}, Status: domain.BuildStatusFailed, StartedAt: time.Now()},
		{TaskID: "test-task-list-3", ProjectID: "test-project-b", Spec: domain.BuildSpec{Prompt: "Build 3"}, Status: domain.BuildStatusPending, StartedAt: time.Now()},
	}
	for _, e := range entries {
		if err := repo.Record(ctx, e); err != nil {
			t.Fatalf("Record() error = %v", err)
		}
	}

	t.Run("filters by project", func(t *testing.T) {
		got, err := repo.List(ctx, port.BuildAuditFilter{
			ProjectID: "test-project-a",
		})
		if err != nil {
			t.Fatalf("List() error = %v", err)
		}
		if len(got) != 2 {
			t.Errorf("got %d entries, want 2", len(got))
		}
	})

	t.Run("filters by status", func(t *testing.T) {
		completed := domain.BuildStatusCompleted
		got, err := repo.List(ctx, port.BuildAuditFilter{
			Status: &completed,
		})
		if err != nil {
			t.Fatalf("List() error = %v", err)
		}
		for _, e := range got {
			if e.Status != domain.BuildStatusCompleted {
				t.Errorf("got status %q, want only completed", e.Status)
			}
		}
	})

	t.Run("respects limit", func(t *testing.T) {
		got, err := repo.List(ctx, port.BuildAuditFilter{
			Limit: 1,
		})
		if err != nil {
			t.Fatalf("List() error = %v", err)
		}
		if len(got) > 1 {
			t.Errorf("got %d entries, want at most 1", len(got))
		}
	})
}
