"""Tests for the CrewAI integration.

Since crewai is an optional dependency that may not be installed,
we mock the import and test the core logic independently.
"""

from __future__ import annotations

import sys
from types import ModuleType
from unittest.mock import MagicMock, patch

import httpx
import pytest
import respx

from vorim import Vorim

from .conftest import (
    AGENT_ID,
    API_KEY,
    AUDIT_RESPONSE,
    BASE_URL,
    PERMISSION_CHECK_ALLOWED,
    PERMISSION_CHECK_DENIED,
    REGISTRATION_DATA,
    TRUST_RECORD_DATA,
)


# ── Provide mock crewai module so imports succeed ────────────────────────

_mock_crewai = ModuleType("crewai")
_mock_crewai_tools = ModuleType("crewai.tools")
_mock_crewai_tools.BaseTool = type("BaseTool", (), {})  # type: ignore
_mock_crewai.tools = _mock_crewai_tools  # type: ignore
sys.modules.setdefault("crewai", _mock_crewai)
sys.modules.setdefault("crewai.tools", _mock_crewai_tools)

from vorim.integrations.crewai import (
    RegisteredCrew,
    RegisteredCrewMember,
    check_crew_permission,
    check_delegation_permission,
    emit_crew_run_events,
    emit_crew_task_event,
    register_crew,
    verify_crew_trust,
    vorim_tool,
)


class TestRegisterCrew:
    @respx.mock
    def test_basic_registration(self):
        respx.post(f"{BASE_URL}/v1/agents").mock(
            return_value=httpx.Response(200, json=REGISTRATION_DATA)
        )
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        crew = register_crew(vorim, {
            "crew_name": "test-crew",
            "members": [
                {
                    "role": "researcher",
                    "name": "crew-researcher",
                    "capabilities": ["web_search"],
                    "scopes": ["agent:read", "agent:execute"],
                },
            ],
        })
        assert crew.crew_name == "test-crew"
        assert len(crew.members) == 1
        assert crew.members[0].role == "researcher"
        assert crew.members[0].agent_id == AGENT_ID
        vorim.close()

    @respx.mock
    def test_delegation_auto_adds_scope(self):
        calls = []
        original_post = httpx.Client.post

        respx.post(f"{BASE_URL}/v1/agents").mock(
            return_value=httpx.Response(200, json=REGISTRATION_DATA)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        crew = register_crew(vorim, {
            "crew_name": "test-crew",
            "members": [
                {
                    "role": "editor",
                    "name": "crew-editor",
                    "capabilities": ["review"],
                    "scopes": ["agent:read"],
                    "allow_delegation": True,
                },
            ],
        })
        # Verify the request included agent:delegate
        body = respx.calls[0].request.content
        assert b"agent:delegate" in body
        vorim.close()

    @respx.mock
    def test_multiple_members(self):
        respx.post(f"{BASE_URL}/v1/agents").mock(
            return_value=httpx.Response(200, json=REGISTRATION_DATA)
        )
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        crew = register_crew(vorim, {
            "crew_name": "pipeline",
            "members": [
                {"role": "a", "name": "a", "capabilities": [], "scopes": ["agent:read"]},
                {"role": "b", "name": "b", "capabilities": [], "scopes": ["agent:read"]},
                {"role": "c", "name": "c", "capabilities": [], "scopes": ["agent:read"]},
            ],
        })
        assert len(crew.members) == 3
        assert crew.agent_ids() == [AGENT_ID, AGENT_ID, AGENT_ID]
        vorim.close()


class TestRegisteredCrew:
    def _crew(self) -> RegisteredCrew:
        return RegisteredCrew(
            crew_name="test",
            members=[
                RegisteredCrewMember(
                    role="researcher",
                    agent_id="agid_1",
                    private_key="key-1",
                    registration=None,
                ),
                RegisteredCrewMember(
                    role="writer",
                    agent_id="agid_2",
                    private_key="key-2",
                    registration=None,
                ),
            ],
        )

    def test_get_member(self):
        crew = self._crew()
        assert crew.get_member("researcher").agent_id == "agid_1"
        assert crew.get_member("nonexistent") is None

    def test_agent_ids(self):
        crew = self._crew()
        assert crew.agent_ids() == ["agid_1", "agid_2"]


class TestVorimTool:
    @respx.mock
    def test_success(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_ALLOWED)
        )
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)

        @vorim_tool(vorim, agent_id=AGENT_ID, permission="agent:execute")
        def search(query: str) -> str:
            """Search docs."""
            return f"Results for {query}"

        result = search("AI papers")
        assert result == "Results for AI papers"
        vorim.close()

    @respx.mock
    def test_denied(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_DENIED)
        )
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)

        @vorim_tool(vorim, agent_id=AGENT_ID)
        def search(query: str) -> str:
            """Search."""
            return query

        with pytest.raises(PermissionError, match="permission denied"):
            search("secret")
        vorim.close()

    @respx.mock
    def test_error_audited(self):
        respx.post(f"{BASE_URL}/v1/agents/{AGENT_ID}/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_ALLOWED)
        )
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )

        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)

        @vorim_tool(vorim, agent_id=AGENT_ID)
        def bad_tool() -> str:
            raise RuntimeError("something broke")

        with pytest.raises(RuntimeError, match="something broke"):
            bad_tool()
        # Verify audit event was emitted (2 calls: permission check + error audit)
        assert len(respx.calls) >= 2
        vorim.close()


