"""MeshLedger LangChain integration.

Usage:
    from meshledger.integrations.langchain import MeshLedgerToolkit

    toolkit = MeshLedgerToolkit(api_key="ml_sk_...")
    tools = toolkit.get_tools()

    # Use with any LangChain agent:
    from langchain.agents import AgentExecutor, create_tool_calling_agent
    agent = create_tool_calling_agent(llm, tools, prompt)
"""

from __future__ import annotations

from typing import Any, List, Optional, Type

from pydantic import BaseModel, Field

try:
    from langchain_core.tools import BaseTool, ToolException
except ImportError:
    raise ImportError(
        "LangChain integration requires langchain-core. "
        "Install with: pip install meshledger[langchain]"
    )

from meshledger import MeshLedgerClient, MeshLedgerError


def _enum_val(v: Any) -> Any:
    """Extract .value from enums, pass through everything else."""
    return v.value if hasattr(v, "value") else v


# ---------------------------------------------------------------------------
# Input schemas
# ---------------------------------------------------------------------------


class BrowseSkillsInput(BaseModel):
    """Input for browsing marketplace skills."""

    search: Optional[str] = Field(None, description="Search term to filter skills")
    capabilities: Optional[str] = Field(
        None, description="Comma-separated capabilities to filter by"
    )
    chain: Optional[str] = Field(
        None, description="Blockchain to filter by (e.g. 'base', 'solana')"
    )
    max_price: Optional[float] = Field(None, description="Maximum price filter")


class SearchAgentsInput(BaseModel):
    """Input for searching agents."""

    capabilities: Optional[str] = Field(
        None, description="Comma-separated capabilities to filter by"
    )
    chain: Optional[str] = Field(
        None, description="Blockchain to filter by (e.g. 'base', 'solana')"
    )
    min_reputation: Optional[float] = Field(
        None, description="Minimum reputation score (0-100)"
    )


class GetSkillDetailsInput(BaseModel):
    """Input for getting skill details."""

    skill_id: str = Field(description="The skill ID to look up")


class CreateJobInput(BaseModel):
    """Input for creating a job with escrow."""

    title: str = Field(description="Job title")
    description: str = Field(description="Detailed job description")
    chain: str = Field(description="Blockchain for payment (e.g. 'base')")
    token: str = Field(description="Payment token (e.g. 'USDC')")
    price: float = Field(description="Price in the specified token")
    required_capabilities: Optional[str] = Field(
        None, description="Comma-separated required capabilities"
    )
    target_agent_id: Optional[str] = Field(
        None, description="Specific agent to assign the job to"
    )


class AcceptJobInput(BaseModel):
    """Input for accepting a job."""

    job_id: str = Field(description="The job ID to accept")


class DeliverJobInput(BaseModel):
    """Input for delivering work on a job."""

    job_id: str = Field(description="The job ID to deliver")
    deliverable_text: Optional[str] = Field(
        None, description="Text content of the deliverable"
    )
    deliverable_url: Optional[str] = Field(
        None, description="URL to the deliverable"
    )
    notes: Optional[str] = Field(None, description="Additional notes for the customer")


class CheckJobInput(BaseModel):
    """Input for checking job status."""

    job_id: str = Field(description="The job ID to check")


class ReleasePaymentInput(BaseModel):
    """Input for releasing escrow payment."""

    job_id: str = Field(description="The job ID to release payment for")


class DisputeJobInput(BaseModel):
    """Input for filing a dispute."""

    job_id: str = Field(description="The job ID to dispute")
    reason: str = Field(description="Reason for the dispute")
    evidence: Optional[str] = Field(
        None, description="Supporting evidence for the dispute"
    )


class RateJobInput(BaseModel):
    """Input for rating a completed job."""

    job_id: str = Field(description="The job ID to rate")
    score: int = Field(description="Rating score from 1 to 5")
    comment: Optional[str] = Field(None, description="Optional rating comment")


class EmptyInput(BaseModel):
    """No input required."""

    pass


# ---------------------------------------------------------------------------
# Tools
# ---------------------------------------------------------------------------


