"""Tests for MeshLedger LangChain integration."""

import pytest
import httpx
import respx

# Skip entire module if langchain-core is not installed
langchain_core = pytest.importorskip("langchain_core")

from meshledger import MeshLedgerClient
from meshledger.integrations.langchain import (
    MeshLedgerToolkit,
    BrowseSkillsTool,
    SearchAgentsTool,
    GetSkillDetailsTool,
    CreateJobTool,
    AcceptJobTool,
    DeliverJobTool,
    CheckJobTool,
    ReleasePaymentTool,
    DisputeJobTool,
    RateJobTool,
    GetDashboardTool,
    MarketplaceStatsTool,
)
from langchain_core.tools import ToolException

BASE = "https://meshledger.io/api/v1"


def ok(data):
    return httpx.Response(200, json={"success": True, "data": data})


def err(status, error="fail", code="ERROR"):
    return httpx.Response(status, json={"success": False, "error": error, "code": code})


# ---------------------------------------------------------------------------
# Toolkit
# ---------------------------------------------------------------------------


class TestMeshLedgerToolkit:
    def test_creates_all_12_tools(self):
        toolkit = MeshLedgerToolkit(api_key="ml_sk_test")
        tools = toolkit.get_tools()
        assert len(tools) == 12

    def test_tool_names(self):
        toolkit = MeshLedgerToolkit(api_key="ml_sk_test")
        tools = toolkit.get_tools()
        names = {t.name for t in tools}
        expected = {
            "meshledger_browse_skills",
            "meshledger_search_agents",
            "meshledger_get_skill_details",
            "meshledger_create_job",
            "meshledger_accept_job",
            "meshledger_deliver_job",
            "meshledger_check_job",
            "meshledger_release_payment",
            "meshledger_dispute_job",
            "meshledger_rate_job",
            "meshledger_dashboard",
            "meshledger_marketplace_stats",
        }
        assert names == expected

    def test_all_tools_have_descriptions(self):
        toolkit = MeshLedgerToolkit(api_key="ml_sk_test")
        for tool in toolkit.get_tools():
            assert tool.description, f"{tool.name} missing description"
            assert len(tool.description) > 10, f"{tool.name} description too short"

    def test_all_tools_have_args_schema(self):
        toolkit = MeshLedgerToolkit(api_key="ml_sk_test")
        for tool in toolkit.get_tools():
            assert tool.args_schema is not None, f"{tool.name} missing args_schema"

    def test_custom_base_url(self):
        toolkit = MeshLedgerToolkit(
            api_key="ml_sk_test", base_url="https://custom.example.com/api/v1"
        )
        assert toolkit.client.base_url == "https://custom.example.com/api/v1"


# ---------------------------------------------------------------------------
# Individual tools
# ---------------------------------------------------------------------------


class TestBrowseSkillsTool:
    @respx.mock
    def test_returns_skills(self):
        respx.get(f"{BASE}/skills").mock(
            return_value=ok(
                {
                    "skills": [
                        {
                            "skill_id": "s1",
                            "agent_id": "a1",
                            "name": "Code Review",
                            "description": "Reviews code for bugs",
                            "capabilities": ["code-review"],
                            "price": 5.0,
                            "price_token": "USDC",
                            "price_chain": "base",
                        }
                    ],
                    "cursor": None,
                    "has_more": False,
                }
            )
        )
        client = MeshLedgerClient()
        tool = BrowseSkillsTool(client=client)
        result = tool._run()
        assert "Code Review" in result
        assert "s1" in result
        assert "$5.0" in result
        client.close()

    @respx.mock
    def test_no_skills_found(self):
        respx.get(f"{BASE}/skills").mock(
            return_value=ok({"skills": [], "cursor": None, "has_more": False})
        )
        client = MeshLedgerClient()
        tool = BrowseSkillsTool(client=client)
        result = tool._run()
        assert "No skills found" in result
        client.close()

    @respx.mock
    def test_error_raises_tool_exception(self):
        respx.get(f"{BASE}/skills").mock(return_value=err(500, "Server error"))
        client = MeshLedgerClient()
        tool = BrowseSkillsTool(client=client)
        with pytest.raises(ToolException):
            tool._run()
        client.close()


class TestSearchAgentsTool:
    @respx.mock
    def test_returns_agents(self):
        respx.get(f"{BASE}/agents/search").mock(
            return_value=ok(
                {
                    "agents": [
                        {
                            "agent_id": "a1",
                            "name": "TestBot",
                            "description": "A test bot",
                            "chains": ["base"],
                            "capabilities": ["testing", "qa"],
                        }
                    ],
                    "cursor": None,
                    "has_more": False,
                }
            )
        )
        client = MeshLedgerClient()
        tool = SearchAgentsTool(client=client)
        result = tool._run(capabilities="testing")
        assert "TestBot" in result
        assert "a1" in result
        client.close()


class TestGetSkillDetailsTool:
    @respx.mock
    def test_returns_details(self):
        respx.get(f"{BASE}/skills/s1").mock(
            return_value=ok(
                {
                    "skill_id": "s1",
                    "agent_id": "a1",
                    "name": "Code Review",
                    "description": "Reviews code",
                    "capabilities": ["code-review"],
                    "price": 5.0,
                    "price_token": "USDC",
                    "price_chain": "base",
                    "estimated_delivery_minutes": 30,
                    "agent_name": "ReviewBot",
                }
            )
        )
        client = MeshLedgerClient()
        tool = GetSkillDetailsTool(client=client)
        result = tool._run(skill_id="s1")
        assert "Code Review" in result
        assert "$5.0" in result
        assert "30 minutes" in result
        assert "ReviewBot" in result
        client.close()


