"""Cohort CLI entry point.

Usage::

    cohort say --sender architect --channel review --file conv.jsonl --message "Hello"
    cohort gate --agent architect --channel review --file conv.jsonl --agents agents.json
    cohort next-speaker --channel review --file conv.jsonl --agents agents.json
    cohort discuss --agents backend_dev,qa_engineer --channel review --prompt "Design the API"
    cohort roundtable --agents backend_dev,qa_engineer --topic "Connection pooling" --channel review
    cohort briefing generate --hours 24
    cohort setup
    cohort tutorial
    cohort mcp                   # Start MCP server (LLM integration)
    cohort mcp-local             # Start local LLM MCP server (Ollama)
"""

import argparse
import json
import sys
from typing import Any

# =====================================================================
# CLI command handlers
# =====================================================================

def _cmd_gate(args: argparse.Namespace) -> int:
    """Should this agent respond? Exit 0 = speak, exit 1 = don't."""
    from cohort.chat import Channel, ChatManager
    from cohort.file_transport import JsonlFileStorage, load_agents_from_file
    from cohort.meeting import (
        STAKEHOLDER_THRESHOLDS,
        StakeholderStatus,
        calculate_contribution_score,
        extract_keywords,
        initialize_meeting_context,
        should_agent_speak,
    )

    storage = JsonlFileStorage(args.file)
    chat = ChatManager(storage)
    agents = load_agents_from_file(args.agents)

    if args.agent not in agents:
        print(f"[X] Agent '{args.agent}' not found in {args.agents}", file=sys.stderr)
        return 2

    messages = chat.get_channel_messages(args.channel, limit=15)
    if not messages:
        print(f"[X] No messages in channel '{args.channel}'", file=sys.stderr)
        return 1

    # Build meeting context from agents
    meeting_ctx = initialize_meeting_context(list(agents.keys()))
    recent_text = " ".join(m.content for m in messages[-5:])
    topic_kw = extract_keywords(recent_text)
    meeting_ctx["current_topic"]["keywords"] = topic_kw

    # Build or update channel
    channel = chat.get_channel(args.channel)
    if channel is None:
        channel = Channel(
            id=args.channel, name=args.channel, description="",
            created_at="", meeting_context=meeting_ctx,
        )
    else:
        channel.meeting_context = meeting_ctx

    agent_config = agents[args.agent]
    last_message = messages[-1]
    status = StakeholderStatus.ACTIVE.value
    threshold = STAKEHOLDER_THRESHOLDS[status]

    speak = should_agent_speak(
        args.agent, last_message, channel, chat, agent_config,
    )
    score = calculate_contribution_score(
        args.agent, "[considering response]",
        meeting_ctx, agent_config, messages,
    )

    decision = "SPEAK" if speak else "SILENT"
    reason = (
        f"score {score:.2f} >= threshold {threshold:.2f}"
        if speak
        else f"score {score:.2f} < threshold {threshold:.2f}"
    )

    if args.format == "json":
        print(json.dumps({
            "agent": args.agent,
            "score": round(score, 4),
            "threshold": threshold,
            "status": status,
            "speak": speak,
            "reason": reason,
        }))
    else:
        print(f"Agent:     {args.agent}")
        print(f"Score:     {score:.2f}")
        print(f"Threshold: {threshold:.2f} ({status})")
        print(f"Decision:  {decision}")
        print(f"Reason:    {reason}")

    return 0 if speak else 1


