"""Tests for air.AIR client with mocked HTTP responses."""

import json

import httpx
import pytest

from air import AIR, AIRError, AuthError, Project


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _make_client(handler):
    """Create an AIR client whose HTTP transport is replaced by *handler*.

    *handler* receives an ``httpx.Request`` and must return an
    ``httpx.Response``.
    """
    client = AIR(api_key="air_k1_test")
    client._client = httpx.Client(
        base_url=client._base_url,
        headers={"Authorization": "Bearer air_k1_test"},
        transport=httpx.MockTransport(handler),
    )
    return client


def _json_response(data, status_code=200):
    return httpx.Response(status_code, json=data)


# ---------------------------------------------------------------------------
# Auth
# ---------------------------------------------------------------------------

class TestAuth:
    def test_requires_api_key(self, monkeypatch):
        monkeypatch.delenv("AIR_API_KEY", raising=False)
        with pytest.raises(AuthError, match="API key required"):
            AIR(api_key="")

    def test_requires_api_key_no_env(self, monkeypatch):
        monkeypatch.delenv("AIR_API_KEY", raising=False)
        with pytest.raises(AuthError):
            AIR()

    def test_auth_error_on_401(self):
        def handler(request):
            return _json_response({"detail": "Invalid token"}, 401)

        client = _make_client(handler)
        with pytest.raises(AuthError, match="Invalid token"):
            client.keywords("test")

    def test_api_error_on_500(self):
        def handler(request):
            return _json_response({"detail": "Internal error"}, 500)

        client = _make_client(handler)
        with pytest.raises(AIRError, match="Internal error") as exc_info:
            client.keywords("test")
        assert exc_info.value.status_code == 500


# ---------------------------------------------------------------------------
# Context manager
# ---------------------------------------------------------------------------

class TestContextManager:
    def test_context_manager_closes(self):
        client = _make_client(lambda r: _json_response({}))
        with client as c:
            assert isinstance(c, AIR)
        # After exiting, the transport is closed — further requests will fail
        with pytest.raises(Exception):
            c.health()


# ---------------------------------------------------------------------------
# Health
# ---------------------------------------------------------------------------

class TestHealth:
    def test_health(self):
        def handler(request):
            assert request.url.path == "/api/health"
            return _json_response({"status": "ok"})

        client = _make_client(handler)
        assert client.health() == {"status": "ok"}


# ---------------------------------------------------------------------------
# Standalone tools
# ---------------------------------------------------------------------------

class TestKeywords:
    def test_keywords(self):
        def handler(request):
            body = json.loads(request.content)
            assert body["text"] == "dark matter"
            assert body["n_keywords"] == 3
            assert body["kw_type"] == "aas"
            return _json_response({"keywords": ["cosmology", "lensing", "CMB"]})

        client = _make_client(handler)
        result = client.keywords("dark matter", n=3, kw_type="aas")
        assert result == ["cosmology", "lensing", "CMB"]

    def test_keywords_defaults(self):
        def handler(request):
            body = json.loads(request.content)
            assert body["n_keywords"] == 5
            assert body["kw_type"] == "unesco"
            return _json_response({"keywords": ["a"]})

        client = _make_client(handler)
        client.keywords("test")


class TestArxiv:
    def test_arxiv(self):
        def handler(request):
            body = json.loads(request.content)
            assert "arxiv" in body["text"]
            return _json_response({"result": {"downloaded": 1}})

        client = _make_client(handler)
        result = client.arxiv("https://arxiv.org/abs/2301.12345")
        assert result == {"downloaded": 1}


class TestEnhance:
    def test_enhance(self):
        def handler(request):
            body = json.loads(request.content)
            assert body["max_workers"] == 2
            assert body["max_depth"] == 10
            return _json_response({"enhanced_text": "enhanced!"})

        client = _make_client(handler)
        result = client.enhance("input text", max_workers=2, max_depth=10)
        assert result == "enhanced!"


class TestOcr:
    def test_ocr(self):
        def handler(request):
            body = json.loads(request.content)
            assert body["file_path"] == "/tmp/paper.pdf"
            return _json_response({"result": {"pages": 5}})

        client = _make_client(handler)
        result = client.ocr("/tmp/paper.pdf")
        assert result == {"pages": 5}


