"""Tests for the ingestion API endpoints (api.ingestion)."""

from __future__ import annotations

from unittest.mock import AsyncMock

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from api.ingestion import router
from domain.models import AnalysisJob
from services.ingestion_service import IngestionResult


@pytest.fixture
def mock_ingestion_service() -> AsyncMock:
    svc = AsyncMock()
    svc.ingest.return_value = IngestionResult(
        doc_id="doc-1", chunks_indexed=5, embedding_dimension=384
    )
    svc.delete_document.return_value = 3
    return svc


@pytest.fixture
def mock_analysis_service() -> AsyncMock:
    svc = AsyncMock()
    job = AnalysisJob(document_id="doc-1")
    job.document_filename = "test.pdf"
    job.mark_running()
    job.mark_completed(
        markdown="# Test",
        html="<h1>Test</h1>",
        pages_json="[]",
        document_json='{"doc": true}',
        chunks_json='[{"text": "hello"}]',
    )
    svc.find_by_id.return_value = job
    return svc


@pytest.fixture
def client(mock_ingestion_service: AsyncMock, mock_analysis_service: AsyncMock) -> TestClient:
    app = FastAPI()
    app.include_router(router)
    app.state.ingestion_service = mock_ingestion_service
    app.state.analysis_service = mock_analysis_service
    return TestClient(app)


class TestIngestAnalysis:
    def test_ingest_success(self, client: TestClient) -> None:
        resp = client.post("/api/ingestion/job-1")
        assert resp.status_code == 200
        data = resp.json()
        assert data["docId"] == "doc-1"
        assert data["chunksIndexed"] == 5
        assert data["embeddingDimension"] == 384

    def test_ingest_not_found(self, client: TestClient, mock_analysis_service: AsyncMock) -> None:
        mock_analysis_service.find_by_id.return_value = None
        resp = client.post("/api/ingestion/missing")
        assert resp.status_code == 404

    def test_ingest_not_completed(
        self, client: TestClient, mock_analysis_service: AsyncMock
    ) -> None:
        job = AnalysisJob(document_id="doc-1")
        mock_analysis_service.find_by_id.return_value = job
        resp = client.post("/api/ingestion/job-1")
        assert resp.status_code == 400

    def test_ingest_no_chunks(self, client: TestClient, mock_analysis_service: AsyncMock) -> None:
        job = AnalysisJob(document_id="doc-1")
        job.mark_running()
        job.mark_completed(markdown="x", html="x", pages_json="[]")
        mock_analysis_service.find_by_id.return_value = job
        resp = client.post("/api/ingestion/job-1")
        assert resp.status_code == 400


class TestDeleteIngested:
    def test_delete_success(self, client: TestClient) -> None:
        resp = client.delete("/api/ingestion/doc-1")
        assert resp.status_code == 204


class TestIngestionStatus:
    def test_available(self, client: TestClient) -> None:
        resp = client.get("/api/ingestion/status")
        assert resp.status_code == 200
        assert resp.json()["available"] is True

    def test_not_available(self) -> None:
        app = FastAPI()
        app.include_router(router)
        app.state.ingestion_service = None
        app.state.analysis_service = AsyncMock()
        tc = TestClient(app)
        resp = tc.get("/api/ingestion/status")
        assert resp.status_code == 200
        assert resp.json()["available"] is False


class TestIngestionDisabled:
    def test_router_not_mounted_returns_404(self) -> None:
        """When ingestion service is None, router should not be mounted → 404."""
        app = FastAPI()
        # Do NOT include ingestion router — simulates main.py conditional mount
        app.state.ingestion_service = None
        app.state.analysis_service = AsyncMock()
        tc = TestClient(app)
        resp = tc.post("/api/ingestion/job-1")
        assert resp.status_code == 404

    def test_status_still_returns_503_when_router_mounted_but_service_none(self) -> None:
        """If router is mounted but service is None, endpoints return 503."""
        app = FastAPI()
        app.include_router(router)
        app.state.ingestion_service = None
        app.state.analysis_service = AsyncMock()
        tc = TestClient(app)
        resp = tc.post("/api/ingestion/job-1")
        assert resp.status_code == 503


class TestStatusOpenSearch:
    def test_status_with_opensearch_connected(
        self, client: TestClient, mock_ingestion_service: AsyncMock
    ) -> None:
        mock_ingestion_service.ping.return_value = True
        resp = client.get("/api/ingestion/status")
        assert resp.status_code == 200
        data = resp.json()
        assert data["available"] is True
        assert data["opensearchConnected"] is True

    def test_status_with_opensearch_disconnected(
        self, client: TestClient, mock_ingestion_service: AsyncMock
    ) -> None:
        mock_ingestion_service.ping.return_value = False
        resp = client.get("/api/ingestion/status")
        assert resp.status_code == 200
        data = resp.json()
        assert data["available"] is True
        assert data["opensearchConnected"] is False


class TestSearchEndpoint:
    def test_search_success(self, client: TestClient, mock_ingestion_service: AsyncMock) -> None:
        from domain.vector_schema import IndexedChunk, SearchResult

        chunk = IndexedChunk(
            doc_id="doc-1",
            filename="test.pdf",
            content="hello world",
            embedding=[],
            chunk_index=0,
            chunk_type="text",
            page_number=1,
            headings=["Intro"],
        )
        mock_ingestion_service.search_fulltext.return_value = [
            SearchResult(chunk=chunk, score=0.95)
        ]
        resp = client.get("/api/ingestion/search", params={"q": "hello"})
        assert resp.status_code == 200
        data = resp.json()
        assert data["total"] == 1
        assert data["query"] == "hello"
        assert data["results"][0]["content"] == "hello world"
        assert data["results"][0]["score"] == 0.95

    def test_search_empty_query(self, client: TestClient) -> None:
        resp = client.get("/api/ingestion/search", params={"q": ""})
        assert resp.status_code == 422

    def test_search_with_doc_filter(
        self, client: TestClient, mock_ingestion_service: AsyncMock
    ) -> None:
        mock_ingestion_service.search_fulltext.return_value = []
        resp = client.get("/api/ingestion/search", params={"q": "test", "doc_id": "doc-1"})
        assert resp.status_code == 200
        mock_ingestion_service.search_fulltext.assert_awaited_once_with(
            "test", k=20, doc_id="doc-1"
        )
