"""MCP Plugin — Auto-Dispatch, Consensus, and Direct Messaging.

Wires three dark matter modules into the MCP tool surface:
  - auto_dispatch: task routing, pull queue, rebalancing
  - consensus: proposals, voting, resolution
  - direct_messaging: private agent-to-agent messages
"""

import asyncio
import json

from mcp.types import TextContent, Tool, ToolAnnotations

_SAFE_WRITE = ToolAnnotations(readOnlyHint=False, destructiveHint=False, idempotentHint=False, openWorldHint=False)
_SAFE_READ = ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=False)

TOOLS = [
    # ── Auto-Dispatch ─────────────────────────────────────────────────
    Tool(
        name="broker_dispatch_all",
        description="Scan all pending tasks and auto-dispatch them to the best available agents based on skill matching and reputation.",
        inputSchema={"type": "object", "properties": {}},
        annotations=_SAFE_WRITE,
    ),
    Tool(
        name="broker_pull_task",
        description="Pull the next best-matching pending task from the queue. Agent grabs work instead of waiting for assignment.",
        inputSchema={
            "type": "object",
            "properties": {
                "skills": {"type": "array", "items": {"type": "string"}, "description": "Your skills to match against tasks"},
                "min_priority": {"type": "string", "description": "Minimum priority: low, normal, high, urgent", "default": "low"},
            },
        },
        annotations=_SAFE_WRITE,
    ),
    Tool(
        name="broker_queue_depth",
        description="Show the number of pending tasks in the queue, broken down by priority.",
        inputSchema={
            "type": "object",
            "properties": {
                "min_priority": {"type": "string", "description": "Minimum priority to count", "default": "low"},
            },
        },
        annotations=_SAFE_READ,
    ),
    Tool(
        name="broker_rebalance",
        description="Find tasks stuck in 'accepted' status for too long and re-queue them for re-dispatch.",
        inputSchema={
            "type": "object",
            "properties": {
                "stuck_minutes": {"type": "integer", "description": "Minutes after which a task is considered stuck", "default": 30},
            },
        },
        annotations=_SAFE_WRITE,
    ),
    Tool(
        name="broker_dispatch_stats",
        description="Get aggregate dispatch statistics: total dispatches, by action type, average latency, most-used agents.",
        inputSchema={"type": "object", "properties": {}},
        annotations=_SAFE_READ,
    ),
    # ── Consensus ─────────────────────────────────────────────────────
    Tool(
        name="broker_create_proposal",
        description="Create a new proposal for agents to vote on. Requires at least 2 options.",
        inputSchema={
            "type": "object",
            "properties": {
                "description": {"type": "string", "description": "What is being decided"},
                "options": {"type": "array", "items": {"type": "string"}, "description": "List of options to vote on (min 2)"},
                "duration_seconds": {"type": "integer", "description": "How long voting stays open", "default": 300},
                "quorum": {"type": "integer", "description": "Minimum votes required to resolve", "default": 1},
            },
            "required": ["description", "options"],
        },
        annotations=_SAFE_WRITE,
    ),
    Tool(
        name="broker_vote",
        description="Cast or change your vote on an open proposal.",
        inputSchema={
            "type": "object",
            "properties": {
                "proposal_id": {"type": "string", "description": "The proposal to vote on"},
                "option": {"type": "string", "description": "Your chosen option"},
            },
            "required": ["proposal_id", "option"],
        },
        annotations=_SAFE_WRITE,
    ),
    Tool(
        name="broker_resolve_proposal",
        description="Resolve a proposal and determine the winner based on votes cast.",
        inputSchema={
            "type": "object",
            "properties": {
                "proposal_id": {"type": "string", "description": "The proposal to resolve"},
            },
            "required": ["proposal_id"],
        },
        annotations=_SAFE_WRITE,
    ),
    Tool(
        name="broker_list_proposals",
        description="List all open proposals that are waiting for votes.",
        inputSchema={"type": "object", "properties": {}},
        annotations=_SAFE_READ,
    ),
    # ── Direct Messaging ──────────────────────────────────────────────
    Tool(
        name="broker_dm",
        description="Send a private direct message to another agent. Only the recipient can read it.",
        inputSchema={
            "type": "object",
            "properties": {
                "to": {"type": "string", "description": "Recipient session_id or session_name"},
                "message": {"type": "string", "description": "The private message content"},
            },
            "required": ["to", "message"],
        },
        annotations=_SAFE_WRITE,
    ),
    Tool(
        name="broker_dm_inbox",
        description="Read your private direct messages.",
        inputSchema={
            "type": "object",
            "properties": {
                "limit": {"type": "integer", "description": "Max messages to return", "default": 20},
            },
        },
        annotations=_SAFE_READ,
    ),
]