class TestReview:
    def test_review_polls_until_completed(self, tmp_path):
        call_count = 0

        def handler(request):
            nonlocal call_count
            call_count += 1

            if request.method == "POST" and "/api/v1/review" in str(request.url):
                body = json.loads(request.content)
                assert body["file_path"] == "/tmp/paper.pdf"
                assert body["thoroughness"] == "High"
                assert body["figures_review"] is True
                assert body["verify_statements"] is False
                assert body["review_maths"] is True
                assert body["review_numerics"] is False
                return _json_response({"task_id": "rev_001"})

            if request.method == "GET" and "/api/v1/tasks/rev_001" in str(request.url):
                if "result" in str(request.url):
                    return _json_response({
                        "result": {"result": {"report": "looks good", "report_pdf_base64": ""}}
                    })
                # First poll returns pending, second returns completed
                if call_count <= 3:
                    return _json_response({"status": "pending"})
                return _json_response({"status": "completed"})

            return _json_response({"detail": "not found"}, 404)

        client = _make_client(handler)
        result = client.review(
            "/tmp/paper.pdf",
            thoroughness="High",
            figures_review=True,
            review_maths=True,
            poll_interval=0.0,
            output_dir=str(tmp_path / "review_out"),
        )
        assert result["review"] == "looks good"
        assert result["review_md_path"].endswith("review.md")
        assert result["review_pdf_path"] == ""
        import pathlib
        assert pathlib.Path(result["review_md_path"]).read_text() == "looks good"

    def test_review_with_pdf(self, tmp_path):
        """Verify that a base64-encoded PDF is saved locally."""
        import base64

        pdf_b64 = base64.b64encode(b"%PDF-1.4 fake").decode("ascii")

        def handler(request):
            if request.method == "POST":
                return _json_response({"task_id": "rev_pdf"})
            if "result" in str(request.url):
                return _json_response({
                    "result": {"result": {"report": "review md", "report_pdf_base64": pdf_b64}}
                })
            return _json_response({"status": "completed"})

        client = _make_client(handler)
        result = client.review(
            "/tmp/paper.pdf",
            poll_interval=0.0,
            output_dir=str(tmp_path / "review_pdf"),
        )
        assert result["review"] == "review md"
        assert result["review_pdf_path"].endswith("review.pdf")
        import pathlib
        assert pathlib.Path(result["review_pdf_path"]).read_bytes() == b"%PDF-1.4 fake"

    def test_review_raises_on_failure(self):
        def handler(request):
            if request.method == "POST":
                return _json_response({"task_id": "rev_fail"})
            if "result" in str(request.url):
                return _json_response({"error": "OCR failed"})
            return _json_response({"status": "failed"})

        client = _make_client(handler)
        with pytest.raises(AIRError, match="failed"):
            client.review("/tmp/bad.pdf", poll_interval=0.0)

    def test_review_timeout(self):
        def handler(request):
            if request.method == "POST":
                return _json_response({"task_id": "rev_slow"})
            return _json_response({"status": "pending"})

        client = _make_client(handler)
        with pytest.raises(AIRError, match="timed out"):
            client.review("/tmp/slow.pdf", timeout=0.0, poll_interval=0.0)

    def test_review_sends_optional_fields(self, tmp_path):
        def handler(request):
            if request.method == "POST":
                body = json.loads(request.content)
                assert body["emails"] == ["a@b.com"]
                return _json_response({"task_id": "rev_002"})
            if "result" in str(request.url):
                return _json_response({
                    "result": {"result": {"report": "done", "report_pdf_base64": ""}}
                })
            return _json_response({"status": "completed"})

        client = _make_client(handler)
        result = client.review(
            "/tmp/paper.pdf",
            emails=["a@b.com"],
            poll_interval=0.0,
            output_dir=str(tmp_path / "review_opt"),
        )
        assert result["review"] == "done"


# ---------------------------------------------------------------------------
# Project management
# ---------------------------------------------------------------------------

class TestProjectManagement:
    def test_create_project(self):
        def handler(request):
            if request.method == "POST":
                body = json.loads(request.content)
                assert body["name"] == "my-proj"
                assert body["data_description"] == "desc"
                return _json_response({"name": "my-proj"})
            # GET /api/v1/projects/my-proj (list_files from __init__)
            return _json_response({"files": []})

        client = _make_client(handler)
        proj = client.create_project("my-proj", data_description="desc")
        assert isinstance(proj, Project)
        assert proj.name == "my-proj"

    def test_get_project(self):
        def handler(request):
            assert "/api/v1/projects/existing" in str(request.url)
            return _json_response({"name": "existing"})

        client = _make_client(handler)
        proj = client.get_project("existing")
        assert proj.name == "existing"

    def test_list_projects(self):
        def handler(request):
            return _json_response({"projects": ["a", "b"]})

        client = _make_client(handler)
        assert client.list_projects() == ["a", "b"]

    def test_delete_project(self):
        def handler(request):
            assert request.method == "DELETE"
            return _json_response({"deleted": True})

        client = _make_client(handler)
        result = client.delete_project("old")
        assert result == {"deleted": True}


# ---------------------------------------------------------------------------
# Repr
# ---------------------------------------------------------------------------

class TestRepr:
    def test_repr(self):
        client = _make_client(lambda r: _json_response({}))
        assert "AIR(base_url=" in repr(client)
