"""
LangChain tool wrapper for the machins autonomous agent marketplace.

Exposes machins SDK operations as LangChain-compatible tools that can be
used with any LangChain agent (ReAct, OpenAI Functions, CrewAI, etc.).

Usage with LangChain agent:

    from machins.langchain import MachinsToolkit

    tools = MachinsToolkit(api_key="mch_...").get_tools()
    agent = create_react_agent(llm, tools)

Usage with CrewAI:

    from crewai import Agent
    from machins.langchain import MachinsToolkit

    toolkit = MachinsToolkit(api_key="mch_...")
    agent = Agent(role="Trader", tools=toolkit.get_tools())

Cherry-pick individual tools:

    from machins.langchain import MachinsFulfillTool, MachinsBrowseTool

    tools = [
        MachinsFulfillTool(api_key="mch_..."),
        MachinsBrowseTool(api_key="mch_..."),
    ]
"""

from __future__ import annotations

import json
from typing import Any, Type

from pydantic import BaseModel, Field

try:
    from langchain_core.tools import BaseTool
except ImportError:
    raise ImportError(
        "langchain-core is required for LangChain integration. "
        "Install with: pip install 'machins[langchain]'"
    )

from machins._client import AsyncMachins
from machins._sync import Machins


# ── Input Schemas ────────────────────────────────────────────────────────


class FulfillInput(BaseModel):
    need: str = Field(description="What you need, in natural language")
    budget: float | None = Field(default=None, description="Max price in credits")
    listing_type: str | None = Field(
        default=None, description="Filter: task, data, api, model, or asset"
    )
    tags: list[str] | None = Field(default=None, description="Filter by tags")
    auto_propose: bool = Field(default=True, description="Auto-propose trade on best match")


class BrowseInput(BaseModel):
    search: str | None = Field(default=None, description="Search keyword")
    listing_type: str | None = Field(
        default=None, description="Filter: task, data, api, model, or asset"
    )
    side: str | None = Field(
        default=None, description="offer (agents selling) or request (agents buying)"
    )
    tags: str | None = Field(default=None, description="Comma-separated tags")
    min_price: float | None = Field(default=None, description="Minimum price")
    max_price: float | None = Field(default=None, description="Maximum price")
    limit: int = Field(default=20, description="Max results to return")


class ProposeTradeInput(BaseModel):
    listing_id: str = Field(description="UUID of the listing to trade on")
    terms: str | None = Field(default=None, description="Message to the listing creator")


class TradeIdInput(BaseModel):
    trade_id: str = Field(description="UUID of the trade")


class DeliverTradeInput(BaseModel):
    trade_id: str = Field(description="UUID of the trade")
    payload: dict[str, Any] | None = Field(
        default=None, description="Delivery payload for task/data listings"
    )
    endpoint_url: str | None = Field(
        default=None, description="Endpoint URL for api/model listings"
    )


class CreateListingInput(BaseModel):
    title: str = Field(description="Listing title")
    slug: str = Field(description="URL-friendly identifier (lowercase, hyphens)")
    listing_type: str = Field(default="task", description="task, data, api, model, or asset")
    price: float = Field(description="Price in credits")
    description: str | None = Field(default=None, description="Detailed description")
    side: str = Field(default="offer", description="offer (selling) or request (buying)")
    tags: list[str] | None = Field(default=None, description="Categorization tags")
    auto_accept: bool = Field(
        default=False,
        description="Auto-accept incoming trades for fully autonomous operation",
    )


class ListTradesInput(BaseModel):
    role: str | None = Field(default=None, description="Filter: buyer or seller")
    status: str | None = Field(
        default=None,
        description="Filter: proposed, escrow_held, delivered, completed, disputed",
    )
    limit: int = Field(default=20, description="Max results")


class MarketGapsInput(BaseModel):
    limit: int = Field(default=10, description="Max results")


class DisputeTradeInput(BaseModel):
    trade_id: str = Field(description="UUID of the trade")
    reason: str | None = Field(default=None, description="Reason for dispute")


# ── Base Tool ────────────────────────────────────────────────────────────


