from __future__ import annotations

import asyncio
import importlib
import json
from typing import Any

import pytest


class StubClient:
    def __init__(self, fail: set[str] | dict[str, str] | None = None) -> None:
        self.fail = fail or set()

    def _maybe_fail(self, key: str) -> None:
        if isinstance(self.fail, dict):
            if key in self.fail:
                raise RuntimeError(self.fail[key])
            return
        if key in self.fail:
            raise RuntimeError(f"{key} failed")

    async def get_subreddit_posts(self, **_kwargs: object) -> dict[str, Any]:
        self._maybe_fail("get_subreddit_posts")
        return {"posts": [{"id": "p1"}], "after": None}

    async def get_post_content(self, **_kwargs: object) -> dict[str, Any]:
        self._maybe_fail("get_post_content")
        return {"post": {"id": "p1"}, "comments": [], "has_more": False}

    async def search_reddit(self, **_kwargs: object) -> dict[str, Any]:
        self._maybe_fail("search_reddit")
        return {"results": [{"id": "r1"}], "after": None}

    async def get_subreddit_info(self, **_kwargs: object) -> dict[str, Any]:
        self._maybe_fail("get_subreddit_info")
        return {"name": "python", "subscribers": 1}

    async def batch_get_posts_info(self, **_kwargs: object) -> dict[str, Any]:
        self._maybe_fail("batch_get_posts")
        return {"results": [{"_input": "abc"}], "errors": [], "requested": 1, "returned": 1}

    async def batch_get_subreddits_info(self, **_kwargs: object) -> dict[str, Any]:
        self._maybe_fail("batch_get_subreddits")
        return {"results": [{"_input": "python"}], "errors": [], "requested": 1, "returned": 1}

    async def check_me(self) -> dict[str, Any]:
        self._maybe_fail("check_cookies")
        return {"username": "tester"}

    def get_rate_status(self) -> dict[str, Any]:
        self._maybe_fail("get_rate_limit_status")
        return {"remaining": 88.0, "reset_seconds": 60, "used": 12.0}


def _load_server_module():
    import reddit_mcp.server as server_module

    return importlib.reload(server_module)


def _register_authed_tools(
    server_module,
    monkeypatch: pytest.MonkeyPatch,
    *,
    fail: set[str] | dict[str, str] | None = None,
) -> None:
    server_module._client = StubClient(fail=fail)
    monkeypatch.setattr(server_module, "cookies_age_warning", lambda: None)
    monkeypatch.setattr(server_module, "load_cookies", lambda: {"reddit_session": "ok"})
    monkeypatch.setattr(server_module, "get_cookie_age_days", lambda: 2.0)

    async def fake_check_cookies_valid(_cookies: dict[str, str]) -> bool:
        if fail and "check_cookies" in fail:
            if isinstance(fail, dict):
                raise RuntimeError(fail["check_cookies"])
            raise RuntimeError("check_cookies failed")
        return True

    monkeypatch.setattr(server_module, "check_cookies_valid", fake_check_cookies_valid)

    server_module._register_data_tools()
    server_module._register_management_tools()


def _call_tool_json(server_module, name: str, args: dict[str, Any]) -> dict[str, Any]:
    result = asyncio.run(server_module.mcp.call_tool(name, args))
    raw = result.structured_content["result"]
    return json.loads(raw)


@pytest.mark.parametrize(
    ("tool_name", "args", "expected_key"),
    [
        ("get_subreddit_posts", {"subreddit": "python"}, "posts"),
        ("get_post_content", {"post_id": "abc123"}, "post"),
        ("search_reddit", {"query": "python"}, "results"),
        ("get_subreddit_info", {"subreddit": "python"}, "name"),
        ("batch_get_posts", {"inputs": ["abc123"]}, "results"),
        ("batch_get_subreddits", {"inputs": ["python"]}, "results"),
        ("check_cookies", {}, "valid"),
        ("get_rate_limit_status", {}, "remaining"),
    ],
)
def test_tools_happy_path(
    monkeypatch: pytest.MonkeyPatch,
    tool_name: str,
    args: dict[str, Any],
    expected_key: str,
) -> None:
    server_module = _load_server_module()
    _register_authed_tools(server_module, monkeypatch)

    payload = _call_tool_json(server_module, tool_name, args)

    assert expected_key in payload


@pytest.mark.parametrize(
    ("tool_name", "args", "fail_key"),
    [
        ("get_subreddit_posts", {"subreddit": "python"}, "get_subreddit_posts"),
        ("get_post_content", {"post_id": "abc123"}, "get_post_content"),
        ("search_reddit", {"query": "python"}, "search_reddit"),
        ("get_subreddit_info", {"subreddit": "python"}, "get_subreddit_info"),
        ("batch_get_posts", {"inputs": ["abc123"]}, "batch_get_posts"),
        ("batch_get_subreddits", {"inputs": ["python"]}, "batch_get_subreddits"),
        ("check_cookies", {}, "check_cookies"),
        ("get_rate_limit_status", {}, "get_rate_limit_status"),
    ],
)
def test_tools_error_path_returns_error_payload(
    monkeypatch: pytest.MonkeyPatch,
    tool_name: str,
    args: dict[str, Any],
    fail_key: str,
) -> None:
    server_module = _load_server_module()
    _register_authed_tools(server_module, monkeypatch, fail={fail_key})

    payload = _call_tool_json(server_module, tool_name, args)

    assert "error" in payload


@pytest.mark.parametrize(
    ("tool_name", "args"),
    [
        ("get_subreddit_posts", {"subreddit": "python"}),
        ("get_post_content", {"post_id": "abc123"}),
        ("search_reddit", {"query": "python"}),
        ("get_subreddit_info", {"subreddit": "python"}),
        ("batch_get_posts", {"inputs": ["abc123"]}),
        ("batch_get_subreddits", {"inputs": ["python"]}),
        ("check_cookies", {}),
        ("get_rate_limit_status", {}),
    ],
)
@pytest.mark.parametrize(
    "failure_message",
    [
        "Invalid input: malformed identifier",
        "Auth expired: token invalid",
        "Rate limited by Reddit even after retry",
        "Reddit returned 404: not found",
    ],
)
def test_tools_surface_domain_failures(
    monkeypatch: pytest.MonkeyPatch,
    tool_name: str,
    args: dict[str, Any],
    failure_message: str,
) -> None:
    server_module = _load_server_module()
    _register_authed_tools(server_module, monkeypatch, fail={tool_name: failure_message})

    payload = _call_tool_json(server_module, tool_name, args)

    assert "error" in payload
    assert failure_message in payload["error"]