class TestEmitCrewTaskEvent:
    @respx.mock
    def test_emit(self):
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        result = emit_crew_task_event(
            vorim,
            role="researcher",
            agent_id=AGENT_ID,
            task="research",
            tool="web_search",
            result="success",
            latency_ms=1500,
        )
        assert result["ingested"] == 1
        vorim.close()

    @respx.mock
    def test_emit_with_delegation(self):
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json=AUDIT_RESPONSE)
        )
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        emit_crew_task_event(
            vorim,
            role="editor",
            agent_id=AGENT_ID,
            task="review",
            result="success",
            delegated_to="writer",
        )
        body = respx.calls[0].request.content
        assert b"delegated_to" in body
        vorim.close()


class TestEmitCrewRunEvents:
    @respx.mock
    def test_batch(self):
        respx.post(f"{BASE_URL}/v1/audit/events").mock(
            return_value=httpx.Response(200, json={"data": {"ingested": 2}})
        )
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        result = emit_crew_run_events(vorim, [
            {"role": "a", "agent_id": AGENT_ID, "task": "t1", "result": "success"},
            {"role": "b", "agent_id": AGENT_ID, "task": "t2", "result": "error", "error": "fail"},
        ])
        assert result["ingested"] == 2
        vorim.close()


class TestCheckCrewPermission:
    @respx.mock
    def test_allowed(self):
        respx.post(f"{BASE_URL}/v1/agents/agid_1/permissions/verify").mock(
            return_value=httpx.Response(200, json=PERMISSION_CHECK_ALLOWED)
        )
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        crew = RegisteredCrew(
            crew_name="test",
            members=[RegisteredCrewMember(role="r", agent_id="agid_1", private_key="k", registration=None)],
        )
        result = check_crew_permission(vorim, crew, "r", "agent:execute")
        assert result["allowed"] is True
        vorim.close()

    def test_unknown_role(self):
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        crew = RegisteredCrew(crew_name="test", members=[])
        result = check_crew_permission(vorim, crew, "missing", "agent:read")
        assert result["allowed"] is False
        assert "Unknown" in result["reason"]
        vorim.close()


class TestCheckDelegationPermission:
    @respx.mock
    def test_allowed(self):
        respx.post(f"{BASE_URL}/v1/agents/agid_1/permissions/verify").mock(
            return_value=httpx.Response(200, json={
                "data": {"allowed": True, "scope": "agent:delegate", "agent_id": "agid_1"}
            })
        )
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        crew = RegisteredCrew(
            crew_name="test",
            members=[
                RegisteredCrewMember(role="editor", agent_id="agid_1", private_key="k", registration=None),
                RegisteredCrewMember(role="writer", agent_id="agid_2", private_key="k", registration=None),
            ],
        )
        result = check_delegation_permission(vorim, crew, "editor", "writer")
        assert result["allowed"] is True
        vorim.close()

    def test_unknown_from_role(self):
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        crew = RegisteredCrew(crew_name="test", members=[])
        result = check_delegation_permission(vorim, crew, "x", "y")
        assert result["allowed"] is False
        vorim.close()


class TestVerifyCrewTrust:
    @respx.mock
    def test_verify_all(self):
        respx.get(f"{BASE_URL}/v1/trust/verify/agid_1").mock(
            return_value=httpx.Response(200, json=TRUST_RECORD_DATA)
        )
        respx.get(f"{BASE_URL}/v1/trust/verify/agid_2").mock(
            return_value=httpx.Response(200, json=TRUST_RECORD_DATA)
        )
        vorim = Vorim(api_key=API_KEY, base_url=BASE_URL)
        crew = RegisteredCrew(
            crew_name="test",
            members=[
                RegisteredCrewMember(role="a", agent_id="agid_1", private_key="k", registration=None),
                RegisteredCrewMember(role="b", agent_id="agid_2", private_key="k", registration=None),
            ],
        )
        results = verify_crew_trust(vorim, crew)
        assert len(results) == 2
        assert all(r["trust_score"] == 85.0 for r in results)
        vorim.close()