def _cmd_next_speaker(args: argparse.Namespace) -> int:
    """Who should talk next? Ranked by composite relevance."""
    from cohort.chat import ChatManager
    from cohort.file_transport import JsonlFileStorage, load_agents_from_file
    from cohort.meeting import (
        calculate_composite_relevance,
        extract_keywords,
        initialize_meeting_context,
    )

    storage = JsonlFileStorage(args.file)
    chat = ChatManager(storage)
    agents = load_agents_from_file(args.agents)

    messages = chat.get_channel_messages(args.channel, limit=15)
    if not messages:
        print(f"[X] No messages in channel '{args.channel}'", file=sys.stderr)
        return 1

    # Build meeting context
    meeting_ctx = initialize_meeting_context(list(agents.keys()))
    recent_text = " ".join(m.content for m in messages[-5:])
    topic_kw = extract_keywords(recent_text)
    meeting_ctx["current_topic"]["keywords"] = topic_kw

    # Score all agents
    scores: list[dict[str, Any]] = []
    for agent_id, agent_config in agents.items():
        relevance = calculate_composite_relevance(
            agent_id=agent_id,
            meeting_context=meeting_ctx,
            agent_config=agent_config,
            recent_messages=messages,
        )
        scores.append({
            "agent_id": agent_id,
            "score": relevance["composite_total"],
            "phase": relevance.get("detected_phase", "unknown"),
            "breakdown": {
                k: round(v, 3) for k, v in relevance.items()
                if k not in ("composite_total", "detected_phase")
            },
        })
    scores.sort(key=lambda x: x["score"], reverse=True)
    top = scores[: args.top]

    if args.format == "json":
        print(json.dumps(top, indent=2))
    else:
        print(f"Next speaker for channel '{args.channel}':")
        for i, entry in enumerate(top, 1):
            bd = entry["breakdown"]
            top_dims = sorted(bd.items(), key=lambda x: x[1], reverse=True)[:2]
            dims_str = ", ".join(f"{k}={v:.2f}" for k, v in top_dims)
            print(
                f"  {i}. {entry['agent_id']:20s}  "
                f"score={entry['score']:.2f}  "
                f"phase={entry['phase']}  ({dims_str})"
            )

    return 0


def _cmd_briefing(args: argparse.Namespace) -> int:
    """Generate or view executive briefings."""
    import os
    from pathlib import Path

    from cohort.chat import ChatManager
    from cohort.executive_briefing import ExecutiveBriefing
    from cohort.registry import create_storage

    resolved_dir = os.environ.get("COHORT_DATA_DIR", getattr(args, "data_dir", "data"))
    storage = create_storage(resolved_dir)
    chat = ChatManager(storage)

    # Try to load work queue (optional)
    work_queue = None
    try:
        from cohort.work_queue import WorkQueue
        work_queue = WorkQueue(Path(resolved_dir))
    except Exception:
        pass

    briefing = ExecutiveBriefing(
        data_dir=Path(resolved_dir),
        chat=chat,
        work_queue=work_queue,
    )

    brief_cmd = getattr(args, "brief_command", None)

    if brief_cmd == "generate":
        report = briefing.generate(
            hours=args.hours,
            post_to_channel=not args.no_post,
        )
        if args.format == "json":
            print(json.dumps(report.to_dict(), indent=2))
        else:
            print(report.to_text())
        return 0

    elif brief_cmd == "latest":
        latest = briefing.get_latest()
        if latest is None:
            print("[X] No briefings found.", file=sys.stderr)
            return 1
        if args.format == "json":
            print(json.dumps(latest.to_dict(), indent=2))
        else:
            print(latest.to_text())
        return 0

    else:
        print("Usage: python -m cohort briefing [generate|latest]", file=sys.stderr)
        return 1


def _cmd_say(args: argparse.Namespace) -> int:
    """Append a message to the conversation."""
    from cohort.chat import ChatManager
    from cohort.file_transport import JsonlFileStorage

    storage = JsonlFileStorage(args.file)
    chat = ChatManager(storage)

    # Auto-create channel if needed
    if chat.get_channel(args.channel) is None:
        chat.create_channel(name=args.channel, description=args.channel)

    msg = chat.post_message(
        channel_id=args.channel,
        sender=args.sender,
        content=args.message,
    )
    print(f"[OK] {msg.id} -> #{args.channel}")
    return 0


def _resolve_data(args: argparse.Namespace):
    """Return (ChatManager, AgentStore) from --data-dir."""
    import os
    from pathlib import Path

    from cohort.agent_store import AgentStore
    from cohort.chat import ChatManager
    from cohort.registry import create_storage

    data_dir = Path(os.environ.get("COHORT_DATA_DIR", getattr(args, "data_dir", "data")))
    storage = create_storage(data_dir)
    chat = ChatManager(storage)

    agents_dir_env = os.environ.get("COHORT_AGENTS_DIR")
    if agents_dir_env:
        agents_dir = Path(agents_dir_env)
    elif (data_dir / "agents").is_dir():
        agents_dir = data_dir / "agents"
    else:
        # Default: shipped agents inside the package
        agents_dir = Path(__file__).parent / "agents"

    store = AgentStore(
        agents_dir=agents_dir if agents_dir.is_dir() else None,
        remote_url=os.environ.get("COHORT_AGENT_STORE_URL", ""),
        api_key=os.environ.get("COHORT_AGENT_STORE_KEY", ""),
    )
    return chat, store, agents_dir


