"""Update-check regression for Issue #104.

STABLE_RELEASE_API used limit=1, so it only ever saw the single newest
release on Gitea regardless of type. Since nightly/dev prereleases publish
far more often than stable releases, that newest release is almost always a
prerelease - the stable_releases filter (not prerelease) then found nothing
and /api/update/check returned "no stable releases found" even though older
stable releases exist.
"""
from unittest.mock import AsyncMock, MagicMock, patch

import pytest


@pytest.mark.asyncio
async def test_update_apply_invalid_json_returns_400(client):
    """A malformed/non-JSON body must be a clean 400, not an unhandled 500
    with a raw JSONDecodeError traceback (code review finding)."""
    c, _ = client
    resp = await c.post("/api/update/apply", data=b"not json", headers={"Content-Type": "application/json"})
    assert resp.status == 400


@pytest.mark.asyncio
async def test_update_check_testing_channel_is_docker_only(client):
    """A testing-<sha> build has no Gitea releases at all - the check must
    report a docker-only channel with nothing to update, NOT fall through to
    the stable path and wrongly offer a stable "update". Must not even call
    the Gitea API."""
    c, bridge = client
    bridge._read_version = lambda: "testing-2e4dbf0"

    # Patch the API so that if the handler wrongly tried to fetch releases,
    # the test would notice (mock returns something, but the handler must not
    # reach it).
    with patch("aiohttp.ClientSession.get") as mock_get:
        resp = await c.get("/api/update/check")
        data = await resp.json()

    assert resp.status == 200
    assert data["update_available"] is False
    assert data["docker_only"] is True
    assert data["current"] == "testing-2e4dbf0"
    mock_get.assert_not_called()  # no Gitea round-trip for the testing channel


@pytest.mark.asyncio
async def test_update_apply_testing_channel_blocked(client):
    """Self-update must be refused on the testing channel, same as nightly -
    testing images are delivered via Docker only."""
    c, bridge = client
    bridge._read_version = lambda: "testing-2e4dbf0"

    resp = await c.post("/api/update/apply", json={"tag": "whatever"})
    data = await resp.json()
    assert resp.status == 400
    assert "testing" in data["error"]
    assert "docker" in data["error"].lower()


def _fake_releases_response(payload):
    resp = MagicMock()
    resp.status = 200
    resp.json = AsyncMock(return_value=payload)
    ctx = MagicMock()
    ctx.__aenter__ = AsyncMock(return_value=resp)
    ctx.__aexit__ = AsyncMock(return_value=False)
    return ctx


@pytest.mark.asyncio
async def test_stable_update_check_finds_release_behind_newer_prereleases(client):
    c, bridge = client
    bridge._read_version = lambda: "0.9.27"

    releases = (
        [{"tag_name": f"nightly-0.9.30-nightly{i}", "prerelease": True} for i in range(1, 7)]
        + [{"tag_name": "v0.9.29", "prerelease": False, "body": "changelog"}]
    )

    with patch("aiohttp.ClientSession.get", return_value=_fake_releases_response(releases)):
        resp = await c.get("/api/update/check")
        data = await resp.json()

    assert resp.status == 200
    assert data["latest"] == "0.9.29"
    assert data["update_available"] is True


@pytest.mark.asyncio
async def test_stable_update_check_requests_enough_releases_to_skip_prereleases(client):
    """The API URL itself must ask for more than the single newest release -
    a limit=1 request can never find a stable release behind a run of
    prereleases no matter how the response is parsed."""
    c, bridge = client
    bridge._read_version = lambda: "0.9.27"

    import re
    assert not re.search(r"limit=1(?!\d)", bridge.STABLE_RELEASE_API), (
        "STABLE_RELEASE_API must request more than 1 release, otherwise a "
        "recent nightly/dev prerelease being the newest release hides all "
        "stable releases behind it (Issue #104)"
    )
