"""Tests for BundlesResource."""

from __future__ import annotations

from typing import Any
from unittest.mock import AsyncMock
from uuid import uuid4

import pytest

from mcpgateway_sdk.models.bundle import (
    Bundle,
    BundleDetail,
    BundleTool,
    BundleWithKey,
    ServerAssignment,
)
from mcpgateway_sdk.models.connection import BundleConnection
from mcpgateway_sdk.resources.bundles import BundlesResource


@pytest.fixture
def http() -> AsyncMock:
    """Mock HTTPClient."""
    return AsyncMock()


@pytest.fixture
def bundles(http: AsyncMock) -> BundlesResource:
    """BundlesResource with mocked HTTP."""
    return BundlesResource(http)


def _bundle_data(**overrides: Any) -> dict[str, Any]:
    return {
        "id": str(uuid4()),
        "name": "test-bundle",
        "description": "A test bundle",
        "agent_id": str(uuid4()),
        "tool_count": 3,
        "created_at": "2026-01-01T00:00:00Z",
        "updated_at": "2026-01-01T00:00:00Z",
        **overrides,
    }


def _bundle_with_key_data(**overrides: Any) -> dict[str, Any]:
    return {**_bundle_data(), "bundle_key": "bk_abc123", **overrides}


def _bundle_detail_data(**overrides: Any) -> dict[str, Any]:
    return {**_bundle_with_key_data(), "tools": [], **overrides}


def _bundle_tool_data(**overrides: Any) -> dict[str, Any]:
    return {
        "id": str(uuid4()),
        "tool_id": str(uuid4()),
        "override_name": None,
        "override_description": None,
        "enabled": True,
        "display_order": 0,
        "has_override_embedding": False,
        "original_name": "get_weather",
        "original_description": "Get weather data",
        "server_id": str(uuid4()),
        "server_name": "weather-server",
        "input_schema": {"type": "object"},
        "effective_name": "get_weather",
        "effective_description": "Get weather data",
        "created_at": "2026-01-01T00:00:00Z",
        "updated_at": "2026-01-01T00:00:00Z",
        **overrides,
    }


def _bundle_connection_data(**overrides: Any) -> dict[str, Any]:
    return {
        "id": str(uuid4()),
        "bundle_id": str(uuid4()),
        "server_id": str(uuid4()),
        "user_connection_id": str(uuid4()),
        "server_name": "github-server",
        "provider_name": "github",
        "provider_email": "user@example.com",
        "is_valid": True,
        "created_at": "2026-01-01T00:00:00Z",
        **overrides,
    }


class TestList:
    """Tests for BundlesResource.list."""

    async def test_sends_get_with_pagination(
        self, bundles: BundlesResource, http: AsyncMock
    ) -> None:
        http.request.return_value = {"items": []}

        await bundles.list(page=2, page_size=10)

        http.request.assert_awaited_once_with(
            "GET", "/api/v1/bundles", params={"page": 2, "page_size": 10}
        )

    async def test_includes_search_param(self, bundles: BundlesResource, http: AsyncMock) -> None:
        http.request.return_value = {"items": []}

        await bundles.list(search="weather")

        call_params = http.request.call_args.kwargs["params"]
        assert call_params["search"] == "weather"

    async def test_returns_list_of_bundles(self, bundles: BundlesResource, http: AsyncMock) -> None:
        http.request.return_value = {"items": [_bundle_data(), _bundle_data()]}

        result = await bundles.list()

        assert len(result) == 2
        assert all(isinstance(b, Bundle) for b in result)


class TestGet:
    """Tests for BundlesResource.get."""

    async def test_sends_get_with_bundle_id(
        self, bundles: BundlesResource, http: AsyncMock
    ) -> None:
        bid = uuid4()
        http.request.return_value = _bundle_detail_data()

        await bundles.get(bid)

        http.request.assert_awaited_once_with("GET", f"/api/v1/bundles/{bid}")

    async def test_returns_bundle_detail(self, bundles: BundlesResource, http: AsyncMock) -> None:
        http.request.return_value = _bundle_detail_data()

        result = await bundles.get(uuid4())

        assert isinstance(result, BundleDetail)
        assert result.bundle_key == "bk_abc123"


class TestGetByAgent:
    """Tests for BundlesResource.get_by_agent."""

    async def test_sends_get_with_agent_id(self, bundles: BundlesResource, http: AsyncMock) -> None:
        aid = uuid4()
        http.request.return_value = _bundle_detail_data()

        await bundles.get_by_agent(aid)

        http.request.assert_awaited_once_with("GET", f"/api/v1/bundles/by-agent/{aid}")


class TestCreate:
    """Tests for BundlesResource.create."""

    async def test_sends_post_with_name(self, bundles: BundlesResource, http: AsyncMock) -> None:
        http.request.return_value = _bundle_with_key_data()

        await bundles.create("my-bundle")

        http.request.assert_awaited_once_with("POST", "/api/v1/bundles", json={"name": "my-bundle"})

    async def test_includes_optional_fields(
        self, bundles: BundlesResource, http: AsyncMock
    ) -> None:
        http.request.return_value = _bundle_with_key_data()
        aid = uuid4()

        await bundles.create(
            "my-bundle",
            description="desc",
            agent_id=aid,
            tools=[{"tool_id": str(uuid4())}],
        )

        body = http.request.call_args.kwargs["json"]
        assert body["description"] == "desc"
        assert body["agent_id"] == str(aid)
        assert len(body["tools"]) == 1

    async def test_returns_bundle_with_key(self, bundles: BundlesResource, http: AsyncMock) -> None:
        http.request.return_value = _bundle_with_key_data()

        result = await bundles.create("my-bundle")

        assert isinstance(result, BundleWithKey)
        assert result.bundle_key == "bk_abc123"


