package api_test

import (
	"testing"
	"time"

	apicontract "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/api"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/domain"
)

func completedRun() domain.IndexingRun {
	completedAt := time.Date(2026, 6, 17, 14, 0, 0, 0, time.UTC)
	return domain.IndexingRun{
		Status: domain.IndexingRunStatusCompleted,
		Stages: domain.IndexingRunStages{
			RepoSync:      domain.StageStatusCompleted,
			CodeParse:     domain.StageStatusCompleted,
			GraphFinalize: domain.StageStatusCompleted,
			Embedding:     domain.StageStatusCompleted,
		},
		CompletedAt: &completedAt,
	}
}

func TestDeriveMinimalIndexingStatus(t *testing.T) {
	tests := []struct {
		name   string
		run    domain.IndexingRun
		hasRun bool
		want   string
	}{
		{
			name:   "no run",
			hasRun: false,
			want:   apicontract.IndexedRepoStatusIndexing,
		},
		{
			name: "ready when all required stages complete",
			run:  completedRun(),
			hasRun: true,
			want: apicontract.IndexedRepoStatusReady,
		},
		{
			name: "failed when run status failed",
			run: domain.IndexingRun{
				Status: domain.IndexingRunStatusFailed,
			},
			hasRun: true,
			want: apicontract.IndexedRepoStatusFailed,
		},
		{
			name: "failed when embedding stage failed",
			run: domain.IndexingRun{
				Status: domain.IndexingRunStatusRunning,
				Stages: domain.IndexingRunStages{
					RepoSync:  domain.StageStatusCompleted,
					CodeParse: domain.StageStatusCompleted,
					Embedding: domain.StageStatusFailed,
				},
				Error: "embedding batch failed",
			},
			hasRun: true,
			want: apicontract.IndexedRepoStatusFailed,
		},
		{
			name: "indexing when pipeline incomplete",
			run: domain.IndexingRun{
				Status: domain.IndexingRunStatusRunning,
				Stages: domain.IndexingRunStages{
					RepoSync:  domain.StageStatusCompleted,
					CodeParse: domain.StageStatusRunning,
				},
			},
			hasRun: true,
			want: apicontract.IndexedRepoStatusIndexing,
		},
		{
			name: "ready even with partial failures when stages complete",
			run: domain.IndexingRun{
				Status:      domain.IndexingRunStatusCompleted,
				FailedFiles: 3,
				Stages: domain.IndexingRunStages{
					RepoSync:      domain.StageStatusCompleted,
					CodeParse:     domain.StageStatusCompleted,
					GraphFinalize: domain.StageStatusCompleted,
					Embedding:     domain.StageStatusCompleted,
				},
			},
			hasRun: true,
			want: apicontract.IndexedRepoStatusReady,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := apicontract.DeriveMinimalIndexingStatus(tt.run, tt.hasRun)
			if got != tt.want {
				t.Fatalf("status: got %q, want %q", got, tt.want)
			}
		})
	}
}

func TestRepoNameFromID(t *testing.T) {
	if got := apicontract.RepoNameFromID("ad/example-repo.com"); got != "example-repo.com" {
		t.Fatalf("repo name: got %q", got)
	}
}

func TestIndexedRepoFromDomainLastIndexedAt(t *testing.T) {
	syncAt := time.Date(2026, 6, 17, 10, 0, 0, 0, time.UTC)
	repo := domain.Repository{
		RepoID:     "ad/example-repo.com",
		CloneURL:   "https://bit.admedia.com/scm/ad/example-repo.com.git",
		LastSyncAt: syncAt,
	}

	t.Run("uses completed_at when available", func(t *testing.T) {
		run := completedRun()
		resp := apicontract.IndexedRepoFromDomain(repo, run, true)
		if resp.LastIndexedAt == nil || *resp.LastIndexedAt != "2026-06-17T14:00:00Z" {
			t.Fatalf("last_indexed_at: got %v", resp.LastIndexedAt)
		}
	})

	t.Run("null when no run and no sync", func(t *testing.T) {
		emptyRepo := domain.Repository{RepoID: "ad/new-repo.com"}
		resp := apicontract.IndexedRepoFromDomain(emptyRepo, domain.IndexingRun{}, false)
		if resp.LastIndexedAt != nil {
			t.Fatalf("expected null last_indexed_at, got %v", resp.LastIndexedAt)
		}
	})
}

func TestIndexedRepoFromDomainFailureDetails(t *testing.T) {
	repo := domain.Repository{
		RepoID:   "ad/api.search.com",
		CloneURL: "https://bit.admedia.com/scm/ad/api.search.com.git",
	}
	run := domain.IndexingRun{
		SnapshotID: "snap_abc123",
		Status:     domain.IndexingRunStatusFailed,
		Error:      "save delta: upsert graph delta: an inserted document is too large",
		Stages: domain.IndexingRunStages{
			RepoSync:    domain.StageStatusCompleted,
			CodeParse:   domain.StageStatusCompleted,
			CommitIntel: domain.StageStatusFailed,
		},
	}

	resp := apicontract.IndexedRepoFromDomain(repo, run, true)
	if resp.Status != apicontract.IndexedRepoStatusFailed {
		t.Fatalf("status: got %q", resp.Status)
	}
	if resp.Error != run.Error {
		t.Fatalf("error: got %q", resp.Error)
	}
	if resp.FailedStage != "commit_intel" {
		t.Fatalf("failed_stage: got %q", resp.FailedStage)
	}
	if resp.LatestSnapshotID != "snap_abc123" {
		t.Fatalf("latest_snapshot_id: got %q", resp.LatestSnapshotID)
	}
}

func TestSortIndexedReposByLastIndexedAt(t *testing.T) {
	older := "2026-06-17T10:00:00Z"
	newer := "2026-06-17T14:00:00Z"
	repos := []apicontract.IndexedRepoResponse{
		{RepoID: "ad/older", LastIndexedAt: &older},
		{RepoID: "ad/newer", LastIndexedAt: &newer},
	}
	fallback := map[string]time.Time{
		"ad/older": time.Date(2026, 6, 17, 10, 0, 0, 0, time.UTC),
		"ad/newer": time.Date(2026, 6, 17, 14, 0, 0, 0, time.UTC),
	}

	apicontract.SortIndexedReposByLastIndexedAt(repos, fallback)
	if repos[0].RepoID != "ad/newer" {
		t.Fatalf("expected newer repo first, got %s", repos[0].RepoID)
	}
}