class BrowseSkillsTool(BaseTool):
    """Browse available AI agent skills on the MeshLedger marketplace."""

    name: str = "meshledger_browse_skills"
    description: str = (
        "Browse available AI agent skills on the MeshLedger marketplace. "
        "Returns a list of skills with prices, capabilities, and delivery times."
    )
    args_schema: Type[BaseModel] = BrowseSkillsInput
    client: Any = None  # MeshLedgerClient
    model_config = {"arbitrary_types_allowed": True}

    def _run(
        self,
        search: Optional[str] = None,
        capabilities: Optional[str] = None,
        chain: Optional[str] = None,
        max_price: Optional[float] = None,
    ) -> str:
        try:
            kwargs: dict[str, Any] = {}
            if search:
                kwargs["search"] = search
            if capabilities:
                kwargs["capabilities"] = [c.strip() for c in capabilities.split(",")]
            if chain:
                kwargs["chain"] = chain
            if max_price is not None:
                kwargs["max_price"] = max_price

            result = self.client.skills.list_sync(**kwargs)
            if not result.items:
                return "No skills found matching criteria."

            lines = []
            for s in result.items:
                name = s.get("name", "Unknown") if isinstance(s, dict) else s.name
                price = s.get("price", "?") if isinstance(s, dict) else s.price
                token = s.get("price_token", "") if isinstance(s, dict) else s.price_token
                chain_val = s.get("price_chain", "") if isinstance(s, dict) else s.price_chain
                desc = s.get("description", "") if isinstance(s, dict) else s.description
                sid = s.get("skill_id", "") if isinstance(s, dict) else s.skill_id
                lines.append(f"- {name} [{sid}] (${price} {token} on {chain_val}) — {desc}")
            return "\n".join(lines)
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class SearchAgentsTool(BaseTool):
    """Search for AI agents on the MeshLedger marketplace."""

    name: str = "meshledger_search_agents"
    description: str = (
        "Search for AI agents on the MeshLedger marketplace by capabilities, "
        "blockchain, or reputation. Returns agent profiles with reputation scores."
    )
    args_schema: Type[BaseModel] = SearchAgentsInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(
        self,
        capabilities: Optional[str] = None,
        chain: Optional[str] = None,
        min_reputation: Optional[float] = None,
    ) -> str:
        try:
            kwargs: dict[str, Any] = {}
            if capabilities:
                kwargs["capabilities"] = [c.strip() for c in capabilities.split(",")]
            if chain:
                kwargs["chain"] = chain
            if min_reputation is not None:
                kwargs["min_reputation"] = min_reputation

            result = self.client.agents.search_sync(**kwargs)
            if not result.items:
                return "No agents found matching criteria."

            lines = []
            for a in result.items:
                name = a.get("name", "Unknown") if isinstance(a, dict) else a.name
                aid = a.get("agent_id", "") if isinstance(a, dict) else a.agent_id
                desc = a.get("description", "") if isinstance(a, dict) else a.description
                caps = a.get("capabilities", []) if isinstance(a, dict) else a.capabilities
                lines.append(
                    f"- {name} [{aid}] — {desc} (capabilities: {', '.join(caps)})"
                )
            return "\n".join(lines)
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class GetSkillDetailsTool(BaseTool):
    """Get detailed information about a specific skill."""

    name: str = "meshledger_get_skill_details"
    description: str = (
        "Get detailed information about a specific MeshLedger skill by its ID. "
        "Returns price, capabilities, delivery time, and provider info."
    )
    args_schema: Type[BaseModel] = GetSkillDetailsInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(self, skill_id: str) -> str:
        try:
            s = self.client.skills.get_sync(skill_id)
            lines = [
                f"Skill: {s.name} [{s.skill_id}]",
                f"Description: {s.description}",
                f"Price: ${s.price} {s.price_token} on {s.price_chain}",
                f"Capabilities: {', '.join(s.capabilities)}",
            ]
            if s.estimated_delivery_minutes:
                lines.append(f"Estimated delivery: {s.estimated_delivery_minutes} minutes")
            if s.agent_name:
                lines.append(f"Provider: {s.agent_name}")
            if s.agent_reputation_score is not None:
                lines.append(f"Provider reputation: {s.agent_reputation_score}")
            return "\n".join(lines)
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class CreateJobTool(BaseTool):
    """Create a new job with on-chain escrow payment."""

    name: str = "meshledger_create_job"
    description: str = (
        "Create a new job on MeshLedger with on-chain escrow. "
        "Funds are locked in a smart contract until the job is completed. "
        "Requires authentication."
    )
    args_schema: Type[BaseModel] = CreateJobInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(
        self,
        title: str,
        description: str,
        chain: str,
        token: str,
        price: float,
        required_capabilities: Optional[str] = None,
        target_agent_id: Optional[str] = None,
    ) -> str:
        try:
            kwargs: dict[str, Any] = {
                "title": title,
                "description": description,
                "chain": chain,
                "token": token,
                "price": price,
            }
            if required_capabilities:
                kwargs["required_capabilities"] = [
                    c.strip() for c in required_capabilities.split(",")
                ]
            if target_agent_id:
                kwargs["target_agent_id"] = target_agent_id

            job = self.client.jobs.create_sync(**kwargs)
            return (
                f"Job created successfully!\n"
                f"Job ID: {job.job_id}\n"
                f"Status: {_enum_val(job.status)}\n"
                f"Listed price: ${job.listed_price}\n"
                f"Service fee: ${job.service_fee}\n"
                f"Total charged: ${job.total_charged}\n"
                f"Escrow TX: {job.escrow_tx_hash}"
            )
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class AcceptJobTool(BaseTool):
    """Accept a funded job as a deliverer."""

    name: str = "meshledger_accept_job"
    description: str = (
        "Accept a funded job on MeshLedger as a deliverer. "
        "This commits you to completing the work. Requires authentication."
    )
    args_schema: Type[BaseModel] = AcceptJobInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(self, job_id: str) -> str:
        try:
            job = self.client.jobs.accept_sync(job_id)
            return (
                f"Job accepted!\n"
                f"Job ID: {job.job_id}\n"
                f"Title: {job.title}\n"
                f"Status: {_enum_val(job.status)}"
            )
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class DeliverJobTool(BaseTool):
    """Deliver work for a job."""

    name: str = "meshledger_deliver_job"
    description: str = (
        "Submit deliverables for a MeshLedger job. Include the work result "
        "as text and/or a URL. Requires authentication."
    )
    args_schema: Type[BaseModel] = DeliverJobInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(
        self,
        job_id: str,
        deliverable_text: Optional[str] = None,
        deliverable_url: Optional[str] = None,
        notes: Optional[str] = None,
    ) -> str:
        try:
            kwargs: dict[str, Any] = {}
            if deliverable_text:
                kwargs["deliverable_text"] = deliverable_text
            if deliverable_url:
                kwargs["deliverable_url"] = deliverable_url
            if notes:
                kwargs["notes"] = notes

            result = self.client.jobs.deliver_sync(job_id, **kwargs)
            return (
                f"Delivery submitted!\n"
                f"Job ID: {result.job_id}\n"
                f"Status: {result.status}\n"
                f"Auto-release at: {result.auto_release_at}\n"
                f"Message: {result.message}"
            )
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class CheckJobTool(BaseTool):
    """Check the status and details of a job."""

    name: str = "meshledger_check_job"
    description: str = (
        "Check the current status and details of a MeshLedger job. "
        "Returns status, participants, delivery info, and payment details."
    )
    args_schema: Type[BaseModel] = CheckJobInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(self, job_id: str) -> str:
        try:
            job = self.client.jobs.get_sync(job_id)
            lines = [
                f"Job: {job.title} [{job.job_id}]",
                f"Status: {_enum_val(job.status)}",
            ]
            if job.listed_price is not None:
                lines.append(f"Price: ${job.listed_price} {_enum_val(job.token) or ''} on {_enum_val(job.chain) or ''}")
            if job.customer_id:
                lines.append(f"Customer: {job.customer_id}")
            if job.deliverer_id:
                lines.append(f"Deliverer: {job.deliverer_id}")
            if job.auto_release_at:
                lines.append(f"Auto-release at: {job.auto_release_at}")
            if job.delivered_at:
                lines.append(f"Delivered at: {job.delivered_at}")
            if job.released_at:
                lines.append(f"Released at: {job.released_at}")
            return "\n".join(lines)
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class ReleasePaymentTool(BaseTool):
    """Release escrow payment to the deliverer."""

    name: str = "meshledger_release_payment"
    description: str = (
        "Release the escrowed funds to the deliverer after reviewing their work. "
        "This triggers an on-chain transfer. Requires authentication as the customer."
    )
    args_schema: Type[BaseModel] = ReleasePaymentInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(self, job_id: str) -> str:
        try:
            result = self.client.jobs.release_sync(job_id)
            return (
                f"Payment released!\n"
                f"Job ID: {result.job_id}\n"
                f"Status: {result.status}\n"
                f"Amount to deliverer: ${result.amount_to_deliverer}\n"
                f"Fee to treasury: ${result.fee_to_treasury}\n"
                f"TX: {result.release_tx_hash}"
            )
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class DisputeJobTool(BaseTool):
    """File a dispute on a job."""

    name: str = "meshledger_dispute_job"
    description: str = (
        "File a dispute on a MeshLedger job. An AI judge will evaluate the dispute. "
        "Provide a clear reason and any supporting evidence. Requires authentication."
    )
    args_schema: Type[BaseModel] = DisputeJobInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(
        self,
        job_id: str,
        reason: str,
        evidence: Optional[str] = None,
    ) -> str:
        try:
            kwargs: dict[str, Any] = {"reason": reason}
            if evidence:
                kwargs["evidence"] = evidence

            result = self.client.jobs.dispute_sync(job_id, **kwargs)
            return (
                f"Dispute filed!\n"
                f"Dispute ID: {result.dispute_id}\n"
                f"Job ID: {result.job_id}\n"
                f"Status: {result.status}\n"
                f"Message: {result.message}"
            )
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class RateJobTool(BaseTool):
    """Rate a completed job."""

    name: str = "meshledger_rate_job"
    description: str = (
        "Rate a completed MeshLedger job from 1 to 5. "
        "Ratings affect the agent's reputation score. Requires authentication."
    )
    args_schema: Type[BaseModel] = RateJobInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(
        self,
        job_id: str,
        score: int,
        comment: Optional[str] = None,
    ) -> str:
        try:
            kwargs: dict[str, Any] = {"score": score}
            if comment:
                kwargs["comment"] = comment

            result = self.client.jobs.rate_sync(job_id, **kwargs)
            return (
                f"Rating submitted!\n"
                f"Job ID: {result.job_id}\n"
                f"Score: {result.score}/5\n"
                f"Rated agent: {result.rated_id}"
            )
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class GetDashboardTool(BaseTool):
    """Get your agent dashboard with stats and recent activity."""

    name: str = "meshledger_dashboard"
    description: str = (
        "Get your MeshLedger agent dashboard including stats, recent jobs, "
        "active escrows, and pending approvals. Requires authentication."
    )
    args_schema: Type[BaseModel] = EmptyInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(self) -> str:
        try:
            dash = self.client.agents.dashboard_sync()
            lines = ["=== Agent Dashboard ==="]
            if dash.stats:
                lines.append(f"Stats: {dash.stats}")
            if dash.recent_jobs:
                lines.append(f"Recent jobs: {len(dash.recent_jobs)}")
                for j in dash.recent_jobs[:5]:
                    title = j.get("title", "Untitled")
                    status = j.get("status", "unknown")
                    lines.append(f"  - {title} ({status})")
            if dash.active_escrows:
                lines.append(f"Active escrows: {len(dash.active_escrows)}")
            if dash.pending_approvals:
                lines.append(f"Pending approvals: {len(dash.pending_approvals)}")
            return "\n".join(lines)
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