def _cmd_discuss(args: argparse.Namespace) -> int:
    """Run a scored multi-agent discussion from the CLI."""
    from cohort.agent_context import build_channel_context
    from cohort.meeting import (
        calculate_composite_relevance,
        extract_keywords,
        initialize_meeting_context,
    )

    chat, store, _agents_dir = _resolve_data(args)

    # Resolve agents
    agent_ids = [a.strip() for a in args.agents.split(",")]
    agent_configs = {}
    for aid in agent_ids:
        cfg = store.get(aid)
        if cfg is None:
            print(f"[X] Agent '{aid}' not found.", file=sys.stderr)
            available = [a.agent_id for a in store.list_agents()]
            if available:
                print(f"    Available: {', '.join(available)}", file=sys.stderr)
            return 2
        agent_configs[aid] = cfg

    # Ensure channel exists and post the prompt
    channel_id = args.channel
    if chat.get_channel(channel_id) is None:
        chat.create_channel(name=channel_id, description=channel_id)

    chat.post_message(channel_id=channel_id, sender="user", content=args.prompt)

    # Build channel context (truncated, optionally distilled)
    channel_history = build_channel_context(chat, channel_id, distill=False)

    # Build meeting context and score agents
    meeting_ctx = initialize_meeting_context(agent_ids)
    messages = chat.get_channel_messages(channel_id, limit=15)
    recent_text = " ".join(m.content for m in messages[-5:])
    topic_kw = extract_keywords(recent_text)
    meeting_ctx["current_topic"]["keywords"] = topic_kw

    scores = []
    for aid in agent_ids:
        relevance = calculate_composite_relevance(
            agent_id=aid,
            meeting_context=meeting_ctx,
            agent_config=(
                agent_configs[aid].to_dict()
                if hasattr(agent_configs[aid], "to_dict")
                else agent_configs[aid]
            ),
            recent_messages=messages,
        )
        scores.append({
            "agent_id": aid,
            "score": relevance["composite_total"],
            "phase": relevance.get("detected_phase", "unknown"),
            "breakdown": {
                k: round(v, 3) for k, v in relevance.items()
                if k not in ("composite_total", "detected_phase")
            },
        })
    scores.sort(key=lambda x: x["score"], reverse=True)

    if args.format == "json":
        print(json.dumps({
            "channel": channel_id,
            "topic": args.prompt,
            "speaker_order": scores,
            "channel_history": channel_history,
        }, indent=2))
    else:
        print(f"\n  Discussion in #{channel_id}")
        print(f"  Topic: {args.prompt}")
        print()
        print("  Scored Speaker Order:")
        for i, entry in enumerate(scores, 1):
            bd = entry["breakdown"]
            top_dims = sorted(bd.items(), key=lambda x: x[1], reverse=True)[:2]
            dims_str = ", ".join(f"{k}={v:.2f}" for k, v in top_dims)
            marker = " <-- NEXT" if i == 1 else ""
            print(
                f"    {i}. {entry['agent_id']:20s}  "
                f"score={entry['score']:.2f}  "
                f"phase={entry['phase']}  ({dims_str}){marker}"
            )

        if channel_history:
            print()
            print(channel_history)

        print()
        print(
            "  Adopt each agent's persona in scored order to run the discussion.\n"
            "  Use 'cohort say' to post each agent's response to the channel."
        )

    return 0