# ── Handlers ──────────────────────────────────────────────────────────────

async def _handle_dispatch_all(args: dict) -> list[TextContent]:
    from brynq.auto_dispatch import dispatch_all_pending
    result = await asyncio.to_thread(dispatch_all_pending)
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def _handle_pull_task(args: dict) -> list[TextContent]:
    from brynq.auto_dispatch import pull_next_task
    from brynq.server import SESSION_ID
    skills = args.get("skills")
    min_pri = args.get("min_priority", "low")
    task = await asyncio.to_thread(pull_next_task, SESSION_ID, skills, min_pri)
    if task:
        return [TextContent(type="text", text=f"Task pulled:\n{json.dumps(task, indent=2)}")]
    return [TextContent(type="text", text="No matching tasks in queue.")]


async def _handle_queue_depth(args: dict) -> list[TextContent]:
    from brynq.auto_dispatch import get_queue_depth
    result = await asyncio.to_thread(get_queue_depth, args.get("min_priority", "low"))
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def _handle_rebalance(args: dict) -> list[TextContent]:
    from brynq.auto_dispatch import rebalance_stuck_tasks
    minutes = args.get("stuck_minutes", 30)
    result = await asyncio.to_thread(rebalance_stuck_tasks, minutes)
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def _handle_dispatch_stats(args: dict) -> list[TextContent]:
    from brynq.auto_dispatch import get_dispatch_stats
    result = await asyncio.to_thread(get_dispatch_stats)
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def _handle_create_proposal(args: dict) -> list[TextContent]:
    from brynq.consensus import ConsensusManager
    from brynq.server import SESSION_ID
    cm = ConsensusManager()
    result = await asyncio.to_thread(
        cm.create_proposal,
        args["description"], args["options"],
        created_by=SESSION_ID,
        duration_sec=args.get("duration_seconds", 300),
        quorum=args.get("quorum", 1),
    )
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def _handle_vote(args: dict) -> list[TextContent]:
    from brynq.consensus import ConsensusManager
    from brynq.server import SESSION_ID
    cm = ConsensusManager()
    result = await asyncio.to_thread(cm.vote, args["proposal_id"], SESSION_ID, args["option"])
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def _handle_resolve_proposal(args: dict) -> list[TextContent]:
    from brynq.consensus import ConsensusManager
    cm = ConsensusManager()
    result = await asyncio.to_thread(cm.resolve, args["proposal_id"])
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def _handle_list_proposals(args: dict) -> list[TextContent]:
    from brynq.consensus import ConsensusManager
    cm = ConsensusManager()
    proposals = await asyncio.to_thread(cm.list_open)
    if not proposals:
        return [TextContent(type="text", text="No open proposals.")]
    return [TextContent(type="text", text=json.dumps(proposals, indent=2))]


async def _handle_dm(args: dict) -> list[TextContent]:
    from brynq.direct_messaging import send_dm
    from brynq.server import SESSION_ID
    result = await asyncio.to_thread(send_dm, SESSION_ID, args["to"], args["message"])
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


async def _handle_dm_inbox(args: dict) -> list[TextContent]:
    from brynq.direct_messaging import read_dms
    from brynq.server import SESSION_ID
    limit = args.get("limit", 20)
    result = await asyncio.to_thread(read_dms, SESSION_ID, limit=limit)
    if not result.get("messages"):
        return [TextContent(type="text", text="No direct messages.")]
    return [TextContent(type="text", text=json.dumps(result, indent=2))]


HANDLERS = {
    "broker_dispatch_all": _handle_dispatch_all,
    "broker_pull_task": _handle_pull_task,
    "broker_queue_depth": _handle_queue_depth,
    "broker_rebalance": _handle_rebalance,
    "broker_dispatch_stats": _handle_dispatch_stats,
    "broker_create_proposal": _handle_create_proposal,
    "broker_vote": _handle_vote,
    "broker_resolve_proposal": _handle_resolve_proposal,
    "broker_list_proposals": _handle_list_proposals,
    "broker_dm": _handle_dm,
    "broker_dm_inbox": _handle_dm_inbox,
}
