"""Tests for the Ingest API endpoints."""

import json
from pathlib import Path
from types import SimpleNamespace

import pytest
from fastapi import HTTPException
from httpx import AsyncClient

from app.api.v1 import ingest as ingest_api


@pytest.mark.asyncio
class TestIngestTrigger:
    """POST /api/v1/ingest/trigger — queue document ingestion."""

    async def test_trigger_requires_auth(self, client: AsyncClient, api_prefix: str):
        resp = await client.post(f"{api_prefix}/ingest/trigger", json={
            "doc_id": "test-doc",
            "pdf_path": "/tmp/nonexistent.pdf",
        })
        assert resp.status_code in (401, 403)

    async def test_trigger_missing_pdf(self, client: AsyncClient, api_prefix: str, auth_headers: dict):
        """Triggering ingest with a non-existent PDF should return 400."""
        resp = await client.post(
            f"{api_prefix}/ingest/trigger",
            json={
                "doc_id": "test-doc-missing",
                "pdf_path": "/tmp/absolutely_nonexistent_file.pdf",
            },
            headers=auth_headers,
        )
        assert resp.status_code == 400
        assert "not found" in resp.json()["detail"].lower()

    async def test_trigger_valid_pdf(
        self, client: AsyncClient, api_prefix: str, auth_headers: dict,
        example_pdf_path: Path,
    ):
        """Triggering ingest with a real PDF should return task info."""
        resp = await client.post(
            f"{api_prefix}/ingest/trigger",
            json={
                "doc_id": f"test-ingest-{example_pdf_path.stem[:20]}",
                "pdf_path": str(example_pdf_path),
                "metadata": {
                    "title": "测试文档",
                    "acl_ids": ["A_01"],
                },
            },
            headers=auth_headers,
        )
        assert resp.status_code == 200
        body = resp.json()
        assert "task_id" in body
        assert body["status"] == "PENDING"


@pytest.mark.asyncio
class TestIngestWebhook:
    """POST /api/v1/ingest/webhook/document — OA webhook file upload."""

    async def test_webhook_missing_metadata(self, client: AsyncClient, api_prefix: str):
        """Webhook without metadata should fail."""
        resp = await client.post(f"{api_prefix}/ingest/webhook/document")
        assert resp.status_code == 422

    async def test_webhook_invalid_json(self, client: AsyncClient, api_prefix: str, example_pdf_path: Path):
        """Webhook with invalid JSON metadata should return 400."""
        with open(example_pdf_path, "rb") as f:
            resp = await client.post(
                f"{api_prefix}/ingest/webhook/document",
                data={"metadata": "not-valid-json"},
                files={"file": ("test.pdf", f, "application/pdf")},
            )
        assert resp.status_code == 400

    async def test_webhook_missing_doc_id(self, client: AsyncClient, api_prefix: str, example_pdf_path: Path):
        """Webhook without doc_id in metadata should return 400."""
        meta = json.dumps({"title": "test"})
        with open(example_pdf_path, "rb") as f:
            resp = await client.post(
                f"{api_prefix}/ingest/webhook/document",
                data={"metadata": meta},
                files={"file": ("test.pdf", f, "application/pdf")},
            )
        assert resp.status_code == 400

    async def test_webhook_valid_upload(self, client: AsyncClient, api_prefix: str, example_pdf_path: Path):
        """Valid webhook upload should return task info."""
        meta = json.dumps({
            "doc_id": "webhook-test-001",
            "title": "Webhook 测试文档",
            "issuing_org": "测试单位",
            "acl_ids": ["A_01", "D_01"],
        })
        with open(example_pdf_path, "rb") as f:
            resp = await client.post(
                f"{api_prefix}/ingest/webhook/document",
                data={"metadata": meta},
                files={"file": ("test.pdf", f, "application/pdf")},
            )
        assert resp.status_code == 200
        body = resp.json()
        assert body["doc_id"] == "webhook-test-001"
        assert body["status"] == "queued"
        assert "task_id" in body


@pytest.mark.asyncio
class TestIngestStatus:
    """GET /api/v1/ingest/status/{task_id} — check task status."""

    async def test_status_requires_auth(self, client: AsyncClient, api_prefix: str):
        resp = await client.get(f"{api_prefix}/ingest/status/fake-task-id")
        assert resp.status_code in (401, 403)

    async def test_status_unknown_task(self, client: AsyncClient, api_prefix: str, auth_headers: dict):
        """Checking a non-existent task should still return PENDING."""
        resp = await client.get(
            f"{api_prefix}/ingest/status/nonexistent-task-id-12345",
            headers=auth_headers,
        )
        assert resp.status_code == 200
        body = resp.json()
        assert body["status"] == "PENDING"

    async def test_status_partial_failed_maps_correctly(
        self,
        monkeypatch: pytest.MonkeyPatch,
    ):
        """A successful Celery task with partial_failed should not be downgraded to FAILED."""

        class FakeAsyncResult:
            def __init__(self, task_id: str, app=None):
                self.state = "SUCCESS"
                self.result = {"status": "partial_failed", "error": None}

        monkeypatch.setattr("celery.result.AsyncResult", FakeAsyncResult)

        status = await ingest_api.get_ingest_status(
            task_id="partial-failed-task",
            user=SimpleNamespace(user_id="tester"),
        )
        assert status.status == "PARTIAL_FAILED"


@pytest.mark.asyncio
class TestIngestQueueFailure:
    """Queueing failures should not leave a false-success response."""

    async def test_trigger_queue_failure_returns_503(
        self,
        example_pdf_path: Path,
        monkeypatch: pytest.MonkeyPatch,
    ):
        async def _noop(*args, **kwargs):
            return None

        def _raise_apply_async(**kwargs):
            raise RuntimeError("broker unavailable")

        fake_request = SimpleNamespace(
            app=SimpleNamespace(
                state=SimpleNamespace(es_client=SimpleNamespace(raw=None))
            )
        )
        body = ingest_api.IngestRequest(
            doc_id="queue-failure-test",
            file_path=str(example_pdf_path),
            metadata={"title": "队列失败测试"},
        )

        monkeypatch.setattr(ingest_api, "_write_initial_trace", _noop)
        monkeypatch.setattr(ingest_api, "_record_task_queue_failed", _noop)
        monkeypatch.setattr(ingest_api.ingest_document_task, "apply_async", _raise_apply_async)

        with pytest.raises(HTTPException) as exc_info:
            await ingest_api.trigger_ingest(
                request=fake_request,
                body=body,
                user=SimpleNamespace(user_id="tester"),
            )

        assert exc_info.value.status_code == 503
        assert "queue ingest task" in exc_info.value.detail.lower()


@pytest.mark.asyncio
class TestPermissionWebhook:
    """POST /api/v1/ingest/webhook/permission — ACL update."""

    async def test_permission_empty_acl(self, client: AsyncClient, api_prefix: str):
        """Empty acl_ids should be queued and interpreted as public."""
        resp = await client.post(
            f"{api_prefix}/ingest/webhook/permission",
            json={"doc_id": "test-doc", "acl_ids": []},
        )
        assert resp.status_code == 200
        body = resp.json()
        assert body["status"] == "queued"
        assert "task_id" in body

    async def test_permission_valid(self, client: AsyncClient, api_prefix: str):
        """Valid permission update should be queued."""
        resp = await client.post(
            f"{api_prefix}/ingest/webhook/permission",
            json={"doc_id": "test-doc", "acl_ids": ["A_01", "D_05"]},
        )
        assert resp.status_code == 200
        body = resp.json()
        assert body["status"] == "queued"
        assert "task_id" in body