def _cmd_roundtable(args: argparse.Namespace) -> int:
    """Run a compiled multi-agent discussion via local Ollama."""
    from cohort.agent_context import build_channel_context
    from cohort.compiled_roundtable import run_compiled_roundtable

    chat, store, _agents_dir = _resolve_data(args)

    # Resolve agents
    agent_ids = [a.strip() for a in args.agents.split(",")]
    for aid in agent_ids:
        cfg = store.get(aid)
        if cfg is None:
            print(f"[X] Agent '{aid}' not found.", file=sys.stderr)
            available = [a.agent_id for a in store.list_agents()]
            if available:
                print(f"    Available: {', '.join(available)}", file=sys.stderr)
            return 2

    # Build context from channel history if provided
    context = args.context or ""
    if args.channel:
        channel_id = args.channel
        if chat.get_channel(channel_id) is None:
            chat.create_channel(name=channel_id, description=channel_id)

        channel_ctx = build_channel_context(chat, channel_id, distill=False)
        if channel_ctx:
            context = channel_ctx + "\n\n" + context if context.strip() else channel_ctx

    # Initialize the global agent store so compiled_roundtable can load personas
    from cohort.agent_store import set_global_store
    set_global_store(store)

    print(f"  Starting roundtable: {', '.join(agent_ids)}")
    print(f"  Topic: {args.topic}")
    if args.channel:
        print(f"  Channel context: #{args.channel}")
    print(f"  Rounds: {args.rounds}, Model: {args.model or 'auto'}")
    print()

    result = run_compiled_roundtable(
        agents=agent_ids,
        topic=args.topic,
        context=context,
        rounds=args.rounds,
        model=args.model,
        temperature=args.temperature,
    )

    if result.error:
        print(f"[X] Roundtable failed: {result.error}", file=sys.stderr)
        return 1

    # Display results
    if args.format == "json":
        print(json.dumps({
            "agent_responses": result.agent_responses,
            "synthesis": result.synthesis,
            "metadata": result.metadata,
        }, indent=2))
    else:
        model_used = result.metadata.get("model", "unknown")
        latency = result.metadata.get("latency_ms", 0)
        parsed = len(result.agent_responses)
        print(f"  Roundtable complete: {parsed}/{len(agent_ids)} agents, "
              f"{model_used}, {latency}ms")
        print()

        for agent_id, response in result.agent_responses.items():
            print(f"  > {agent_id}:")
            for line in response.split("\n"):
                print(f"    {line}")
            print()

        if result.synthesis:
            print("  > synthesis:")
            for line in result.synthesis.split("\n"):
                print(f"    {line}")
            print()

    # Post results to channel if specified
    if args.channel and result.agent_responses:
        for agent_id, response in result.agent_responses.items():
            chat.post_message(channel_id=args.channel, sender=agent_id, content=response)
        if result.synthesis:
            chat.post_message(
                channel_id=args.channel, sender="system",
                content=f"**Discussion Synthesis:**\n{result.synthesis}",
            )
        print(f"  Results posted to #{args.channel}")

    return 0


def _cmd_create_agent(args: argparse.Namespace) -> int:
    """Register a new agent in the local agent store."""
    from cohort.agent import AgentConfig
    from cohort.agent_store import AgentStore

    _chat, store, agents_dir = _resolve_data(args)

    # Create agents dir if needed, then re-init store with it
    agents_dir.mkdir(parents=True, exist_ok=True)
    store = AgentStore(agents_dir=agents_dir)

    # Check if agent already exists
    existing = store.get(args.agent_id)
    if existing and not args.force:
        print(
            f"[X] Agent '{args.agent_id}' already exists."
            " Use --force to overwrite.", file=sys.stderr
        )
        return 1

    triggers = [t.strip() for t in args.triggers.split(",")] if args.triggers else []
    capabilities = [c.strip() for c in args.capabilities.split(",")] if args.capabilities else []
    domain_expertise = [d.strip() for d in args.domain.split(",")] if args.domain else []

    config = AgentConfig(
        agent_id=args.agent_id,
        name=args.name or args.agent_id.replace("_", " ").title(),
        role=args.role or "specialist",
        triggers=triggers,
        capabilities=capabilities,
        domain_expertise=domain_expertise,
    )

    store.register(config)
    print(f"[OK] Agent '{args.agent_id}' registered")
    print(f"     Name: {config.name}")
    print(f"     Role: {config.role}")
    if triggers:
        print(f"     Triggers: {', '.join(triggers)}")
    if capabilities:
        print(f"     Capabilities: {', '.join(capabilities)}")
    if domain_expertise:
        print(f"     Domain: {', '.join(domain_expertise)}")
    print(f"     Stored: {agents_dir / args.agent_id / 'agent_config.json'}")
    return 0


def _cmd_list_agents(args: argparse.Namespace) -> int:
    """List all registered agents."""
    _chat, store, _agents_dir = _resolve_data(args)

    agents = store.list_agents()
    if not agents:
        print("No agents registered.")
        print("  Use 'cohort create-agent' to add one.")
        return 0

    if args.format == "json":
        out = []
        for a in agents:
            out.append({
                "agent_id": a.agent_id,
                "name": a.name,
                "role": a.role,
                "triggers": a.triggers,
                "capabilities": a.capabilities,
            })
        print(json.dumps(out, indent=2))
    else:
        print(f"\n  Agents ({len(agents)} registered)\n")
        for a in agents:
            triggers = ", ".join(a.triggers[:5]) if a.triggers else "(none)"
            print(f"  {a.agent_id:24s}  {a.name:20s}  triggers: {triggers}")
        print()

    return 0