class TestUpdate:
    """Tests for BundlesResource.update."""

    async def test_sends_put_with_fields(self, bundles: BundlesResource, http: AsyncMock) -> None:
        bid = uuid4()
        http.request.return_value = _bundle_with_key_data()

        await bundles.update(bid, name="new-name")

        http.request.assert_awaited_once_with(
            "PUT", f"/api/v1/bundles/{bid}", json={"name": "new-name"}
        )


class TestDelete:
    """Tests for BundlesResource.delete."""

    async def test_sends_delete(self, bundles: BundlesResource, http: AsyncMock) -> None:
        bid = uuid4()
        http.request.return_value = None

        await bundles.delete(bid)

        http.request.assert_awaited_once_with("DELETE", f"/api/v1/bundles/{bid}")


class TestAssignServer:
    """Tests for BundlesResource.assign_server."""

    async def test_sends_post_with_server_id(
        self, bundles: BundlesResource, http: AsyncMock
    ) -> None:
        bid, sid = uuid4(), uuid4()
        http.request.return_value = {"tools_added": 3, "tools_existing": 0}

        result = await bundles.assign_server(bid, sid)

        http.request.assert_awaited_once_with("POST", f"/api/v1/bundles/{bid}/servers/{sid}")
        assert isinstance(result, ServerAssignment)
        assert result.tools_added == 3


class TestRemoveServer:
    """Tests for BundlesResource.remove_server."""

    async def test_sends_delete(self, bundles: BundlesResource, http: AsyncMock) -> None:
        bid, sid = uuid4(), uuid4()
        http.request.return_value = None

        await bundles.remove_server(bid, sid)

        http.request.assert_awaited_once_with("DELETE", f"/api/v1/bundles/{bid}/servers/{sid}")


class TestAddTools:
    """Tests for BundlesResource.add_tools."""

    async def test_sends_post_with_tools_list(
        self, bundles: BundlesResource, http: AsyncMock
    ) -> None:
        bid = uuid4()
        tid = str(uuid4())
        http.request.return_value = [_bundle_tool_data()]

        await bundles.add_tools(bid, [{"tool_id": tid}])

        http.request.assert_awaited_once_with(
            "POST",
            f"/api/v1/bundles/{bid}/tools",
            json={"tools": [{"tool_id": tid}]},
        )

    async def test_returns_list_of_bundle_tools(
        self, bundles: BundlesResource, http: AsyncMock
    ) -> None:
        http.request.return_value = [_bundle_tool_data()]

        result = await bundles.add_tools(uuid4(), [{"tool_id": str(uuid4())}])

        assert len(result) == 1
        assert isinstance(result[0], BundleTool)


class TestListTools:
    """Tests for BundlesResource.list_tools."""

    async def test_sends_get_with_pagination(
        self, bundles: BundlesResource, http: AsyncMock
    ) -> None:
        bid = uuid4()
        http.request.return_value = {"items": []}

        await bundles.list_tools(bid, page=1, page_size=50)

        http.request.assert_awaited_once_with(
            "GET",
            f"/api/v1/bundles/{bid}/tools",
            params={"page": 1, "page_size": 50},
        )


class TestUpdateTool:
    """Tests for BundlesResource.update_tool."""

    async def test_sends_put_with_overrides(
        self, bundles: BundlesResource, http: AsyncMock
    ) -> None:
        bid, tid = uuid4(), uuid4()
        http.request.return_value = _bundle_tool_data()

        await bundles.update_tool(bid, tid, override_name="renamed", enabled=False)

        http.request.assert_awaited_once_with(
            "PUT",
            f"/api/v1/bundles/{bid}/tools/{tid}",
            json={"override_name": "renamed", "enabled": False},
        )


class TestRemoveTool:
    """Tests for BundlesResource.remove_tool."""

    async def test_sends_delete(self, bundles: BundlesResource, http: AsyncMock) -> None:
        bid, tid = uuid4(), uuid4()
        http.request.return_value = None

        await bundles.remove_tool(bid, tid)

        http.request.assert_awaited_once_with("DELETE", f"/api/v1/bundles/{bid}/tools/{tid}")


class TestAssignConnection:
    """Tests for BundlesResource.assign_connection."""

    async def test_sends_post(self, bundles: BundlesResource, http: AsyncMock) -> None:
        bid, cid = uuid4(), uuid4()
        http.request.return_value = _bundle_connection_data()

        result = await bundles.assign_connection(bid, cid)

        http.request.assert_awaited_once_with("POST", f"/api/v1/bundles/{bid}/connections/{cid}")
        assert isinstance(result, BundleConnection)


class TestRemoveConnection:
    """Tests for BundlesResource.remove_connection."""

    async def test_sends_delete(self, bundles: BundlesResource, http: AsyncMock) -> None:
        bid, cid = uuid4(), uuid4()
        http.request.return_value = None

        await bundles.remove_connection(bid, cid)

        http.request.assert_awaited_once_with("DELETE", f"/api/v1/bundles/{bid}/connections/{cid}")


class TestListConnections:
    """Tests for BundlesResource.list_connections."""

    async def test_sends_get(self, bundles: BundlesResource, http: AsyncMock) -> None:
        bid = uuid4()
        http.request.return_value = {"items": [_bundle_connection_data()]}

        result = await bundles.list_connections(bid)

        http.request.assert_awaited_once_with("GET", f"/api/v1/bundles/{bid}/connections")
        assert len(result) == 1
        assert isinstance(result[0], BundleConnection)
