import importlib
import json
from unittest.mock import AsyncMock, MagicMock

import httpx
import pytest

http_client = importlib.import_module("gjalla_precommit.protocol.http_client")


def _mock_async_client(monkeypatch, response: MagicMock):
    mock_client = AsyncMock()
    mock_client.get = AsyncMock(return_value=response)
    mock_client.post = AsyncMock(return_value=response)

    mock_client_class = MagicMock()
    mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None)
    monkeypatch.setattr(http_client.httpx, "AsyncClient", mock_client_class)
    return mock_client


@pytest.mark.asyncio
async def test_list_projects_async_success(monkeypatch):
    response = MagicMock()
    response.status_code = 200
    response.json.return_value = {"projects": [{"id": 1, "title": "Project"}]}
    mock_client = _mock_async_client(monkeypatch, response)

    result = await http_client.list_projects_async("gj_test", "https://api.gjalla.io")

    assert result == [{"id": 1, "title": "Project"}]
    mock_client.get.assert_called_once()


@pytest.mark.asyncio
async def test_list_projects_async_invalid_json(monkeypatch):
    response = MagicMock()
    response.status_code = 200
    response.json.side_effect = json.JSONDecodeError("bad", "doc", 1)
    _mock_async_client(monkeypatch, response)

    result = await http_client.list_projects_async("gj_test", "https://api.gjalla.io")

    assert result is None


@pytest.mark.asyncio
async def test_fetch_context_async_errors(monkeypatch):
    response = MagicMock()
    response.status_code = 404
    response.text = "nope"
    _mock_async_client(monkeypatch, response)

    with pytest.raises(RuntimeError, match="not found or access denied"):
        await http_client.fetch_context_async(999, "gj_test", "https://api.gjalla.io")


@pytest.mark.asyncio
async def test_fetch_context_async_invalid_json(monkeypatch):
    response = MagicMock()
    response.status_code = 200
    response.json.side_effect = json.JSONDecodeError("bad", "doc", 1)
    _mock_async_client(monkeypatch, response)

    with pytest.raises(RuntimeError, match="Invalid JSON response"):
        await http_client.fetch_context_async(1, "gj_test", "https://api.gjalla.io")


@pytest.mark.asyncio
async def test_upload_receipt_async_success(monkeypatch):
    response = MagicMock()
    response.status_code = 201
    mock_client = _mock_async_client(monkeypatch, response)

    result = await http_client.upload_receipt_async({"id": "r1"}, "gj_test", "https://api.gjalla.io")

    assert result is True
    mock_client.post.assert_called_once()


@pytest.mark.asyncio
async def test_upload_receipt_async_network_error(monkeypatch):
    mock_client = AsyncMock()
    mock_client.post = AsyncMock(side_effect=httpx.RequestError("boom"))

    mock_client_class = MagicMock()
    mock_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client)
    mock_client_class.return_value.__aexit__ = AsyncMock(return_value=None)
    monkeypatch.setattr(http_client.httpx, "AsyncClient", mock_client_class)

    result = await http_client.upload_receipt_async({"id": "r1"}, "gj_test", "https://api.gjalla.io")

    assert result is False


@pytest.mark.asyncio
async def test_lookup_project_by_url_async_matches(monkeypatch):
    async def fake_list_projects(api_key, api_url):
        return [{"id": 1, "title": "example-repo"}]

    monkeypatch.setattr(http_client, "list_projects_async", fake_list_projects)

    result = await http_client.lookup_project_by_url_async(
        "https://github.com/org/example-repo.git",
        "gj_test",
        "https://api.gjalla.io",
    )

    assert result == {"id": 1, "name": "example-repo"}


@pytest.mark.asyncio
async def test_lookup_project_by_url_async_no_match(monkeypatch):
    async def fake_list_projects(api_key, api_url):
        return [{"id": 1, "title": "different"}]

    monkeypatch.setattr(http_client, "list_projects_async", fake_list_projects)

    result = await http_client.lookup_project_by_url_async(
        "ssh://example.com/repo",
        "gj_test",
        "https://api.gjalla.io",
    )

    assert result is None


@pytest.mark.asyncio
async def test_create_rule_async_error_codes(monkeypatch):
    response = MagicMock()
    response.status_code = 400
    response.json.return_value = {"error": {"message": "bad"}}
    _mock_async_client(monkeypatch, response)

    with pytest.raises(RuntimeError, match="bad"):
        await http_client.create_rule_async(1, "invariant", "rule", "gj_test", "https://api.gjalla.io")


@pytest.mark.asyncio
async def test_create_project_async_invalid_json(monkeypatch):
    response = MagicMock()
    response.status_code = 201
    response.json.side_effect = json.JSONDecodeError("bad", "doc", 1)
    _mock_async_client(monkeypatch, response)

    with pytest.raises(RuntimeError, match="Invalid JSON response"):
        await http_client.create_project_async("Title", "gj_test", "https://api.gjalla.io")


def test_verify_api_key_checks_list_projects(monkeypatch):
    monkeypatch.setattr(http_client, "list_projects", lambda *_args, **_kwargs: [])
    assert http_client.verify_api_key("gj_test", "https://api.gjalla.io") is True

    monkeypatch.setattr(http_client, "list_projects", lambda *_args, **_kwargs: None)
    assert http_client.verify_api_key("gj_test", "https://api.gjalla.io") is False