def _cmd_read(args: argparse.Namespace) -> int:
    """Read recent messages from a channel."""
    chat, _store, _agents_dir = _resolve_data(args)

    channel = chat.get_channel(args.channel)
    if channel is None:
        print(f"[X] Channel '{args.channel}' not found.", file=sys.stderr)
        available = [ch.name for ch in chat.list_channels()]
        if available:
            print(f"    Available: {', '.join(available)}", file=sys.stderr)
        return 1

    messages = chat.get_channel_messages(args.channel, limit=args.limit)
    if not messages:
        print(f"No messages in #{args.channel}.")
        return 0

    if args.format == "json":
        out = []
        for m in messages:
            out.append({
                "sender": m.sender,
                "content": m.content,
                "timestamp": m.timestamp,
            })
        print(json.dumps(out, indent=2))
    else:
        print(f"\n  #{args.channel} ({len(messages)} messages)\n")
        for m in messages:
            ts = m.timestamp or ""
            if "T" in ts:
                ts = ts.split("T")[1][:8]
            print(f"  [{ts}] {m.sender}: {m.content}")
        print()

    return 0


def _cmd_search(args: argparse.Namespace) -> int:
    """Search messages across all channels."""
    chat, _store, _agents_dir = _resolve_data(args)

    results = chat.search_messages(args.query)[:args.limit]
    if not results:
        print(f"No messages matching '{args.query}'.")
        return 0

    if args.format == "json":
        out = []
        for m in results:
            out.append({
                "channel": getattr(m, "channel_id", ""),
                "sender": m.sender,
                "content": m.content,
                "timestamp": m.timestamp,
            })
        print(json.dumps(out, indent=2))
    else:
        print(f"\n  Search: '{args.query}' ({len(results)} results)\n")
        for m in results:
            ts = m.timestamp or ""
            if "T" in ts:
                ts = ts.split("T")[1][:8]
            ch = getattr(m, "channel_id", "?")
            print(f"  [{ts}] #{ch} {m.sender}: {m.content}")
        print()

    return 0


def _cmd_mcp(args: argparse.Namespace) -> int:
    """Start the Cohort MCP server for LLM integration."""
    try:
        from cohort.mcp.server import mcp
    except ImportError as e:
        print(
            f"[X] MCP dependencies not installed: {e}\n"
            "    Install with: pip install cohort[mcp]     (basic tools)\n"
            "              or: pip install cohort[claude]   (all tools)",
            file=sys.stderr,
        )
        return 1

    mcp.run(transport=args.transport)
    return 0


def _cmd_mcp_local(args: argparse.Namespace) -> int:
    """Start the local LLM MCP server (Ollama integration)."""
    try:
        from cohort.mcp.local_llm_server import mcp
    except ImportError as e:
        print(
            f"[X] MCP dependencies not installed: {e}\n"
            "    Install with: pip install cohort[mcp]     (basic tools)\n"
            "              or: pip install cohort[claude]   (all tools)",
            file=sys.stderr,
        )
        return 1

    mcp.run(transport=args.transport)
    return 0


