"""Tests for air.Project with mocked HTTP responses."""

import json

import httpx
import pytest

from air import AIRError, TaskTimeoutError
from air.project import Project


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

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


def _make_project(handler, name="test-proj", poll_interval=0.0):
    """Create a Project backed by a mock transport."""

    def _wrapped_handler(request):
        path = request.url.path
        # Handle list_files / project info GETs (from __init__, _auto_pull, etc.)
        # Delegate to real handler; if it errors, return empty files as fallback.
        if (
            request.method == "GET"
            and "/files/" not in path
            and "/tasks/" not in path
            and path.rstrip("/") == f"/api/v1/projects/{name}"
        ):
            try:
                resp = handler(request)
                if resp.status_code < 400:
                    return resp
            except Exception:
                pass
            return _json_response({"files": []})
        return handler(request)

    client = httpx.Client(
        base_url="http://localhost:8000",
        headers={"Authorization": "Bearer air_k1_test"},
        transport=httpx.MockTransport(_wrapped_handler),
    )
    return Project(name, client, poll_interval=poll_interval)


def _pipeline_handler(step_path, output_file_path, output_content):
    """Return a handler that simulates a pipeline step.

    Handles the POST (submit) -> GET poll (completed) -> GET result ->
    GET file sequence.
    """

    def handler(request):
        path = request.url.path
        method = request.method

        # GET /api/v1/projects/<name> (list_files / project info)
        if method == "GET" and "/files/" not in path and path.rstrip("/").endswith("/test-proj"):
            return _json_response({
                "files": [{"relative_path": output_file_path, "size": len(output_content), "mtime": 0}]
            })

        # POST /api/v1/projects/<name>/<step>
        if method == "POST" and step_path in path:
            return _json_response({"task_id": "task_abc123"})

        # GET /api/v1/tasks/task_abc123
        if method == "GET" and "/tasks/task_abc123" in path and "/result" not in path:
            return _json_response({"status": "completed"})

        # GET /api/v1/tasks/task_abc123/result
        if method == "GET" and "/tasks/task_abc123/result" in path:
            return _json_response({"result": "ok"})

        # GET /api/v1/projects/<name>/files/<path>
        if method == "GET" and "/files/" in path:
            return _json_response({"content": output_content})

        return _json_response({"detail": f"Unexpected: {method} {path}"}, 404)

    return handler


# ---------------------------------------------------------------------------
# Pipeline steps
# ---------------------------------------------------------------------------

class TestIdea:
    def test_idea(self):
        handler = _pipeline_handler("/idea", "Iteration0/input_files/idea.md", "# Great idea")
        proj = _make_project(handler)
        result = proj.idea(mode="fast", timeout=10)
        assert result == "# Great idea"


class TestLiterature:
    def test_literature(self):
        handler = _pipeline_handler("/literature", "Iteration0/input_files/literature.md", "# Lit review")
        proj = _make_project(handler)
        result = proj.literature(timeout=10)
        assert result == "# Lit review"


class TestMethods:
    def test_methods(self):
        handler = _pipeline_handler("/methods", "Iteration0/input_files/methods.md", "# Methods")
        proj = _make_project(handler)
        result = proj.methods(mode="fast", timeout=10)
        assert result == "# Methods"


class TestPaper:
    def test_paper(self):
        handler = _pipeline_handler("/paper", "Iteration0/paper_output/paper_v4_final.tex", "\\documentclass{}")
        proj = _make_project(handler)
        result = proj.paper(journal="AAS", timeout=10)
        assert result == "\\documentclass{}"


class TestReview:
    def test_review(self, tmp_path):
        handler = _pipeline_handler("/review", "Iteration0/input_files/referee.md", "# Review")
        proj = _make_project(handler)
        proj._local_dir = str(tmp_path)
        result = proj.review(timeout=10)
        assert isinstance(result, dict)
        assert result["review"] == "# Review"
        assert result["review_md_path"].endswith("referee.md")
        import pathlib
        assert pathlib.Path(result["review_md_path"]).read_text() == "# Review"