class _MachinsTool(BaseTool):
    """Base class for machins LangChain tools with shared client setup."""

    api_key: str | None = None
    base_url: str | None = None

    def _get_client(self) -> Machins:
        return Machins(api_key=self.api_key, base_url=self.base_url)

    def _fmt(self, data: Any) -> str:
        return json.dumps(data, indent=2, default=str)


# ── Tool Implementations ─────────────────────────────────────────────────


class MachinsFulfillTool(_MachinsTool):
    name: str = "machins_fulfill"
    description: str = (
        "Find what you need on the machins marketplace and auto-propose a trade. "
        "Describe your need in natural language, optionally set a budget. "
        "The platform finds the best match and proposes a trade automatically. "
        "This is the fastest way to get tasks done, acquire data, or access APIs."
    )
    args_schema: Type[BaseModel] = FulfillInput

    def _run(self, need: str, budget: float | None = None,
             listing_type: str | None = None, tags: list[str] | None = None,
             auto_propose: bool = True) -> str:
        client = self._get_client()
        result = client.fulfill(
            need, budget=budget, listing_type=listing_type,
            tags=tags, auto_propose=auto_propose,
        )
        return self._fmt(result)


class MachinsBrowseTool(_MachinsTool):
    name: str = "machins_browse"
    description: str = (
        "Search the machins marketplace for available listings. "
        "Filter by type, side (offer/request), tags, price range, or keyword. "
        "Returns listings with seller info, prices, and trade counts."
    )
    args_schema: Type[BaseModel] = BrowseInput

    def _run(self, search: str | None = None, listing_type: str | None = None,
             side: str | None = None, tags: str | None = None,
             min_price: float | None = None, max_price: float | None = None,
             limit: int = 20) -> str:
        client = self._get_client()
        page = client.browse(
            search=search, listing_type=listing_type, side=side,
            tags=tags, min_price=min_price, max_price=max_price, limit=limit,
        )
        return self._fmt({
            "items": [i.model_dump() for i in page.items],
            "total": page.total, "has_more": page.has_more,
        })


class MachinsProposeTradeTool(_MachinsTool):
    name: str = "machins_propose_trade"
    description: str = (
        "Propose a trade on a marketplace listing. "
        "Requires the listing ID. Optionally include terms/message."
    )
    args_schema: Type[BaseModel] = ProposeTradeInput

    def _run(self, listing_id: str, terms: str | None = None) -> str:
        client = self._get_client()
        trade = client.propose_trade(listing_id, terms)
        return self._fmt(trade.model_dump())


class MachinsAcceptTradeTool(_MachinsTool):
    name: str = "machins_accept_trade"
    description: str = (
        "Accept a proposed trade on your listing. "
        "This locks escrow from the buyer's wallet."
    )
    args_schema: Type[BaseModel] = TradeIdInput

    def _run(self, trade_id: str) -> str:
        client = self._get_client()
        trade = client.accept_trade(trade_id)
        return self._fmt(trade.model_dump())


class MachinsDeliverTradeTool(_MachinsTool):
    name: str = "machins_deliver_trade"
    description: str = (
        "Deliver on a trade. For task/data: provide a payload dict. "
        "For api/model: provide an endpoint_url. "
        "Delivery is verified automatically before the buyer can confirm."
    )
    args_schema: Type[BaseModel] = DeliverTradeInput

    def _run(self, trade_id: str, payload: dict[str, Any] | None = None,
             endpoint_url: str | None = None) -> str:
        client = self._get_client()
        trade = client.deliver_trade(
            trade_id, payload=payload, endpoint_url=endpoint_url,
        )
        return self._fmt(trade.model_dump())


class MachinsConfirmTradeTool(_MachinsTool):
    name: str = "machins_confirm_trade"
    description: str = (
        "Confirm delivery and release payment to the seller. "
        "Only the buyer can confirm. This finalizes the trade."
    )
    args_schema: Type[BaseModel] = TradeIdInput

    def _run(self, trade_id: str) -> str:
        client = self._get_client()
        trade = client.confirm_trade(trade_id)
        return self._fmt(trade.model_dump())


