import json
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch

import pytest
import requests

from backend.magic_plan.magic_plan_client import MagicPlanClient
from datatypes.magicplan.api.response import MagicPlanPlan, PlanSummary

FIXTURE_DIR = Path(__file__).parents[2] / "magic_plan"
BASE_URL = "https://cloud.magicplan.app/api/v2"
CUSTOMER_ID = "test-customer"
API_KEY = "test-key"


def _load_fixture(name: str) -> dict[str, Any]:
    return json.loads((FIXTURE_DIR / name).read_text())


def _make_client(mock_session: MagicMock) -> MagicPlanClient:
    mock_session.headers = {}
    with patch(
        "backend.magic_plan.magic_plan_client.requests.Session",
        return_value=mock_session,
    ):
        return MagicPlanClient(customer_id=CUSTOMER_ID, api_key=API_KEY)


@pytest.fixture()
def mock_session() -> MagicMock:
    return MagicMock()


@pytest.fixture()
def client(mock_session: MagicMock) -> MagicPlanClient:
    return _make_client(mock_session)


# --- constructor ---


def test_customer_header_set_on_session(mock_session: MagicMock) -> None:
    # Act
    _make_client(mock_session)
    # Assert
    assert mock_session.headers["customer"] == CUSTOMER_ID


def test_api_key_header_set_on_session(mock_session: MagicMock) -> None:
    # Act
    _make_client(mock_session)
    # Assert
    assert mock_session.headers["key"] == API_KEY


# --- get_plans ---


def test_get_plans_calls_correct_url(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    plans_data = _load_fixture("magicplan_api_plans_response_example.json")["data"]
    mock_session.get.return_value.json.return_value = {
        "message": "OK",
        "data": plans_data,
    }
    # Act
    client.get_plans()
    # Assert
    mock_session.get.assert_called_once_with(
        f"{BASE_URL}/workgroups/plans", params={"page": 1}
    )


def test_get_plans_calls_raise_for_status(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    plans_data = _load_fixture("magicplan_api_plans_response_example.json")["data"]
    mock_session.get.return_value.json.return_value = {
        "message": "OK",
        "data": plans_data,
    }
    # Act
    client.get_plans()
    # Assert
    mock_session.get.return_value.raise_for_status.assert_called_once()


def test_get_plans_returns_list_of_plan_summaries(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    plans_data = _load_fixture("magicplan_api_plans_response_example.json")["data"]
    mock_session.get.return_value.json.return_value = {
        "message": "OK",
        "data": plans_data,
    }
    # Act
    result = client.get_plans()
    # Assert
    assert isinstance(result, list)
    assert len(result) == 1
    assert isinstance(result[0], PlanSummary)


def test_get_plans_propagates_http_error(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    mock_session.get.return_value.raise_for_status.side_effect = requests.HTTPError(
        "404"
    )
    # Act / Assert
    with pytest.raises(requests.HTTPError):
        client.get_plans()


def test_get_plans_multi_page_fetches_all_pages(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    page1_plan = _load_fixture("magicplan_api_plans_response_example.json")["data"][
        "plans"
    ][0]
    page2_plan = {**page1_plan, "id": "page-2-plan-id"}
    page1_response = MagicMock()
    page1_response.json.return_value = {
        "data": {"paging": {"page": 1, "next_page": True, "count": 2}, "plans": [page1_plan]}
    }
    page2_response = MagicMock()
    page2_response.json.return_value = {
        "data": {"paging": {"page": 2, "next_page": False, "count": 2}, "plans": [page2_plan]}
    }
    mock_session.get.side_effect = [page1_response, page2_response]
    # Act
    result = client.get_plans()
    # Assert
    assert mock_session.get.call_count == 2
    mock_session.get.assert_any_call(f"{BASE_URL}/workgroups/plans", params={"page": 1})
    mock_session.get.assert_any_call(f"{BASE_URL}/workgroups/plans", params={"page": 2})
    assert len(result) == 2
    assert result[0].id == page1_plan["id"]
    assert result[1].id == "page-2-plan-id"


# --- get_plan ---


def test_get_plan_calls_correct_url(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    plan_data = _load_fixture("magicplan_api_plan_response_example.json")["data"]
    mock_session.get.return_value.json.return_value = {
        "message": "OK",
        "data": plan_data,
    }
    plan_id = "a7285ed1-878d-47eb-8aa6-85ef9e187516"
    # Act
    client.get_plan(plan_id)
    # Assert
    mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/get/{plan_id}")


def test_get_plan_calls_raise_for_status(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    plan_data = _load_fixture("magicplan_api_plan_response_example.json")["data"]
    mock_session.get.return_value.json.return_value = {
        "message": "OK",
        "data": plan_data,
    }
    # Act
    client.get_plan("a7285ed1-878d-47eb-8aa6-85ef9e187516")
    # Assert
    mock_session.get.return_value.raise_for_status.assert_called_once()


def test_get_plan_returns_magic_plan(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    plan_data = _load_fixture("magicplan_api_plan_response_example.json")["data"]
    mock_session.get.return_value.json.return_value = {
        "message": "OK",
        "data": plan_data,
    }
    # Act
    result = client.get_plan("a7285ed1-878d-47eb-8aa6-85ef9e187516")
    # Assert
    assert isinstance(result, MagicPlanPlan)
    assert result.plan.id == "a7285ed1-878d-47eb-8aa6-85ef9e187516"


def test_get_plan_propagates_http_error(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    mock_session.get.return_value.raise_for_status.side_effect = requests.HTTPError(
        "500"
    )
    # Act / Assert
    with pytest.raises(requests.HTTPError):
        client.get_plan("some-id")


# --- get_plan_raw ---


def test_get_plan_raw_returns_bytes(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    mock_session.get.return_value.content = b'{"data": "raw"}'
    plan_id = "a7285ed1-878d-47eb-8aa6-85ef9e187516"
    # Act
    result = client.get_plan_raw(plan_id)
    # Assert
    assert isinstance(result, bytes)


def test_get_plan_raw_calls_correct_url(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    mock_session.get.return_value.content = b"{}"
    plan_id = "a7285ed1-878d-47eb-8aa6-85ef9e187516"
    # Act
    client.get_plan_raw(plan_id)
    # Assert
    mock_session.get.assert_called_once_with(f"{BASE_URL}/plans/get/{plan_id}")


def test_get_plan_raw_calls_raise_for_status(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    mock_session.get.return_value.content = b"{}"
    # Act
    client.get_plan_raw("a7285ed1-878d-47eb-8aa6-85ef9e187516")
    # Assert
    mock_session.get.return_value.raise_for_status.assert_called_once()


def test_get_plan_raw_propagates_http_error(
    client: MagicPlanClient, mock_session: MagicMock
) -> None:
    # Arrange
    mock_session.get.return_value.raise_for_status.side_effect = requests.HTTPError(
        "500"
    )
    # Act / Assert
    with pytest.raises(requests.HTTPError):
        client.get_plan_raw("some-id")
