"""Offline contract tests using the MCP client and a mocked upstream API."""

import os
import unittest
from unittest.mock import patch

import httpx
from mcp import Client

# Tests never read a developer's real GitHub credential or repository setting.
with patch.dict(os.environ, {"GITHUB_REPOSITORY": "example/project", "GITHUB_TOKEN": "test-only"}):
    import server

ISSUE = {
    "number": 17,
    "title": "A sample issue",
    "state": "open",
    "html_url": "https://github.com/example/project/issues/17",
    "body": "Example body",
}


class IssueToolTests(unittest.IsolatedAsyncioTestCase):
    async def call_with_response(self, response, arguments=None):
        requests = []

        def handler(request):
            requests.append(request)
            if isinstance(response, Exception):
                raise response
            return response

        upstream = httpx.AsyncClient(transport=httpx.MockTransport(handler))
        try:
            with patch("server.httpx.AsyncClient", return_value=upstream):
                async with Client(server.mcp) as client:
                    result = await client.call_tool(
                        "get_issue", {"issue_number": 17} if arguments is None else arguments
                    )
        finally:
            await upstream.aclose()
        return result, requests

    async def test_discovery_exposes_only_the_bounded_issue_argument(self):
        async with Client(server.mcp) as client:
            result = await client.list_tools()
        self.assertEqual([tool.name for tool in result.tools], ["get_issue"])
        tool = result.tools[0]
        self.assertEqual(set(tool.input_schema["properties"]), {"issue_number"})
        self.assertEqual(tool.input_schema["properties"]["issue_number"]["minimum"], 1)
        self.assertIsNotNone(tool.output_schema)

    async def test_reads_fixed_repository_with_credential_and_filters_output(self):
        result, requests = await self.call_with_response(httpx.Response(200, json=ISSUE))
        self.assertFalse(result.is_error)
        self.assertEqual(len(requests), 1)
        self.assertEqual(str(requests[0].url), "https://api.github.com/repos/example/project/issues/17")
        self.assertEqual(requests[0].method, "GET")
        self.assertEqual(requests[0].headers["Authorization"], "Bearer test-only")
        self.assertEqual(result.structured_content["kind"], "issue")
        self.assertNotIn("test-only", str(result))

    async def test_invalid_arguments_never_reach_github(self):
        for value in (0, -1, "../secrets", 1.5, True, 2_147_483_648):
            with self.subTest(value=value):
                result, requests = await self.call_with_response(
                    httpx.Response(200, json=ISSUE), {"issue_number": value}
                )
                self.assertTrue(result.is_error)
                self.assertEqual(requests, [])

    async def test_body_is_bounded_and_pull_requests_are_identified(self):
        data = {**ISSUE, "body": "x" * 5000, "pull_request": {}}
        result, _ = await self.call_with_response(httpx.Response(200, json=data))
        self.assertEqual(len(result.structured_content["body"]), 4000)
        self.assertTrue(result.structured_content["body_truncated"])
        self.assertEqual(result.structured_content["kind"], "pull_request")

    async def test_missing_body_and_public_requests_without_token(self):
        with patch.object(server, "TOKEN", None):
            result, requests = await self.call_with_response(
                httpx.Response(200, json={**ISSUE, "body": None})
            )
        self.assertEqual(result.structured_content["body"], "")
        self.assertNotIn("Authorization", requests[0].headers)

    async def test_http_failures_do_not_return_upstream_body_or_follow_redirects(self):
        for status in (301, 401, 403, 404, 410, 429, 500):
            with self.subTest(status=status):
                result, requests = await self.call_with_response(
                    httpx.Response(status, text="test-only", headers={"Location": "https://other.example"})
                )
                self.assertTrue(result.is_error)
                self.assertNotIn("test-only", str(result))
                self.assertEqual(len(requests), 1)

    async def test_network_failure_returns_a_useful_error(self):
        result, _ = await self.call_with_response(httpx.ReadTimeout("test-only"))
        self.assertTrue(result.is_error)
        self.assertIn("Try later", str(result))
        self.assertNotIn("test-only", str(result))

    async def test_bad_upstream_payload_is_a_tool_error(self):
        for response in (httpx.Response(200, text="not json"), httpx.Response(200, json={})):
            result, _ = await self.call_with_response(response)
            self.assertTrue(result.is_error)
            self.assertIn("unexpected response format", str(result))


if __name__ == "__main__":
    unittest.main()
