package httpserver_test

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	contractevents "bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/contracts/events"
	httpserver "bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/http"
	"bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/service/parser"
	"bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/store"
)

type mockJobReader struct {
	jobs         map[string]parser.ParseJob
	diagnostics  map[string][]contractevents.ParserDiagnostic
}

func (m *mockJobReader) GetParseJob(jobID string) (parser.ParseJob, bool) {
	job, ok := m.jobs[jobID]
	return job, ok
}

func (m *mockJobReader) GetJobDiagnostics(jobID string) ([]contractevents.ParserDiagnostic, bool) {
	if _, ok := m.jobs[jobID]; !ok {
		return nil, false
	}
	diagnostics, ok := m.diagnostics[jobID]
	if !ok {
		return []contractevents.ParserDiagnostic{}, true
	}
	return diagnostics, true
}

type mockArtifactReader struct {
	metadata map[string]store.ArtifactMetadata
	err      error
}

func (m *mockArtifactReader) GetArtifactMetadata(_ context.Context, artifactID string) (store.ArtifactMetadata, error) {
	if m.err != nil {
		return store.ArtifactMetadata{}, m.err
	}
	metadata, ok := m.metadata[artifactID]
	if !ok {
		return store.ArtifactMetadata{}, store.ErrArtifactNotFound
	}
	return metadata, nil
}

func newDebugServer(t *testing.T, jobs *mockJobReader, artifacts *mockArtifactReader) *httpserver.Server {
	t.Helper()

	debug := httpserver.DebugAPI{}
	if jobs != nil {
		debug.Jobs = jobs
	}
	if artifacts != nil {
		debug.Artifacts = artifacts
	}
	return httpserver.New("code-parser-service", func() bool { return true }, debug)
}

func TestHandleParseJobFound(t *testing.T) {
	jobs := &mockJobReader{
		jobs: map[string]parser.ParseJob{
			"job_1": {
				JobID:      "job_1",
				RepoID:     "ad/example",
				SnapshotID: "snap_1",
				CommitSHA:  "abc123",
				FilePath:   "main.go",
				Language:   "go",
				Status:     parser.JobStatusCompleted,
				ArtifactID: "art_1",
			},
		},
	}
	server := newDebugServer(t, jobs, nil)

	req := httptest.NewRequest(http.MethodGet, "/parse-jobs/job_1", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

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

	var job parser.ParseJob
	if err := json.Unmarshal(rec.Body.Bytes(), &job); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if job.JobID != "job_1" || job.ArtifactID != "art_1" {
		t.Fatalf("unexpected job: %+v", job)
	}
}

func TestHandleParseJobNotFound(t *testing.T) {
	server := newDebugServer(t, &mockJobReader{jobs: map[string]parser.ParseJob{}}, nil)

	req := httptest.NewRequest(http.MethodGet, "/parse-jobs/missing", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

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

func TestHandleDiagnosticsFound(t *testing.T) {
	jobs := &mockJobReader{
		jobs: map[string]parser.ParseJob{
			"job_1": {JobID: "job_1", Status: parser.JobStatusFailed},
		},
		diagnostics: map[string][]contractevents.ParserDiagnostic{
			"job_1": {
				{
					Code:     "parse_failed",
					Message:  "syntax error",
					Severity: contractevents.DiagnosticSeverityError,
					FilePath: "main.go",
					Line:     3,
				},
			},
		},
	}
	server := newDebugServer(t, jobs, nil)

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

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

	var response struct {
		JobID       string                            `json:"job_id"`
		Diagnostics []contractevents.ParserDiagnostic `json:"diagnostics"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if response.JobID != "job_1" || len(response.Diagnostics) != 1 {
		t.Fatalf("unexpected response: %+v", response)
	}
}

func TestHandleDiagnosticsEmptyForCompletedJob(t *testing.T) {
	jobs := &mockJobReader{
		jobs: map[string]parser.ParseJob{
			"job_1": {JobID: "job_1", Status: parser.JobStatusCompleted},
		},
	}
	server := newDebugServer(t, jobs, nil)

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

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

	var response struct {
		Diagnostics []contractevents.ParserDiagnostic `json:"diagnostics"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if len(response.Diagnostics) != 0 {
		t.Fatalf("expected empty diagnostics, got %d", len(response.Diagnostics))
	}
}

func TestHandleDiagnosticsNotFound(t *testing.T) {
	server := newDebugServer(t, &mockJobReader{jobs: map[string]parser.ParseJob{}}, nil)

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

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

func TestHandleArtifactMetadataFound(t *testing.T) {
	artifacts := &mockArtifactReader{
		metadata: map[string]store.ArtifactMetadata{
			"art_1": {
				ArtifactID:    "art_1",
				SchemaVersion: "v1",
				RepoID:        "ad/example",
				SnapshotID:    "snap_1",
				CommitSHA:     "abc123",
				FilePath:      "main.go",
				NodeCount:     3,
				EdgeCount:     2,
				ArtifactURI:   "file:///tmp/art_1.json",
			},
		},
	}
	server := newDebugServer(t, nil, artifacts)

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

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

	var metadata store.ArtifactMetadata
	if err := json.Unmarshal(rec.Body.Bytes(), &metadata); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	if metadata.ArtifactID != "art_1" || metadata.NodeCount != 3 {
		t.Fatalf("unexpected metadata: %+v", metadata)
	}
}

func TestHandleArtifactMetadataNotFound(t *testing.T) {
	server := newDebugServer(t, nil, &mockArtifactReader{metadata: map[string]store.ArtifactMetadata{}})

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

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

func TestHandleArtifactMetadataInvalidID(t *testing.T) {
	artifacts := &mockArtifactReader{err: store.ErrInvalidArtifactID}
	server := newDebugServer(t, nil, artifacts)

	req := httptest.NewRequest(http.MethodGet, "/artifacts/bad%2Fid", nil)
	rec := httptest.NewRecorder()
	server.Handler().ServeHTTP(rec, req)

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