package httpserver_test

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"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"
	httpserver "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/http"
)

type mockMetadataReader struct {
	repo   domain.Repository
	snap   domain.Snapshot
	run    domain.IndexingRun
	repoOK bool
	snapOK bool
	runOK  bool
}

func (m *mockMetadataReader) GetLatestIndexingRun(repoID string) (domain.IndexingRun, bool) {
	if !m.runOK || m.run.RepoID != repoID {
		return domain.IndexingRun{}, false
	}
	return m.run, true
}

func (m *mockMetadataReader) GetIndexingRunBySnapshot(repoID, snapshotID string) (domain.IndexingRun, bool) {
	if !m.runOK || m.run.RepoID != repoID || m.run.SnapshotID != snapshotID {
		return domain.IndexingRun{}, false
	}
	return m.run, true
}

func (m *mockMetadataReader) ListIndexingDiagnostics(repoID, snapshotID string, limit int) ([]domain.IndexingDiagnostic, bool) {
	if !m.runOK || m.run.RepoID != repoID || m.run.SnapshotID != snapshotID {
		return nil, false
	}
	return []domain.IndexingDiagnostic{
		{
			DiagnosticID: "diag_test",
			RepoID:       repoID,
			SnapshotID:   snapshotID,
			Stage:        "code_parse",
			SourceType:   "file",
			SourceID:     "src/main.go",
			Code:         "parse_failed",
			Severity:     domain.DiagnosticSeverityError,
			Message:      "parse failed",
			CreatedAt:    time.Date(2026, 6, 17, 12, 0, 0, 0, time.UTC),
		},
	}, true
}

func (m *mockMetadataReader) GetRepository(repoID string) (domain.Repository, bool) {
	if !m.repoOK || m.repo.RepoID != repoID {
		return domain.Repository{}, false
	}
	return m.repo, true
}

func (m *mockMetadataReader) GetSnapshotMetadata(repoID, snapshotID string) (domain.Snapshot, bool) {
	if !m.snapOK || m.snap.RepoID != repoID || m.snap.SnapshotID != snapshotID {
		return domain.Snapshot{}, false
	}
	return m.snap, true
}

func sampleRepository() domain.Repository {
	ts := time.Date(2026, 5, 26, 12, 0, 0, 0, time.UTC)
	return domain.Repository{
		RepoID:        "ad/example-repo",
		CloneURL:      "https://bit.admedia.com/scm/ad/example-repo.git",
		DefaultRef:    "master",
		LastCommitSHA: "abc123",
		LastSyncAt:    ts,
		Status:        domain.RepositoryStatusActive,
		UpdatedAt:     ts,
	}
}

func sampleSnapshot() domain.Snapshot {
	ts := time.Date(2026, 5, 26, 12, 0, 0, 0, time.UTC)
	return domain.Snapshot{
		SnapshotID: "snap_test12345678",
		RepoID:     "ad/example-repo",
		CommitSHA:  "abc123",
		Ref:        "master",
		FileCount:  42,
		Status:     domain.SnapshotStatusReady,
		CreatedAt:  ts,
	}
}

func TestGetRepositorySuccess(t *testing.T) {
	reader := &mockMetadataReader{repo: sampleRepository(), repoOK: true}
	server := httpserver.New("repo-sync-service", func() bool { return true }, nil, nil, nil, reader, nil, nil, "")

	req := httptest.NewRequest(http.MethodGet, "/repos/ad%2Fexample-repo", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", rec.Code, rec.Body.String())
	}

	var resp apicontract.RepositoryResponse
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode: %v", err)
	}
	if resp.RepoID != "ad/example-repo" || resp.LastCommitSHA != "abc123" || resp.DefaultRef != "master" {
		t.Fatalf("unexpected response: %+v", resp)
	}
}

func TestGetRepositoryNotFound(t *testing.T) {
	server := httpserver.New("repo-sync-service", func() bool { return true }, nil, nil, nil, &mockMetadataReader{}, nil, nil, "")

	req := httptest.NewRequest(http.MethodGet, "/repos/ad%2Fmissing-repo", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusNotFound {
		t.Fatalf("status: got %d", rec.Code)
	}
}

func TestGetRepositoryInvalidRepoID(t *testing.T) {
	server := httpserver.New("repo-sync-service", func() bool { return true }, nil, nil, nil, &mockMetadataReader{}, nil, nil, "")

	req := httptest.NewRequest(http.MethodGet, "/repos/invalid", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusBadRequest {
		t.Fatalf("status: got %d", rec.Code)
	}
}

func TestGetSnapshotSuccess(t *testing.T) {
	reader := &mockMetadataReader{snap: sampleSnapshot(), snapOK: true}
	server := httpserver.New("repo-sync-service", func() bool { return true }, nil, nil, nil, reader, nil, nil, "")

	req := httptest.NewRequest(http.MethodGet, "/repos/ad%2Fexample-repo/snapshots/snap_test12345678", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", rec.Code, rec.Body.String())
	}

	var resp apicontract.SnapshotResponse
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode: %v", err)
	}
	if resp.SnapshotID != "snap_test12345678" || resp.FileCount != 42 || resp.CommitSHA != "abc123" {
		t.Fatalf("unexpected response: %+v", resp)
	}
}

func TestGetSnapshotNotFound(t *testing.T) {
	server := httpserver.New("repo-sync-service", func() bool { return true }, nil, nil, nil, &mockMetadataReader{}, nil, nil, "")

	req := httptest.NewRequest(http.MethodGet, "/repos/ad%2Fexample-repo/snapshots/snap_missing", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusNotFound {
		t.Fatalf("status: got %d", rec.Code)
	}
}

func TestGetSnapshotInvalidRepoID(t *testing.T) {
	server := httpserver.New("repo-sync-service", func() bool { return true }, nil, nil, nil, &mockMetadataReader{snapOK: true, snap: sampleSnapshot()}, nil, nil, "")

	req := httptest.NewRequest(http.MethodGet, "/repos/invalid/snapshots/snap_test12345678", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusBadRequest {
		t.Fatalf("status: got %d", rec.Code)
	}
}

func TestGetLatestIndexingRunSuccess(t *testing.T) {
	run := domain.IndexingRun{
		IndexingRunID:      "run_test",
		RepoID:             "ad/example-repo",
		SnapshotID:         "snap_test12345678",
		Status:             domain.IndexingRunStatusCompleted,
		ExpectedCodeFiles:  100,
		ProcessedCodeFiles: 100,
		FailedFiles:        2,
		Stages: domain.IndexingRunStages{
			RepoSync:      domain.StageStatusCompleted,
			CodeParse:     domain.StageStatusCompleted,
			GraphFinalize: domain.StageStatusCompleted,
			Embedding:     domain.StageStatusCompleted,
		},
		StartedAt: time.Date(2026, 6, 17, 12, 0, 0, 0, time.UTC),
	}
	reader := &mockMetadataReader{run: run, runOK: true}
	server := httpserver.New("repo-sync-service", func() bool { return true }, nil, nil, nil, reader, nil, nil, "")

	req := httptest.NewRequest(http.MethodGet, "/repos/ad%2Fexample-repo/indexing-runs/latest", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", rec.Code, rec.Body.String())
	}

	var resp apicontract.IndexingRunResponse
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	if resp.PipelineStatus != domain.IndexingRunStatusDegraded {
		t.Fatalf("pipeline_status: got %q, want degraded", resp.PipelineStatus)
	}
	if resp.Progress["code_parse"].FailureRate != 0.02 {
		t.Fatalf("failure_rate: got %v, want 0.02", resp.Progress["code_parse"].FailureRate)
	}
}