class TestCreateJobTool:
    @respx.mock
    def test_creates_job(self):
        respx.post(f"{BASE}/jobs").mock(
            return_value=httpx.Response(
                201,
                json={
                    "success": True,
                    "data": {
                        "job_id": "j1",
                        "escrow_tx_hash": "0xabc",
                        "total_charged": 10.5,
                        "listed_price": 10.0,
                        "service_fee": 0.5,
                        "status": "funded",
                    },
                },
            )
        )
        client = MeshLedgerClient(api_key="ml_sk_test")
        tool = CreateJobTool(client=client)
        result = tool._run(
            title="Test Job",
            description="Do something",
            chain="base",
            token="USDC",
            price=10.0,
        )
        assert "j1" in result
        assert "10.5" in result
        client.close()


class TestAcceptJobTool:
    @respx.mock
    def test_accepts_job(self):
        respx.post(f"{BASE}/jobs/j1/accept").mock(
            return_value=ok(
                {"job_id": "j1", "title": "Test", "status": "accepted"}
            )
        )
        client = MeshLedgerClient(api_key="ml_sk_test")
        tool = AcceptJobTool(client=client)
        result = tool._run(job_id="j1")
        assert "accepted" in result.lower()
        client.close()


class TestDeliverJobTool:
    @respx.mock
    def test_delivers_job(self):
        respx.post(f"{BASE}/jobs/j1/deliver").mock(
            return_value=ok(
                {
                    "job_id": "j1",
                    "status": "delivered",
                    "auto_release_at": "2025-01-02T00:00:00Z",
                    "message": "Customer has 24 hours to review.",
                }
            )
        )
        client = MeshLedgerClient(api_key="ml_sk_test")
        tool = DeliverJobTool(client=client)
        result = tool._run(job_id="j1", deliverable_text="Here is the work")
        assert "delivered" in result.lower()
        assert "auto_release" in result.lower() or "auto-release" in result.lower()
        client.close()


class TestCheckJobTool:
    @respx.mock
    def test_checks_job(self):
        respx.get(f"{BASE}/jobs/j1").mock(
            return_value=ok(
                {
                    "job_id": "j1",
                    "title": "Test Job",
                    "status": "accepted",
                    "customer_id": "c1",
                    "deliverer_id": "d1",
                    "listed_price": 10.0,
                    "token": "USDC",
                    "chain": "base",
                }
            )
        )
        client = MeshLedgerClient()
        tool = CheckJobTool(client=client)
        result = tool._run(job_id="j1")
        assert "Test Job" in result
        assert "accepted" in result
        client.close()


class TestReleasePaymentTool:
    @respx.mock
    def test_releases_payment(self):
        respx.post(f"{BASE}/jobs/j1/release").mock(
            return_value=ok(
                {
                    "job_id": "j1",
                    "status": "released",
                    "release_tx_hash": "0xdef",
                    "amount_to_deliverer": 9.5,
                    "fee_to_treasury": 0.5,
                }
            )
        )
        client = MeshLedgerClient(api_key="ml_sk_test")
        tool = ReleasePaymentTool(client=client)
        result = tool._run(job_id="j1")
        assert "released" in result.lower()
        assert "9.5" in result
        client.close()


class TestDisputeJobTool:
    @respx.mock
    def test_disputes_job(self):
        respx.post(f"{BASE}/jobs/j1/dispute").mock(
            return_value=ok(
                {
                    "dispute_id": "d1",
                    "job_id": "j1",
                    "status": "disputed",
                    "message": "AI Judge will review.",
                }
            )
        )
        client = MeshLedgerClient(api_key="ml_sk_test")
        tool = DisputeJobTool(client=client)
        result = tool._run(job_id="j1", reason="Bad work")
        assert "d1" in result
        assert "disputed" in result.lower()
        client.close()


class TestRateJobTool:
    @respx.mock
    def test_rates_job(self):
        respx.post(f"{BASE}/jobs/j1/rate").mock(
            return_value=httpx.Response(
                201,
                json={
                    "success": True,
                    "data": {
                        "rating_id": "r1",
                        "job_id": "j1",
                        "rated_id": "a2",
                        "score": 5,
                        "comment": "Great",
                    },
                },
            )
        )
        client = MeshLedgerClient(api_key="ml_sk_test")
        tool = RateJobTool(client=client)
        result = tool._run(job_id="j1", score=5, comment="Great")
        assert "5" in result
        assert "j1" in result
        client.close()


class TestGetDashboardTool:
    @respx.mock
    def test_returns_dashboard(self):
        respx.get(f"{BASE}/agents/me/dashboard").mock(
            return_value=ok(
                {
                    "agent": {"agent_id": "a1"},
                    "stats": {"completed_jobs": 10},
                    "recent_jobs": [{"title": "Job 1", "status": "released"}],
                    "active_escrows": [],
                    "pending_approvals": [],
                }
            )
        )
        client = MeshLedgerClient(api_key="ml_sk_test")
        tool = GetDashboardTool(client=client)
        result = tool._run()
        assert "Dashboard" in result
        assert "Recent jobs: 1" in result
        client.close()


class TestMarketplaceStatsTool:
    @respx.mock
    def test_returns_stats(self):
        respx.get(f"{BASE}/marketplace/stats").mock(
            return_value=ok(
                {
                    "total_agents": 100,
                    "total_skills": 500,
                    "total_jobs_completed": 1000,
                    "total_volume_usd": 50000.0,
                    "active_escrows": 25,
                    "average_completion_time_minutes": 45.0,
                }
            )
        )
        client = MeshLedgerClient()
        tool = MarketplaceStatsTool(client=client)
        result = tool._run()
        assert "100" in result
        assert "500" in result
        assert "50,000.00" in result
        assert "45 min" in result
        client.close()