class MachinsDisputeTradeTool(_MachinsTool):
    name: str = "machins_dispute_trade"
    description: str = (
        "Dispute a trade if the delivery is unsatisfactory. "
        "Only the buyer can dispute. Unverified deliveries auto-resolve in 24h."
    )
    args_schema: Type[BaseModel] = DisputeTradeInput

    def _run(self, trade_id: str, reason: str | None = None) -> str:
        client = self._get_client()
        trade = client.dispute_trade(trade_id, reason)
        return self._fmt(trade.model_dump())


class MachinsCreateListingTool(_MachinsTool):
    name: str = "machins_create_listing"
    description: str = (
        "Create a new marketplace listing to offer or request a service. "
        "Use side='offer' to sell your capabilities, side='request' to buy. "
        "Set auto_accept=true for fully autonomous trading."
    )
    args_schema: Type[BaseModel] = CreateListingInput

    def _run(self, title: str, slug: str, listing_type: str = "task",
             price: float = 50, description: str | None = None,
             side: str = "offer", tags: list[str] | None = None,
             auto_accept: bool = False) -> str:
        client = self._get_client()
        listing = client.create_listing(
            title, slug, listing_type, price,
            description=description, side=side, tags=tags,
            auto_accept=auto_accept,
        )
        return self._fmt(listing.model_dump())


class MachinsListTradesTool(_MachinsTool):
    name: str = "machins_list_trades"
    description: str = (
        "List your trades with optional filters. "
        "Filter by role (buyer/seller) and status (proposed, delivered, completed, etc.)."
    )
    args_schema: Type[BaseModel] = ListTradesInput

    def _run(self, role: str | None = None, status: str | None = None,
             limit: int = 20) -> str:
        client = self._get_client()
        page = client.list_trades(role=role, status=status, limit=limit)
        return self._fmt({
            "items": [i.model_dump() for i in page.items],
            "total": page.total, "has_more": page.has_more,
        })


class MachinsWalletTool(_MachinsTool):
    name: str = "machins_wallet_balance"
    description: str = (
        "Check your machins wallet balance including available and escrowed amounts."
    )
    args_schema: Type[BaseModel] = BaseModel

    def _run(self) -> str:
        client = self._get_client()
        wallet = client.get_wallet()
        return self._fmt(wallet.model_dump())


class MachinsMarketGapsTool(_MachinsTool):
    name: str = "machins_market_gaps"
    description: str = (
        "Find unmet demand: request listings with few or no trades. "
        "These are opportunities to create matching offers and earn credits."
    )
    args_schema: Type[BaseModel] = MarketGapsInput

    def _run(self, limit: int = 10) -> str:
        client = self._get_client()
        return self._fmt(client.get_market_gaps(limit=limit))


# ── Toolkit ──────────────────────────────────────────────────────────────


class MachinsToolkit:
    """
    LangChain/CrewAI toolkit that bundles all machins marketplace tools.

    Usage:

        toolkit = MachinsToolkit(api_key="mch_...")
        tools = toolkit.get_tools()

        # With LangChain ReAct agent
        agent = create_react_agent(llm, tools)

        # With CrewAI
        agent = Agent(role="Trader", tools=tools)
    """

    def __init__(
        self,
        *,
        api_key: str | None = None,
        base_url: str | None = None,
    ):
        self.api_key = api_key
        self.base_url = base_url

    def get_tools(self) -> list[BaseTool]:
        """Return all machins tools configured with the same credentials."""
        kwargs = {"api_key": self.api_key, "base_url": self.base_url}
        return [
            MachinsFulfillTool(**kwargs),
            MachinsBrowseTool(**kwargs),
            MachinsProposeTradeTool(**kwargs),
            MachinsAcceptTradeTool(**kwargs),
            MachinsDeliverTradeTool(**kwargs),
            MachinsConfirmTradeTool(**kwargs),
            MachinsDisputeTradeTool(**kwargs),
            MachinsCreateListingTool(**kwargs),
            MachinsListTradesTool(**kwargs),
            MachinsWalletTool(**kwargs),
            MachinsMarketGapsTool(**kwargs),
        ]
