import asyncio

import httpx
import pytest

from src import gitea_proxy, main


@pytest.mark.anyio
async def test_notification_detail_returns_core_without_loading_conversation(monkeypatch):
    requested_pages = []

    def comment(comment_id):
        return {
            "id": comment_id,
            "user": {"login": f"user-{comment_id}"},
            "body": f"message {comment_id}",
            "created_at": f"2026-08-06T12:{comment_id:02d}:00Z",
            "html_url": f"https://forge.example/stackchain/api/issues/7#issuecomment-{comment_id}",
        }

    def handler(request):
        if request.url.path.endswith("/notifications/threads/42"):
            return httpx.Response(200, json={
                "repository": {"full_name": "stackchain/api"},
                "subject": {
                    "type": "Issue", "title": "Retry failed deploy", "state": "open",
                    "url": "https://forge.example/api/v1/repos/stackchain/api/issues/7",
                    "latest_comment_url": "https://forge.example/api/v1/repos/stackchain/api/issues/comments/47",
                    "html_url": "https://forge.example/stackchain/api/issues/7",
                },
            })
        if request.url.path.endswith("/repos/stackchain/api/issues/7/comments"):
            page = int(request.url.params["page"])
            requested_pages.append(page)
            comments = [comment(i) for i in (range(1, 21) if page == 1 else range(41, 48))]
            return httpx.Response(200, json=comments, headers={"X-Total-Count": "47"})
        if request.url.path.endswith("/repos/stackchain/api/issues/7"):
            return httpx.Response(200, json={
                "number": 7,
                "state": "open",
                "assignees": [],
                "body": "Deploy fails after retries.",
            })
        if request.url.path.endswith("/repos/stackchain/api/issues/comments/47"):
            return httpx.Response(200, json=comment(47))
        raise AssertionError(f"unexpected request: {request.url}")

    monkeypatch.setattr(gitea_proxy, "GITEA_URL", "https://forge.example")
    gitea_proxy.start_client(transport=httpx.MockTransport(handler))
    try:
        result = await gitea_proxy.notification_detail(42)
    finally:
        await gitea_proxy.stop_client()

    assert requested_pages == []
    assert result["conversation_available"] is True
    assert "conversation" not in result
    assert result["issue"] == {
        "number": 7,
        "assignees": [],
        "claimable": True,
    }
    assert result["acknowledge_supported"] is True


@pytest.mark.anyio
async def test_notification_detail_api_is_bounded_and_never_cacheable(monkeypatch):
    calls = []

    async def detail(thread_id):
        calls.append(thread_id)
        return {
            "id": thread_id,
            "repository": "stackchain/api",
            "title": "Retry failed deploy",
            "subject_type": "Issue",
            "state": "open",
            "url": "https://forge.example/stackchain/api/issues/7#issuecomment-9",
            "subject_body": "Deploy fails after three retries.",
            "latest_comment": {
                "author": "alexander",
                "body": "Logs point to the worker timeout.",
                "created_at": "2026-08-06T12:30:00Z",
                "url": "https://forge.example/stackchain/api/issues/7#issuecomment-9",
            },
        }

    monkeypatch.setattr(main, "notification_detail", detail, raising=False)
    transport = httpx.ASGITransport(app=main.app)
    async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.get("/api/v1/notifications/42")
        invalid = await client.get("/api/v1/notifications/0")

    assert response.status_code == 200
    assert response.json()["latest_comment"]["author"] == "alexander"
    assert response.headers["cache-control"] == "no-store"
    assert invalid.status_code == 422
    assert calls == [42]


@pytest.mark.anyio
async def test_notification_conversation_api_loads_one_bounded_older_page(monkeypatch):
    calls = []

    async def conversation(thread_id, page, limit):
        calls.append((thread_id, page, limit))
        return {
            "comments": [{"id": 21, "author": "timmy", "body": "Earlier context"}],
            "page": page,
            "older_page": 1,
            "total": 47,
        }

    monkeypatch.setattr(main.gitea_proxy, "notification_conversation_page", conversation, raising=False)
    transport = httpx.ASGITransport(app=main.app)
    async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.get("/api/v1/notifications/42/conversation?page=2&limit=20")
        invalid = await client.get("/api/v1/notifications/42/conversation?page=0&limit=51")

    assert response.status_code == 200
    assert response.json()["comments"][0]["id"] == 21
    assert response.headers["cache-control"] == "no-store"
    assert invalid.status_code == 422
    assert calls == [(42, 2, 20)]


@pytest.mark.anyio
async def test_notification_conversation_api_opens_newest_page_when_page_is_omitted(monkeypatch):
    calls = []

    async def conversation(thread_id, page, limit):
        calls.append((thread_id, page, limit))
        return {"comments": [{"id": 47}], "page": 3, "older_page": 2, "total": 47}

    monkeypatch.setattr(main.gitea_proxy, "notification_conversation_page", conversation)
    transport = httpx.ASGITransport(app=main.app)
    async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.get("/api/v1/notifications/42/conversation?limit=20")

    assert response.status_code == 200
    assert response.json()["page"] == 3
    assert calls == [(42, None, 20)]


@pytest.mark.anyio
async def test_notification_detail_timeout_is_sanitized_and_retryable(monkeypatch):
    async def detail(_thread_id):
        await asyncio.sleep(0.05)

    monkeypatch.setattr(main, "notification_detail", detail)
    monkeypatch.setattr(main, "NOTIFICATION_DETAIL_TIMEOUT_SECONDS", 0.01)
    transport = httpx.ASGITransport(app=main.app, raise_app_exceptions=False)
    async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.get("/api/v1/notifications/42")

    assert response.status_code == 503
    assert response.json() == {"error": "Loading the update timed out. Please retry."}
    assert response.headers["retry-after"] == "1"
    assert response.headers["cache-control"] == "no-store"


@pytest.mark.anyio
async def test_notification_detail_upstream_failure_does_not_leak_exception(monkeypatch):
    async def detail(_thread_id):
        raise httpx.HTTPError("token=secret upstream exploded")

    monkeypatch.setattr(main, "notification_detail", detail)
    transport = httpx.ASGITransport(app=main.app, raise_app_exceptions=False)
    async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
        response = await client.get("/api/v1/notifications/42")

    assert response.status_code == 503
    assert response.json() == {
        "error": "The update is temporarily unavailable. Please retry."
    }
    assert "secret" not in response.text
    assert response.headers["retry-after"] == "1"