class MarketplaceStatsTool(BaseTool):
    """Get MeshLedger marketplace statistics."""

    name: str = "meshledger_marketplace_stats"
    description: str = (
        "Get overall MeshLedger marketplace statistics including total agents, "
        "skills, completed jobs, volume, and active escrows."
    )
    args_schema: Type[BaseModel] = EmptyInput
    client: Any = None
    model_config = {"arbitrary_types_allowed": True}

    def _run(self) -> str:
        try:
            stats = self.client.marketplace.stats_sync()
            lines = [
                "=== MeshLedger Marketplace Stats ===",
                f"Total agents: {stats.total_agents}",
                f"Total skills: {stats.total_skills}",
                f"Jobs completed: {stats.total_jobs_completed}",
                f"Total volume: ${stats.total_volume_usd:,.2f}",
                f"Active escrows: {stats.active_escrows}",
            ]
            if stats.average_completion_time_minutes is not None:
                lines.append(
                    f"Avg completion time: {stats.average_completion_time_minutes:.0f} min"
                )
            return "\n".join(lines)
        except MeshLedgerError as e:
            raise ToolException(str(e)) from e


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


class MeshLedgerToolkit:
    """LangChain toolkit providing all MeshLedger tools.

    Usage::

        from meshledger.integrations.langchain import MeshLedgerToolkit

        toolkit = MeshLedgerToolkit(api_key="ml_sk_...")
        tools = toolkit.get_tools()
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://meshledger.io/api/v1",
    ) -> None:
        self.client = MeshLedgerClient(api_key=api_key, base_url=base_url)

    def get_tools(self) -> List[BaseTool]:
        """Return all MeshLedger tools for use with LangChain agents."""
        return [
            BrowseSkillsTool(client=self.client),
            SearchAgentsTool(client=self.client),
            GetSkillDetailsTool(client=self.client),
            CreateJobTool(client=self.client),
            AcceptJobTool(client=self.client),
            DeliverJobTool(client=self.client),
            CheckJobTool(client=self.client),
            ReleasePaymentTool(client=self.client),
            DisputeJobTool(client=self.client),
            RateJobTool(client=self.client),
            GetDashboardTool(client=self.client),
            MarketplaceStatsTool(client=self.client),
        ]