def _cmd_benchmark(args: argparse.Namespace) -> int:
    """Run the multi-agent orchestration benchmark."""
    import logging
    from pathlib import Path

    from cohort.benchmark.llm_backend import LLMBackend, OllamaBackend
    from cohort.benchmark.runner import BenchmarkRunner
    from cohort.benchmark.task_loader import load_all_tasks

    logging.basicConfig(
        level=logging.INFO,
        format="  %(message)s",
    )

    tasks_dir = Path(args.tasks_dir)
    if not tasks_dir.is_dir():
        print(f"[X] Tasks directory not found: {tasks_dir}", file=sys.stderr)
        return 1

    # Load tasks (optionally filtered)
    all_tasks = load_all_tasks(tasks_dir)
    if not all_tasks:
        print(f"[X] No task files found in {tasks_dir}", file=sys.stderr)
        return 1

    if args.tasks:
        task_ids = {t.strip() for t in args.tasks.split(",")}
        all_tasks = [t for t in all_tasks if t.task_id in task_ids]
        if not all_tasks:
            print(f"[X] No matching tasks for: {args.tasks}", file=sys.stderr)
            return 1

    # Select strategies
    strategies = None
    if args.strategies:
        from cohort.benchmark.strategies import ALL_STRATEGIES

        strategy_names = {s.strip() for s in args.strategies.split(",")}
        strategies = [s for s in ALL_STRATEGIES if s.NAME in strategy_names]
        if not strategies:
            print(f"[X] No matching strategies for: {args.strategies}", file=sys.stderr)
            return 1

    # Initialize LLM backend
    backend = getattr(args, "backend", "ollama")
    workers = getattr(args, "workers", 1)

    try:
        if backend == "llamacpp":
            from cohort.benchmark.llm_backend import LlamaCppBackend

            urls = [u.strip() for u in args.urls.split(",")] if args.urls else None
            llm: LLMBackend = LlamaCppBackend(urls=urls, model=args.model or "llamacpp")
            if not workers:
                workers = len(llm._urls)  # type: ignore[attr-defined]
        else:
            llm = OllamaBackend(model=args.model)
            if not workers:
                workers = 1
    except RuntimeError as e:
        print(f"[X] {e}", file=sys.stderr)
        return 1

    print("\n  Cohort Orchestration Benchmark")
    print(f"  Backend: {backend}")
    print(f"  Model: {llm.model_name}")
    print(f"  Tasks: {len(all_tasks)}")
    print(f"  Workers: {workers}")
    strategy_names_str = ", ".join(s.NAME for s in (strategies or []))
    if strategies:
        print(f"  Strategies: {strategy_names_str}")
    else:
        print("  Strategies: all (single_agent, round_robin, hierarchical, cohort_scored)")
    print()

    # Optional separate distill backend
    distill_llm = None
    distill_url = getattr(args, "distill_url", None)
    if distill_url and backend == "llamacpp":
        from cohort.benchmark.llm_backend import LlamaCppBackend
        distill_llm = LlamaCppBackend(
            urls=[distill_url], model=args.model or "llamacpp"
        )
        print(f"  Distill server: {distill_url}")

    runner = BenchmarkRunner(
        llm=llm,
        tasks=all_tasks,
        output_dir=Path(args.output_dir),
        strategies=strategies,
        distill_llm=distill_llm,
    )
    report = runner.run_all(max_workers=workers)

    if args.format == "json":
        print(json.dumps(report.to_dict(), indent=2))
    else:
        print(report.to_summary_table())

    return 0


# =====================================================================
# Main entry point
# =====================================================================