# ---------------------------------------------------------------------------
# Polling edge cases
# ---------------------------------------------------------------------------

class TestPolling:
    def test_task_pending_then_completed(self):
        """Task returns 'pending' once before 'completed'."""
        calls = {"n": 0}

        def handler(request):
            path = request.url.path
            method = request.method

            if method == "POST" and "/idea" in path:
                return _json_response({"task_id": "task_slow"})

            if method == "GET" and "/tasks/task_slow" in path and "/result" not in path:
                calls["n"] += 1
                if calls["n"] <= 1:
                    return _json_response({"status": "pending"})
                return _json_response({"status": "completed"})

            if method == "GET" and "/tasks/task_slow/result" in path:
                return _json_response({"result": "done"})

            if method == "GET" and "/files/" in path:
                return _json_response({"content": "idea text"})

            return _json_response({}, 404)

        proj = _make_project(handler)
        result = proj.idea(timeout=10)
        assert result == "idea text"
        assert calls["n"] == 2

    def test_task_timeout(self):
        """Task never completes -> TaskTimeoutError."""
        def handler(request):
            path = request.url.path
            method = request.method

            if method == "POST" and "/idea" in path:
                return _json_response({"task_id": "task_forever"})

            if method == "GET" and "/tasks/task_forever" in path:
                return _json_response({"status": "pending"})

            return _json_response({}, 404)

        proj = _make_project(handler)
        with pytest.raises(TaskTimeoutError) as exc_info:
            proj.idea(timeout=0.01)
        assert exc_info.value.task_id == "task_forever"

    def test_task_failed(self):
        """Task fails on the backend -> AIRError."""
        def handler(request):
            path = request.url.path
            method = request.method

            if method == "POST" and "/idea" in path:
                return _json_response({"task_id": "task_fail"})

            if method == "GET" and "/tasks/task_fail" in path and "/result" not in path:
                return _json_response({"status": "failed"})

            if method == "GET" and "/tasks/task_fail/result" in path:
                return _json_response({"error": "OOM"})

            return _json_response({}, 404)

        proj = _make_project(handler)
        with pytest.raises(AIRError, match="OOM"):
            proj.idea(timeout=10)


# ---------------------------------------------------------------------------
# File access
# ---------------------------------------------------------------------------

class TestFileAccess:
    def test_get_file(self):
        def handler(request):
            assert "/files/notes.md" in str(request.url)
            return _json_response({"content": "hello"})

        proj = _make_project(handler)
        assert proj.get_file("notes.md") == "hello"

    def test_list_files(self):
        def handler(request):
            return _json_response({
                "files": [
                    {"relative_path": "a.md", "size": 100, "mtime": 0},
                    {"relative_path": "b.md", "size": 200, "mtime": 0},
                ]
            })

        proj = _make_project(handler)
        files = proj.list_files()
        assert len(files) == 2
        assert files[0]["relative_path"] == "a.md"

    def test_write_file(self):
        def handler(request):
            body = json.loads(request.content)
            assert body["path"] == "data.csv"
            assert body["content"] == "a,b\n1,2"
            assert body["encoding"] == "utf-8"
            return _json_response({"ok": True})

        proj = _make_project(handler)
        result = proj.write_file("data.csv", "a,b\n1,2")
        assert result == {"ok": True}


# ---------------------------------------------------------------------------
# Delete
# ---------------------------------------------------------------------------

class TestDelete:
    def test_delete(self):
        def handler(request):
            assert request.method == "DELETE"
            assert "/projects/test-proj" in str(request.url)
            return _json_response({"deleted": True})

        proj = _make_project(handler)
        assert proj.delete() == {"deleted": True}


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

class TestRepr:
    def test_repr(self):
        proj = _make_project(lambda r: _json_response({}))
        assert repr(proj) == "Project('test-proj')"
