"""Claude-tier MCP tools (cohort[claude]).

Gated because these tools require one or more of:
- httpx (server connection for orchestration)
- LLM inference (condensation, discussion)
- Privileged operations (agent creation)

Install: pip install cohort[claude]
"""

from __future__ import annotations

import logging
from typing import List, Optional

import httpx  # noqa: F401 -- gate: ImportError if [claude] extra not installed
from mcp.types import ToolAnnotations
from pydantic import BaseModel, ConfigDict, Field

logger = logging.getLogger(__name__)

CHARACTER_LIMIT = 25000


# =====================================================================
# Input models
# =====================================================================

class CondenseChannelInput(BaseModel):
    """Input for condensing a channel."""
    model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")

    channel: str = Field(
        ..., description="Channel ID to condense",
        min_length=1, max_length=100,
    )
    keep_last: int = Field(
        5, description="Number of recent messages to keep (default 5).",
        ge=1, le=50,
    )


class CreateAgentInput(BaseModel):
    """Input for creating a new agent."""
    model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")

    name: str = Field(..., description="Agent display name (e.g. 'Python Developer')", min_length=1)
    role: str = Field(..., description="Agent role (e.g. 'Senior Python Engineer')", min_length=1)
    primary_task: str = Field("", description="Primary task description")
    agent_type: str = Field(
        "specialist",
        description="Type: specialist, orchestrator, supervisor, infrastructure, utility",
    )
    capabilities: List[str] = Field(default_factory=list, description="List of capabilities")
    domain_expertise: List[str] = Field(
        default_factory=list, description="List of domain expertise areas",
    )
    personality: str = Field("", description="Personality description")
    triggers: List[str] = Field(default_factory=list, description="Trigger words for routing")
    avatar: str = Field("", description="Two-letter avatar code")
    color: str = Field("#95A5A6", description="Hex color code")
    group: str = Field("Agents", description="Agent group/category")


class DiscussionInput(BaseModel):
    """Input for running a multi-agent discussion."""
    model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")

    channel: str = Field(
        ..., description="Channel ID to run the discussion in.",
        min_length=1, max_length=100,
    )
    agents: List[str] = Field(
        ..., description="List of agent IDs to solicit responses from.",
        min_length=1,
    )
    prompt: str = Field(
        ..., description="The question or topic to pose to all agents.",
        min_length=1,
    )
    sender: str = Field(
        "claude_code", description="Who is asking the question (default 'claude_code').",
        max_length=100,
    )
    timeout_per_agent: int = Field(
        90, description="Max seconds to wait for each agent's response (default 90, max 300).",
        ge=10, le=300,
    )


# Deprecated alias
RoundtableInput = DiscussionInput


class AdoptPersonaInput(BaseModel):
    """Input for loading an agent persona."""
    model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")

    agent_id: str = Field(
        ..., description="Agent ID to adopt (e.g. 'python_developer', 'marketing_agent').",
        min_length=1, max_length=100,
    )
    full: bool = Field(
        False, description="If true, return full agent prompt instead of lightweight persona.",
    )
    channel: Optional[str] = Field(
        None,
        description="Channel ID to include conversation context from. "
        "When set, the agent sees recent channel messages, its memory, "
        "grounding rules, and other agents in the channel.",
        max_length=100,
    )


class CompiledDiscussionInput(BaseModel):
    """Input for compiled (single-call) multi-agent discussion."""
    model_config = ConfigDict(extra="forbid")

    agents: List[str] = Field(
        description="Agent IDs to participate (3-8 agents).",
        min_length=1,
    )
    topic: str = Field(
        description="Discussion topic or question.",
        min_length=1,
    )
    context: str = Field(
        default="",
        description="Optional background context or prior discussion summary.",
    )
    rounds: int = Field(
        default=2, ge=1, le=3,
        description="Number of discussion rounds (1-3). Default 2.",
    )
    model: Optional[str] = Field(
        default=None,
        description="Ollama model to use. Auto-selects if omitted.",
    )
    temperature: float = Field(
        default=0.30, ge=0.0, le=1.0,
        description="Generation temperature.",
    )
    channel: Optional[str] = Field(
        default=None,
        description="Channel to post results to. If omitted, results are returned inline only.",
    )
    sender: str = Field(
        default="claude_code",
        description="Sender identity for channel messages.",
    )