def main() -> None:
    parser = argparse.ArgumentParser(description="cohort -- multi-agent orchestration")
    sub = parser.add_subparsers(dest="command")

    # -- gate -----------------------------------------------------------
    gate_parser = sub.add_parser("gate", help="Check if an agent should respond")
    gate_parser.add_argument("--agent", required=True, help="Agent ID to check")
    gate_parser.add_argument("--channel", required=True, help="Channel ID")
    gate_parser.add_argument("--file", required=True, help="Path to conversation .jsonl file")
    gate_parser.add_argument("--agents", required=True, help="Path to agents.json config")
    gate_parser.add_argument(
        "--format", choices=["json", "text"], default="text", help="Output format"
    )

    # -- next-speaker ---------------------------------------------------
    next_parser = sub.add_parser("next-speaker", help="Recommend who should talk next")
    next_parser.add_argument("--channel", required=True, help="Channel ID")
    next_parser.add_argument("--file", required=True, help="Path to conversation .jsonl file")
    next_parser.add_argument("--agents", required=True, help="Path to agents.json config")
    next_parser.add_argument("--top", type=int, default=3, help="Number of speakers to show")
    next_parser.add_argument(
        "--format", choices=["json", "text"], default="text", help="Output format"
    )

    # -- say ------------------------------------------------------------
    say_parser = sub.add_parser("say", help="Append a message to the conversation")
    say_parser.add_argument("--sender", required=True, help="Sender agent ID")
    say_parser.add_argument("--channel", required=True, help="Channel ID")
    say_parser.add_argument("--file", required=True, help="Path to conversation .jsonl file")
    say_parser.add_argument("--message", required=True, help="Message content")

    # -- briefing -------------------------------------------------------
    brief_parser = sub.add_parser("briefing", help="Generate or view executive briefing")
    brief_sub = brief_parser.add_subparsers(dest="brief_command")

    gen_parser = brief_sub.add_parser("generate", help="Generate a new briefing")
    gen_parser.add_argument("--hours", type=int, default=24, help="Hours to cover")
    gen_parser.add_argument("--data-dir", default="data", help="Data directory")
    gen_parser.add_argument("--no-post", action="store_true", help="Don't post to channel")
    gen_parser.add_argument(
        "--format", choices=["json", "text"], default="text", help="Output format"
    )

    latest_parser = brief_sub.add_parser("latest", help="Show latest briefing")
    latest_parser.add_argument("--data-dir", default="data", help="Data directory")
    latest_parser.add_argument(
        "--format", choices=["json", "text"], default="text", help="Output format"
    )

    # -- discuss ----------------------------------------------------
    discuss_parser = sub.add_parser(
        "discuss",
        help="Scored multi-agent discussion (who should speak, in what order)",
    )
    discuss_parser.add_argument(
        "--agents", required=True,
        help="Comma-separated agent IDs (e.g. backend_dev,qa_engineer)",
    )
    discuss_parser.add_argument("--channel", required=True, help="Channel ID")
    discuss_parser.add_argument("--prompt", required=True, help="Discussion topic/prompt")
    discuss_parser.add_argument("--data-dir", default="data", help="Data directory")
    discuss_parser.add_argument(
        "--format", choices=["json", "text"], default="text", help="Output format",
    )

    # -- roundtable -------------------------------------------------
    rt_parser = sub.add_parser(
        "roundtable",
        help="Compiled multi-agent discussion via local Ollama (single LLM call)",
    )
    rt_parser.add_argument(
        "--agents", required=True,
        help="Comma-separated agent IDs (e.g. backend_dev,qa_engineer)",
    )
    rt_parser.add_argument("--topic", required=True, help="Discussion topic")
    rt_parser.add_argument(
        "--channel", default=None, help="Channel for context and posting results"
    )
    rt_parser.add_argument("--context", default="", help="Additional context text")
    rt_parser.add_argument("--data-dir", default="data", help="Data directory")
    rt_parser.add_argument(
        "--rounds", type=int, default=2, help="Discussion rounds 1-3 (default: 2)"
    )
    rt_parser.add_argument("--model", default=None, help="Ollama model (default: auto-select)")
    rt_parser.add_argument(
        "--temperature", type=float, default=0.30, help="Generation temperature (default: 0.30)",
    )
    rt_parser.add_argument(
        "--format", choices=["json", "text"], default="text", help="Output format",
    )

    # -- create-agent -----------------------------------------------
    ca_parser = sub.add_parser(
        "create-agent",
        help="Register a new agent in the local store",
    )
    ca_parser.add_argument("agent_id", help="Agent ID (e.g. backend_dev)")
    ca_parser.add_argument("--name", default=None, help="Display name (default: derived from ID)")
    ca_parser.add_argument("--role", default="specialist", help="Role (default: specialist)")
    ca_parser.add_argument(
        "--triggers", default="",
        help="Comma-separated trigger keywords (e.g. api,database,python)",
    )
    ca_parser.add_argument(
        "--capabilities", default="",
        help="Comma-separated capabilities (e.g. 'REST API design,database queries')",
    )
    ca_parser.add_argument(
        "--domain", default="",
        help="Comma-separated domain expertise (e.g. python,fastapi,postgresql)",
    )
    ca_parser.add_argument("--data-dir", default="data", help="Data directory")
    ca_parser.add_argument(
        "--force", action="store_true", help="Overwrite if agent already exists",
    )

    # -- list-agents ------------------------------------------------
    la_parser = sub.add_parser(
        "list-agents",
        help="List all registered agents",
    )
    la_parser.add_argument("--data-dir", default="data", help="Data directory")
    la_parser.add_argument(
        "--format", choices=["json", "text"], default="text", help="Output format",
    )

    # -- read -------------------------------------------------------
    read_parser = sub.add_parser(
        "read",
        help="Read recent messages from a channel",
    )
    read_parser.add_argument("channel", help="Channel ID to read")
    read_parser.add_argument(
        "--limit", type=int, default=20, help="Number of messages (default: 20)",
    )
    read_parser.add_argument("--data-dir", default="data", help="Data directory")
    read_parser.add_argument(
        "--format", choices=["json", "text"], default="text", help="Output format",
    )

    # -- search -----------------------------------------------------
    search_parser = sub.add_parser(
        "search",
        help="Search messages across all channels",
    )
    search_parser.add_argument("query", help="Search query")
    search_parser.add_argument(
        "--limit", type=int, default=20, help="Max results (default: 20)",
    )
    search_parser.add_argument("--data-dir", default="data", help="Data directory")
    search_parser.add_argument(
        "--format", choices=["json", "text"], default="text", help="Output format",
    )

    # -- setup ------------------------------------------------------
    sub.add_parser(
        "setup",
        help="Interactive setup wizard (Ollama + model + content feeds)",
    )

    # -- tutorial ---------------------------------------------------
    sub.add_parser(
        "tutorial",
        help="Interactive walkthrough of Cohort's scoring engine",
    )

    # -- mcp --------------------------------------------------------
    mcp_parser = sub.add_parser(
        "mcp",
        help="Start the Cohort MCP server (LLM integration)",
    )
    mcp_parser.add_argument(
        "--transport", choices=["stdio", "sse"], default="stdio",
        help="MCP transport (default: stdio)",
    )

    # -- mcp-local --------------------------------------------------
    mcp_local_parser = sub.add_parser(
        "mcp-local",
        help="Start the local LLM MCP server (Ollama integration)",
    )
    mcp_local_parser.add_argument(
        "--transport", choices=["stdio", "sse"], default="stdio",
        help="MCP transport (default: stdio)",
    )

    # -- benchmark --------------------------------------------------
    bench_parser = sub.add_parser(
        "benchmark",
        help="Run multi-agent orchestration benchmark (4 strategies × N tasks)",
    )
    bench_parser.add_argument(
        "--tasks-dir", default="benchmark_tasks/tasks",
        help="Directory containing task JSON files",
    )
    bench_parser.add_argument(
        "--output-dir", default="benchmark_results",
        help="Directory for result JSON files",
    )
    bench_parser.add_argument(
        "--backend", choices=["ollama", "llamacpp"], default="ollama",
        help="LLM backend (default: ollama)",
    )
    bench_parser.add_argument(
        "--urls", default=None,
        help="Comma-separated server URLs for llamacpp backend (default: :11435,:11436)",
    )
    bench_parser.add_argument(
        "--distill-url", default=None,
        help="Separate server URL for distillation (e.g. http://127.0.0.1:11435)",
    )
    bench_parser.add_argument(
        "--workers", type=int, default=None,
        help="Parallel workers (default: number of server URLs)",
    )
    bench_parser.add_argument(
        "--model", default=None,
        help="Model name (default: auto-select)",
    )
    bench_parser.add_argument(
        "--strategies", default=None,
        help="Comma-separated strategy names to run (default: all four)",
    )
    bench_parser.add_argument(
        "--tasks", default=None,
        help="Comma-separated task IDs to run (default: all in tasks-dir)",
    )
    bench_parser.add_argument(
        "--format", choices=["json", "text"], default="text",
        help="Output format",
    )

    # -- reset ------------------------------------------------------
    reset_parser = sub.add_parser(
        "reset",
        help="Factory reset -- wipe runtime data back to clean defaults",
    )
    reset_parser.add_argument(
        "--yes", "-y", action="store_true",
        help="Skip confirmation prompt",
    )
    reset_parser.add_argument(
        "--dry-run", action="store_true",
        help="Show what would be reset without changing anything",
    )

    args = parser.parse_args()

    if args.command == "setup":
        from cohort.banner import print_banner
        from cohort.local.setup import run_setup

        print_banner()
        sys.exit(run_setup())
    elif args.command == "tutorial":
        from cohort.banner import print_banner
        from cohort.local.tutorial import run_tutorial

        print_banner()
        sys.exit(run_tutorial())
    elif args.command == "gate":
        sys.exit(_cmd_gate(args))
    elif args.command == "next-speaker":
        sys.exit(_cmd_next_speaker(args))
    elif args.command == "say":
        sys.exit(_cmd_say(args))
    elif args.command == "briefing":
        sys.exit(_cmd_briefing(args))
    elif args.command == "discuss":
        sys.exit(_cmd_discuss(args))
    elif args.command == "roundtable":
        sys.exit(_cmd_roundtable(args))
    elif args.command == "create-agent":
        sys.exit(_cmd_create_agent(args))
    elif args.command == "list-agents":
        sys.exit(_cmd_list_agents(args))
    elif args.command == "read":
        sys.exit(_cmd_read(args))
    elif args.command == "search":
        sys.exit(_cmd_search(args))
    elif args.command == "mcp":
        sys.exit(_cmd_mcp(args))
    elif args.command == "mcp-local":
        sys.exit(_cmd_mcp_local(args))
    elif args.command == "benchmark":
        sys.exit(_cmd_benchmark(args))
    elif args.command == "reset":
        from cohort.reset import run_reset

        sys.exit(run_reset(yes=args.yes, dry_run=args.dry_run))
    else:
        from cohort.banner import print_banner, print_help

        print_banner()
        print_help()
        sys.exit(1)


if __name__ == "__main__":
    main()
