from __future__ import annotations

import asyncio
import json
from dataclasses import dataclass
from typing import Any

import pytest

from reddit_mcp.reddit_client import RedditClient


@dataclass
class FakeResponse:
    status_code: int
    text: str
    headers: dict[str, str] | None = None

    def __post_init__(self) -> None:
        if self.headers is None:
            self.headers = {}


class FakeSession:
    def __init__(self, responses: list[FakeResponse]) -> None:
        self._responses = responses
        self.calls: list[dict[str, Any]] = []

    async def get(self, url: str, **kwargs: Any) -> FakeResponse:
        self.calls.append({"url": url, **kwargs})
        if not self._responses:
            raise AssertionError("No fake responses left")
        return self._responses.pop(0)

    async def close(self) -> None:
        return None


def _run(coro: Any) -> Any:
    return asyncio.run(coro)


def _client_with_responses(responses: list[FakeResponse]) -> tuple[RedditClient, FakeSession]:
    client = RedditClient({"reddit_session": "old-session"})
    fake_session = FakeSession(responses)
    client._session = fake_session  # type: ignore[assignment]
    return client, fake_session


def test_invalid_input_is_rejected_before_network_calls() -> None:
    client, session = _client_with_responses([])

    with pytest.raises(ValueError, match="Invalid subreddit"):
        _run(client.get_subreddit_posts("bad subreddit!"))
    with pytest.raises(ValueError, match="Invalid post_id"):
        _run(client.get_post_content("bad post id!"))
    with pytest.raises(ValueError, match="Invalid subreddit"):
        _run(client.search_reddit("ai", subreddit="bad subreddit!"))
    with pytest.raises(ValueError, match="Invalid subreddit"):
        _run(client.get_subreddit_info("bad subreddit!"))

    assert session.calls == []


def test_batch_tools_collect_invalid_inputs_as_errors() -> None:
    client, session = _client_with_responses([])

    post_batch = _run(client.batch_get_posts_info(["bad post id!"]))
    sub_batch = _run(client.batch_get_subreddits_info(["bad subreddit!"]))

    assert post_batch["returned"] == 0
    assert post_batch["errors"] and post_batch["errors"][0]["input"] == "bad post id!"
    assert sub_batch["returned"] == 0
    assert sub_batch["errors"] and sub_batch["errors"][0]["input"] == "bad subreddit!"
    assert session.calls == []


def test_auth_expired_403_with_failed_auto_refresh_raises(monkeypatch: pytest.MonkeyPatch) -> None:
    client, _session = _client_with_responses([
        FakeResponse(status_code=403, text="forbidden"),
    ])

    async def fake_auto_refresh() -> None:
        return None

    monkeypatch.setattr("reddit_mcp.cookie_manager.auto_refresh_cookies", fake_auto_refresh)

    with pytest.raises(RuntimeError, match="auto-refresh failed"):
        _run(client._request("/api/me.json", {}))


def test_auth_expired_403_refreshes_and_retries(monkeypatch: pytest.MonkeyPatch) -> None:
    client, session = _client_with_responses([
        FakeResponse(status_code=403, text="forbidden"),
        FakeResponse(status_code=200, text=json.dumps({"ok": True})),
    ])

    async def fake_auto_refresh() -> dict[str, str]:
        return {"reddit_session": "new-session"}

    monkeypatch.setattr("reddit_mcp.cookie_manager.auto_refresh_cookies", fake_auto_refresh)

    payload = _run(client._request("/api/me.json", {}))

    assert payload == {"ok": True}
    assert client._cookies["reddit_session"] == "new-session"
    assert len(session.calls) == 2


def test_rate_limited_429_twice_raises(monkeypatch: pytest.MonkeyPatch) -> None:
    client, _session = _client_with_responses([
        FakeResponse(status_code=429, text="rate limited"),
        FakeResponse(status_code=429, text="rate limited again"),
    ])
    client.rate_reset = 0

    async def fast_sleep(_seconds: float) -> None:
        return None

    monkeypatch.setattr("reddit_mcp.reddit_client.asyncio.sleep", fast_sleep)

    with pytest.raises(RuntimeError, match="Rate limited"):
        _run(client._request("/api/me.json", {}))


def test_not_found_404_raises() -> None:
    client, _session = _client_with_responses([
        FakeResponse(status_code=404, text="not found"),
    ])

    with pytest.raises(RuntimeError, match="Reddit returned 404"):
        _run(client._request("/api/me.json", {}))
