from __future__ import annotations

import io
import json
import unittest
from urllib.error import HTTPError
from unittest.mock import patch

import uniserver
from uniserver import UniServerAuthenticationError, UniServerClient


class _FakeResponse:
    def __init__(self, payload: dict | None, status: int = 200):
        self.status = status
        self._payload = payload

    def read(self) -> bytes:
        if self._payload is None:
            return b""
        return json.dumps(self._payload).encode("utf-8")

    def __enter__(self) -> "_FakeResponse":
        return self

    def __exit__(self, exc_type, exc, tb) -> bool:
        return False


class UniServerClientTests(unittest.TestCase):
    def test_health_uses_expected_url_and_method(self) -> None:
        client = UniServerClient(base_url="https://example.com")

        with patch("uniserver.client.urlopen", return_value=_FakeResponse({"status": "ok"})) as mocked:
            payload = client.health()

        self.assertEqual(payload["status"], "ok")
        request = mocked.call_args.args[0]
        self.assertEqual(request.full_url, "https://example.com/health")
        self.assertEqual(request.get_method(), "GET")

    def test_list_kv_sends_account_and_project_headers(self) -> None:
        client = UniServerClient(base_url="https://example.com", account_api_key="uacc_test")
        client.set_project_api_key(9, "unis_project")

        with patch("uniserver.client.urlopen", return_value=_FakeResponse({"kv": {}})) as mocked:
            client.list_kv(9)

        request = mocked.call_args.args[0]
        headers = {key.lower(): value for key, value in request.header_items()}

        self.assertEqual(headers["x-uniserver-account-key"], "uacc_test")
        self.assertEqual(headers["x-project-api-key"], "unis_project")

    def test_create_project_remembers_first_project_key(self) -> None:
        client = UniServerClient(base_url="https://example.com", account_api_key="uacc_test")

        response = {
            "id": 41,
            "name": "demo",
            "apiKeys": [{"key": "unis_41_first", "scopes": ["read", "write"]}],
        }

        with patch("uniserver.client.urlopen", return_value=_FakeResponse(response)):
            created = client.create_project("demo")

        self.assertEqual(created["id"], 41)
        self.assertEqual(client.get_project_api_key(41), "unis_41_first")

    def test_http_401_maps_to_authentication_error(self) -> None:
        client = UniServerClient(base_url="https://example.com", account_api_key="uacc_invalid")
        payload = b'{"error":"authentication_error","message":"Invalid key."}'

        http_error = HTTPError(
            url="https://example.com/v1/accounts/me",
            code=401,
            msg="Unauthorized",
            hdrs=None,
            fp=io.BytesIO(payload),
        )

        with patch("uniserver.client.urlopen", side_effect=http_error):
            with self.assertRaises(UniServerAuthenticationError):
                client.get_account()

    def test_package_level_functions_share_default_client_state(self) -> None:
        create_project_response = {
            "id": 7,
            "name": "pkg-project",
            "apiKeys": [{"key": "unis_7_default", "scopes": ["read", "write"]}],
        }
        put_kv_response = {"value": "hello", "version": 1}

        with patch(
            "uniserver.client.urlopen",
            side_effect=[
                _FakeResponse(create_project_response),
                _FakeResponse(put_kv_response),
            ],
        ) as mocked:
            uniserver.configure(base_url="https://example.com", account_api_key="uacc_pkg")
            project = uniserver.create_project("pkg-project")
            result = uniserver.put_kv(project["id"], "hello", "hello")

        self.assertEqual(result["version"], 1)

        second_request = mocked.call_args_list[1].args[0]
        headers = {key.lower(): value for key, value in second_request.header_items()}
        self.assertEqual(headers["x-uniserver-account-key"], "uacc_pkg")
        self.assertEqual(headers["x-project-api-key"], "unis_7_default")


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