# Deprecated alias
CompiledRoundtableInput = CompiledDiscussionInput


# =====================================================================
# Tool registration
# =====================================================================

def _error_msg(service_down: bool = False) -> str:
    if service_down:
        return "Error: Cannot connect to chat server. Is it running?"
    return "Error: Unexpected error communicating with chat server."


def register_claude_tools(mcp, client) -> None:
    """Register claude-tier tools onto the MCP server.

    Called from server.py via try/except ImportError -- if httpx is not
    installed (cohort[mcp] only), this module fails to import and the
    tools are silently skipped.
    """

    # =================================================================
    # condense_channel
    # =================================================================

    @mcp.tool(
        name="condense_channel",
        annotations=ToolAnnotations(
            title="Condense Channel",
            readOnlyHint=False,
            destructiveHint=False,
            idempotentHint=False,
            openWorldHint=False,
        ),
    )
    async def condense_channel(params: CondenseChannelInput) -> str:
        """Condense a channel: summarise old messages and archive originals."""
        result = await client.condense_channel(params.channel, keep_last=params.keep_last)
        if result is None:
            return _error_msg(service_down=True)
        if result.get("success"):
            archived = result.get("archived_count", 0)
            archive_ch = result.get("archive_channel", "?")
            preview = result.get("summary_preview", "")
            if archived == 0:
                return result.get("message") or f"Nothing to condense in #{params.channel}."
            return (
                f"Condensed #{params.channel}: {archived} messages archived to #{archive_ch}\n\n"
                f"**Summary preview**: {preview}"
            )
        return f"Error condensing #{params.channel}: {result.get('error', 'Unknown')}"

    # =================================================================
    # cohort_create_agent
    # =================================================================

    @mcp.tool(
        name="cohort_create_agent",
        annotations=ToolAnnotations(
            title="Create Agent",
            readOnlyHint=False,
            destructiveHint=False,
            idempotentHint=False,
            openWorldHint=False,
        ),
    )
    async def cohort_create_agent(params: CreateAgentInput) -> str:
        """Create a new agent with directory structure, config, prompt, and memory."""
        result = await client.create_agent(params.model_dump())
        if result is None:
            return _error_msg(service_down=True)
        if result.get("success"):
            aid = result.get("agent_id", "?")
            return f"Created agent **{params.name}** (id: `{aid}`)"
        return f"Error creating agent: {result.get('error', 'Unknown')}"

    # =================================================================
    # cohort_discussion
    # =================================================================

    @mcp.tool(
        name="cohort_discussion",
        annotations=ToolAnnotations(
            title="Multi-Agent Discussion",
            readOnlyHint=False,
            destructiveHint=False,
            idempotentHint=False,
            openWorldHint=False,
        ),
    )
    async def cohort_discussion(params: DiscussionInput) -> str:
        """Run a scored multi-agent discussion.

        Posts the prompt with @mentions to the channel, starts a scored
        session via the orchestrator, and returns speaker recommendations
        with relevance scores.  In full server mode, agents respond
        sequentially.  In lite mode, the scoring engine determines the
        optimal speaking order and returns it for the host LLM to
        orchestrate.
        """
        import asyncio

        # Post the prompt with @mentions
        mentions = " ".join(f"@{a}" for a in params.agents)
        full_message = f"{mentions}\n\n{params.prompt}"
        post_result = await client.post_message(
            params.channel, params.sender, full_message,
        )
        if post_result is None:
            return _error_msg(service_down=True)

        # Start the session (orchestrator scores agents)
        result = await client.start_session(
            channel=params.channel,
            agents=params.agents,
            prompt=params.prompt,
            sender=params.sender,
        )
        if result is None:
            return _error_msg(service_down=True)
        if not result.get("success") and not result.get("session_id"):
            return f"Error starting session: {result.get('error', 'Unknown')}"

        session_id = result.get("session_id", "?")

        # If we got scoring data back (lite mode with orchestrator),
        # format the scored speaker order for the host LLM
        if result.get("all_scores"):
            lines = [
                f"## Discussion Session in #{params.channel}",
                f"**Session**: `{session_id}`",
                f"**Topic**: {params.prompt[:200]}",
                f"**Phase**: {result.get('phase', 'DISCOVER')}",
                "",
                "### Scored Speaker Order",
                "",
                "| Rank | Agent | Score | Status | Reason |",
                "|------|-------|-------|--------|--------|",
            ]
            for i, entry in enumerate(result["all_scores"], 1):
                agent_id = entry.get("agent_id", "?")
                score = entry.get("score", 0)
                status = entry.get("status", "?")
                breakdown = entry.get("breakdown", {})
                # Build reason from top scoring dimensions
                reasons = []
                if breakdown.get("domain_expertise", 0) > 0.3:
                    reasons.append(f"domain:{breakdown['domain_expertise']:.2f}")
                if breakdown.get("complementary_value", 0) > 0.2:
                    reasons.append(f"complement:{breakdown['complementary_value']:.2f}")
                if breakdown.get("phase_alignment", 0) > 0.5:
                    reasons.append(f"phase:{breakdown['phase_alignment']:.2f}")
                if breakdown.get("data_ownership", 0) > 0.2:
                    reasons.append(f"data:{breakdown['data_ownership']:.2f}")
                if entry.get("contribution_bonus", 0) > 0:
                    reasons.append("first-contribution")
                reason_str = ", ".join(reasons) if reasons else "baseline"
                marker = " **[NEXT]**" if i == 1 else ""
                lines.append(
                    f"| {i} | @{agent_id}{marker} | {score:.2f} | {status} | {reason_str} |"
                )

            # Include channel context so the host LLM sees the conversation
            channel_ctx = await client.get_channel_context(params.channel)
            if channel_ctx:
                lines.append(channel_ctx)

            lines.append("")
            lines.append(
                "Adopt each agent's persona (via `cohort_adopt_persona` with "
                f'channel="{params.channel}") in the scored order above to run '
                f"the discussion. Each agent will receive the channel context, "
                f"their memory, and collaboration awareness. Post each response "
                f"to #{params.channel} as that agent."
            )
            return "\n".join(lines)

        # Full server mode: poll for agent responses
        final_status = None
        for _ in range(params.timeout_per_agent // 5):
            await asyncio.sleep(5)
            status = await client.get_session_status(session_id)
            if status is None:
                continue
            state = status.get("status") or status.get("state", "")
            if state in ("completed", "ended", "done"):
                final_status = status
                break
            # Check if all agents have responded
            turns = status.get("turns", [])
            responded = {t.get("agent_id") for t in turns if t.get("agent_id")}
            if responded >= set(params.agents):
                final_status = status
                break

        # Format results
        if final_status:
            turns = final_status.get("turns", [])
            lines = [f"## Discussion in #{params.channel} ({len(turns)} responses)"]
            for turn in turns:
                agent = turn.get("agent_id", "unknown")
                content = (turn.get("content") or turn.get("response") or "")[:2000]
                lines.append(f"\n### > {agent}:\n{content}")
            result_text = "\n".join(lines)
            if len(result_text) > CHARACTER_LIMIT:
                result_text = result_text[:CHARACTER_LIMIT] + "\n\n*[truncated]*"
            return result_text

        return (
            f"Discussion started (session: {session_id}) but timed out waiting for responses. "
            f"Check #{params.channel} for agent replies."
        )

    # Deprecated alias
    cohort_roundtable = cohort_discussion  # noqa: F841

    # =================================================================
    # cohort_adopt_persona
    # =================================================================

    @mcp.tool(
        name="cohort_adopt_persona",
        annotations=ToolAnnotations(
            title="Adopt Agent Persona",
            readOnlyHint=True,
            destructiveHint=False,
            idempotentHint=True,
            openWorldHint=False,
        ),
    )
    async def cohort_adopt_persona(params: AdoptPersonaInput) -> str:
        """Load an agent persona with optional channel context.

        Returns the persona text that should be used as a system-level
        identity for the rest of the conversation.  When a channel is
        specified, also includes the agent's memory, grounding rules,
        collaboration awareness, and recent channel messages so the
        agent can see the conversation.
        """
        from cohort.personas import load_persona

        persona_text = None
        mode_label = ""

        if params.full:
            prompt = await client.get_agent_persona(params.agent_id)
            if prompt:
                persona_text = prompt
                mode_label = " (full mode)"

        if not persona_text:
            persona_text = load_persona(params.agent_id)

        if not persona_text:
            agent_data = await client.get_agent(params.agent_id)
            if agent_data and not agent_data.get("error"):
                name = agent_data.get("name", params.agent_id)
                role = agent_data.get("role", "Agent")
                personality = agent_data.get("personality", "")
                caps = agent_data.get("capabilities", [])
                persona_text = (
                    f"# {name}\n"
                    f"**Role**: {role}\n"
                    + (f"**Personality**: {personality}\n" if personality else "")
                    + (f"**Capabilities**: {', '.join(caps)}\n" if caps else "")
                )

        if not persona_text:
            return (
                f"Agent '{params.agent_id}' not found. "
                f"Use `cohort_list_agents` to see available agents."
            )

        # Build output: persona + optional context
        parts = [
            f"## Adopted: {params.agent_id}{mode_label}",
            "",
            "Use the following as your identity for this conversation:",
            "",
            f"---\n{persona_text}\n---",
        ]

        # Inject full agent context if a channel is specified
        if params.channel:
            context = await client.build_agent_context(
                params.agent_id, params.channel,
            )
            if context:
                parts.append("")
                parts.append(context)

        output = "\n".join(parts)
        if len(output) > CHARACTER_LIMIT:
            output = output[:CHARACTER_LIMIT] + "\n\n*[truncated]*"
        return output

    # =================================================================
    # cohort_compiled_discussion
    # =================================================================

    @mcp.tool(
        name="cohort_compiled_discussion",
        annotations=ToolAnnotations(
            title="Compiled Discussion (Single-Call)",
            readOnlyHint=False,
            destructiveHint=False,
            idempotentHint=False,
            openWorldHint=True,
        ),
    )
    async def cohort_compiled_discussion(params: CompiledDiscussionInput) -> str:
        """Run a compiled discussion: all agent personas in a single LLM call.

        ~90% token reduction vs separate per-agent calls. Best for 3-8 agent
        planning/review discussions. Uses local Ollama inference.

        If a channel is specified, a scored orchestrator session is started
        and individual agent responses are posted as separate messages to the
        channel (each recorded as a scored turn).
        """
        from cohort.compiled_roundtable import run_compiled_roundtable

        # Start a scored session if posting to a channel
        session_id = None
        if params.channel:
            session_result = await client.start_session(
                channel=params.channel,
                agents=params.agents,
                prompt=params.topic,
                sender=params.sender,
            )
            if session_result and session_result.get("success"):
                session_id = session_result.get("session_id")

        # Auto-populate context from channel history if not user-supplied
        discussion_context = params.context
        if params.channel:
            channel_ctx = await client.get_channel_context(params.channel)
            if channel_ctx:
                if discussion_context.strip():
                    # User provided explicit context -- prepend channel history
                    discussion_context = channel_ctx + "\n\n" + discussion_context
                else:
                    discussion_context = channel_ctx

        result = run_compiled_roundtable(
            agents=params.agents,
            topic=params.topic,
            context=discussion_context,
            rounds=params.rounds,
            model=params.model,
            temperature=params.temperature,
        )

        if result.error:
            return f"Compiled discussion failed: {result.error}"

        # Format output
        lines = [
            f"## Compiled Discussion ({len(result.agent_responses)}/{len(params.agents)} agents)",
        ]
        if session_id:
            lines.append(f"*Session: `{session_id}`*")

        if result.metadata:
            model_used = result.metadata.get("model", "unknown")
            latency = result.metadata.get("latency_ms", 0)
            lines.append(f"*Model: {model_used}, {latency}ms*\n")

        for agent_id, response in result.agent_responses.items():
            lines.append(f"### > {agent_id}:")
            lines.append(response)
            lines.append("")

        if result.synthesis:
            lines.append("### Synthesis:")
            lines.append(result.synthesis)
            lines.append("")

        # Post to channel if specified (each post records a scored turn)
        if params.channel and result.agent_responses:
            for agent_id, response in result.agent_responses.items():
                await client.post_message(
                    params.channel, agent_id, response,
                )
            if result.synthesis:
                await client.post_message(
                    params.channel, params.sender,
                    f"**Discussion Synthesis:**\n{result.synthesis}",
                )
            lines.append(f"\n*Results posted to #{params.channel}*")

        # Warn about missing agents
        missing = set(params.agents) - set(result.agent_responses.keys())
        if missing:
            lines.append(f"\n*Missing responses from: {', '.join(sorted(missing))}*")

        output = "\n".join(lines)
        if len(output) > CHARACTER_LIMIT:
            output = output[:CHARACTER_LIMIT] + "\n\n*[truncated]*"
        return output

    # Deprecated alias
    cohort_compiled_roundtable = cohort_compiled_discussion  # noqa: F841

    # =================================================================
    # WQ Session Harness Tools
    # (cohort_ready / cohort_respond / cohort_post / cohort_error)
    # =================================================================

    import os as _os
    wq_worker_url = _os.environ.get(
        "WQ_WORKER_URL", "http://127.0.0.1:8102",
    )

    class CohortReadyInput(BaseModel):
        model_config = ConfigDict(
            str_strip_whitespace=True, extra="forbid",
        )
        channel: str = Field(
            ..., description="Channel ID this session is consuming.",
            min_length=1, max_length=100,
        )
        session_id: Optional[str] = Field(
            None, description="Optional stable identifier.",
            max_length=200,
        )

    @mcp.tool(
        name="cohort_ready",
        annotations=ToolAnnotations(
            title="Signal Session Ready",
            readOnlyHint=False, destructiveHint=False,
            idempotentHint=True, openWorldHint=False,
        ),
    )
    async def cohort_ready(params: CohortReadyInput) -> str:
        """Signal to the WQ dispatcher that this session is ready.

        Call once at session startup before processing work items.
        """
        payload = {
            "channel": params.channel,
            "session_id": params.session_id or "",
        }
        try:
            async with httpx.AsyncClient(timeout=5) as hc:
                resp = await hc.post(
                    f"{wq_worker_url}/ready", json=payload,
                )
                resp.raise_for_status()
                data = resp.json()
                queued = data.get("queued_items", 0)
                suffix = (
                    f" ({queued} item(s) queued)" if queued else ""
                )
                return (
                    f"[OK] Session registered on "
                    f"#{params.channel}{suffix}"
                )
        except Exception as exc:
            return f"[!] Could not reach WQ dispatcher: {exc}"

    class CohortRespondInput(BaseModel):
        model_config = ConfigDict(
            str_strip_whitespace=True, extra="forbid",
        )
        item_id: str = Field(
            ..., description="Work queue item ID to complete.",
            min_length=1, max_length=100,
        )
        response: str = Field(
            ..., description="Response content to post.",
            min_length=1, max_length=10000,
        )
        channel: str = Field(
            ..., description="Channel to post the response to.",
            min_length=1, max_length=100,
        )
        sender: str = Field(
            "claude_code", description="Agent ID to post as.",
            min_length=1, max_length=100,
        )

    @mcp.tool(
        name="cohort_respond",
        annotations=ToolAnnotations(
            title="Complete Item and Respond",
            readOnlyHint=False, destructiveHint=False,
            idempotentHint=False, openWorldHint=False,
        ),
    )
    async def cohort_respond(params: CohortRespondInput) -> str:
        """Complete a WQ item and post the result to the channel."""
        complete_result = await client.update_work_item(
            params.item_id, "completed",
            result=params.response[:2000],
        )
        if complete_result is None:
            return _error_msg(service_down=True)
        if "error" in complete_result:
            return (
                f"Error completing item: {complete_result['error']}"
            )
        post_result = await client.post_message(
            params.channel, params.sender, params.response,
            metadata={
                "wq_item_id": params.item_id,
                "source": "wq_response",
            },
        )
        if post_result is None:
            return (
                f"Completed {params.item_id} but could not post."
            )
        return (
            f"[OK] Completed {params.item_id} "
            f"and posted to #{params.channel}"
        )

    class CohortPostInput(BaseModel):
        model_config = ConfigDict(
            str_strip_whitespace=True, extra="forbid",
        )
        channel: str = Field(
            ..., description="Channel ID to post to.",
            min_length=1, max_length=100,
        )
        sender: str = Field(
            ..., description="Agent ID to post as.",
            min_length=1, max_length=100,
        )
        content: str = Field(
            ..., description="Message content.",
            min_length=1, max_length=10000,
        )
        thread_id: Optional[str] = Field(
            None, description="Optional thread ID.",
            max_length=200,
        )

    @mcp.tool(
        name="cohort_post",
        annotations=ToolAnnotations(
            title="Post as Agent",
            readOnlyHint=False, destructiveHint=False,
            idempotentHint=False, openWorldHint=False,
        ),
    )
    async def cohort_post(params: CohortPostInput) -> str:
        """Post a message to a channel as a specific agent."""
        metadata: dict = {"source": "cohort_post"}
        if params.thread_id:
            metadata["thread_id"] = params.thread_id
        result = await client.post_message(
            params.channel, params.sender, params.content,
            metadata=metadata,
        )
        if result is None:
            return _error_msg(service_down=True)
        return (
            f"[OK] Posted to #{params.channel} as {params.sender}"
        )

    class CohortErrorInput(BaseModel):
        model_config = ConfigDict(
            str_strip_whitespace=True, extra="forbid",
        )
        item_id: str = Field(
            ..., description="Work queue item ID to fail.",
            min_length=1, max_length=100,
        )
        error: str = Field(
            ..., description="Error description.",
            min_length=1, max_length=2000,
        )
        channel: str = Field(
            ..., description="Channel to post error to.",
            min_length=1, max_length=100,
        )
        sender: str = Field(
            "claude_code", description="Agent ID to post as.",
            min_length=1, max_length=100,
        )

    @mcp.tool(
        name="cohort_error",
        annotations=ToolAnnotations(
            title="Fail Item and Report Error",
            readOnlyHint=False, destructiveHint=False,
            idempotentHint=False, openWorldHint=False,
        ),
    )
    async def cohort_error(params: CohortErrorInput) -> str:
        """Mark a WQ item as failed and post an error notice."""
        fail_result = await client.update_work_item(
            params.item_id, "failed",
            result=f"Error: {params.error}",
        )
        if fail_result is None:
            return _error_msg(service_down=True)
        if "error" in fail_result:
            return (
                f"Error updating item: {fail_result['error']}"
            )
        error_msg = (
            f"[!] Work item {params.item_id} failed: "
            f"{params.error}"
        )
        await client.post_message(
            params.channel, params.sender, error_msg,
            metadata={
                "wq_item_id": params.item_id,
                "source": "wq_error",
            },
        )
        return (
            f"[X] Failed {params.item_id} "
            f"and posted error to #{params.channel}"
        )
