"""
KIESSCLAW CLI Entrypoint.

Provides both:
- Generator commands (usecases, prompt packs, scaffolds, diagnostics)
- Operational SDR/SEO runtime commands.
"""

from __future__ import annotations

import argparse
import importlib.util
import json
import logging
import os
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Any

from dotenv import load_dotenv

from kiessclaw.runtime import build_agent_registry, load_config, workspace_memory

# Load .env
load_dotenv()

# Configure logging
logging.basicConfig(
    level=os.getenv("LOG_LEVEL", "INFO"),
    format="%(asctime)s [%(name)s] %(levelname)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("kiessclaw")


def _load_json_inputs(path: str | None) -> dict[str, Any]:
    """Load JSON object from a file path."""
    if not path:
        return {}
    payload = json.loads(Path(path).read_text(encoding="utf-8"))
    if not isinstance(payload, dict):
        raise ValueError("JSON input must be an object")
    return payload


def _print_json(payload: dict[str, Any]) -> None:
    """Print deterministic JSON for CLI copy/paste output."""
    print(json.dumps(payload, indent=2, sort_keys=True))


def cmd_status(_: argparse.Namespace, config: dict[str, Any], registry: dict[str, Any]) -> None:
    """Print agent status and workspace summary."""
    print("\n🦅 KIESSCLAW Agent Status")
    print("=" * 50)

    for codename, agent in registry.items():
        state = agent.report_status()
        status_icon = "🟢" if state["status"] == "idle" else "🔄"
        print(f"  {status_icon} {codename:6} | {state['name']:20} | {state['model'] or 'default':15}")

    memory = workspace_memory(config)
    contacts = memory.load_data("contacts")
    sequences = memory.load_data("sequences")
    enrollments = memory.load_data("enrollments")
    messages = memory.load_data("messages")
    replies = memory.load_data("replies")
    seo_audits = memory.load_data("seo_audits")

    print("\n📊 Data Summary")
    print("-" * 50)
    print(f"  Contacts:    {len(contacts)}")
    print(f"  Sequences:   {len(sequences)}")
    print(f"  Enrollments: {len(enrollments)}")
    print(f"  Messages:    {len(messages)}")
    print(f"  Replies:     {len(replies)}")
    print(f"  SEO Audits:  {len(seo_audits)}")
    print()


def cmd_import(args: argparse.Namespace, _: dict[str, Any], registry: dict[str, Any]) -> None:
    """Import contacts from CSV via KPRO."""
    kpro = registry.get("KPRO")
    if not kpro:
        print("❌ KPRO agent not available")
        sys.exit(1)

    print(f"📥 Importing from {args.file}...")
    print(kpro.run("import", file_path=args.file))


def cmd_audit(args: argparse.Namespace, config: dict[str, Any], registry: dict[str, Any]) -> None:
    """Run SEO audit and optionally trigger autonomous lead generation."""
    if args.auto_enroll:
        from kiessclaw.pipeline import LeadGenPipeline

        pipeline = LeadGenPipeline(config=config, registry=registry)
        result = pipeline.run_audit(url=args.url, auto_enroll=True, max_pages=args.max_pages)
        audit = result["audit"]
        lead = result["pipeline"]

        print("## Audit")
        print(f"- Domain: {audit['domain']}")
        print(f"- Technical SEO score: {audit['technical_score']}")
        print(f"- Pages crawled: {audit['pages_crawled']}")
        print(f"- Owner contact: {audit.get('owner_contact') or 'not found'}")

        print("\n## Lead Pipeline")
        print(f"- Qualified: {lead['qualified']}")
        print(f"- ICP score: {lead.get('icp_score')}")
        print(f"- Enrolled: {lead['enrolled']}")
        print(f"- Sequence ID: {lead.get('sequence_id')}")
        return

    krawl = registry.get("KRAWL")
    if not krawl:
        print("❌ KRAWL agent not available")
        sys.exit(1)
    print(krawl.run("audit", url=args.url, max_pages=args.max_pages))


def cmd_usecase(args: argparse.Namespace, _: dict[str, Any], __: dict[str, Any]) -> None:
    """Use case registry inspection commands."""
    from kiessclaw.usecases.registry import get_usecase, list_usecases, usecase_to_dict

    if args.action == "list":
        for spec in list_usecases():
            print(f"- {spec.id}: {spec.title}")
        return

    spec = get_usecase(args.usecase_id)
    _print_json(usecase_to_dict(spec))


def cmd_prompt(args: argparse.Namespace, _: dict[str, Any], __: dict[str, Any]) -> None:
    """Prompt rendering and prompt pack generation."""
    from kiessclaw.prompting.prompt_factory import render_prompt, render_prompt_pack

    inputs = _load_json_inputs(args.json_input)
    if args.action == "render":
        rendered = render_prompt(
            usecase_id=args.usecase_id,
            template_name=args.template,
            inputs=inputs,
            style=args.style,
            deterministic=args.deterministic,
        )
        print(rendered)
        return

    pack = render_prompt_pack(
        usecase_id=args.usecase_id,
        inputs=inputs,
        style=args.style,
        deterministic=args.deterministic,
    )
    if args.out:
        output_path = Path(args.out)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_text(json.dumps(pack, indent=2, sort_keys=True), encoding="utf-8")
        print(f"✅ Wrote prompt pack: {output_path}")
        return
    _print_json(pack)


def cmd_scaffold(args: argparse.Namespace, _: dict[str, Any], __: dict[str, Any]) -> None:
    """Scaffold use cases and building blocks to disk."""
    from kiessclaw.codegen.scaffold import (
        scaffold_agent,
        scaffold_skill,
        scaffold_tests,
        scaffold_usecase,
        scaffold_workflow,
    )

    outdir = args.outdir
    force = bool(args.force)
    if args.action == "usecase":
        result = scaffold_usecase(
            usecase_id=args.usecase_id,
            target_dir=outdir,
            inputs=_load_json_inputs(args.json_input),
            overwrite=force,
        )
    elif args.action == "agent":
        result = scaffold_agent(name=args.name, target_dir=outdir, overwrite=force)
    elif args.action == "workflow":
        result = scaffold_workflow(name=args.name, target_dir=outdir, overwrite=force)
    elif args.action == "skill":
        result = scaffold_skill(name=args.name, target_dir=outdir, overwrite=force)
    else:
        result = scaffold_tests(kind=args.kind, target_dir=outdir, overwrite=force)

    for item in result:
        status = item.get("status", "unknown")
        path = item.get("path", "")
        reason = f" ({item['reason']})" if "reason" in item else ""
        print(f"- {status}: {path}{reason}")


def cmd_doctor(args: argparse.Namespace, _: dict[str, Any], __: dict[str, Any]) -> None:
    """Run CLI health diagnostics and print quick fixes."""
    checks: list[str] = []
    fixes: list[str] = []

    config_path = Path(args.config)
    checks.append(f"config_file_exists={config_path.exists()}")
    if not config_path.exists():
        fixes.append(f"Create config file at {config_path}")

    try:
        config = load_config(args.config)
        checks.append("config_load=ok")
    except Exception as exc:  # noqa: BLE001
        config = {"workspace": {"root": "./workspace"}}
        checks.append(f"config_load=failed ({exc})")
        fixes.append("Set required env vars or switch provider in config.yaml")

    workspace_root = Path(config.get("workspace", {}).get("root", "./workspace"))
    checks.append(f"workspace_exists={workspace_root.exists()}")
    if not workspace_root.exists():
        fixes.append("Run: python scripts/init_workspace.py")

    required_files = [
        "accounts.json",
        "contacts.json",
        "sequences.json",
        "enrollments.json",
        "messages.json",
        "replies.json",
        "analytics.json",
    ]
    missing_files = [name for name in required_files if not (workspace_root / "data" / name).exists()]
    checks.append(f"workspace_data_missing={len(missing_files)}")
    if missing_files:
        fixes.append("Run workspace init to create missing data files")

    deps = ["yaml", "dotenv", "requests", "fastapi", "uvicorn", "pydantic"]
    missing_deps = [dep for dep in deps if importlib.util.find_spec(dep) is None]
    checks.append(f"missing_deps={len(missing_deps)}")
    if missing_deps:
        fixes.append(f"Install deps: pip install {' '.join(sorted(missing_deps))}")

    print("## Doctor")
    for item in checks:
        print(f"- {item}")
    if fixes:
        print("\n## Quick Fixes")
        for fix in fixes:
            print(f"- {fix}")
    else:
        print("\nAll checks passed.")


def cmd_providers(args: argparse.Namespace, config: dict[str, Any], _: dict[str, Any]) -> None:
    """Inspect provider plugin registration and health."""
    from kiessclaw.providers import active_provider, list_providers

    providers_cfg = config.get("providers", {})
    if args.action == "list":
        print("## Providers")
        for name in list_providers():
            print(f"- {name}")
        print("\n## Active by Kind")
        for kind in ("email_finder", "enrichment", "crm", "inbox"):
            provider = active_provider(kind=kind, config=config)
            configured = providers_cfg.get(kind, "mock") if isinstance(providers_cfg, dict) else "mock"
            resolved = provider.name if provider else "none"
            print(f"- {kind}: configured={configured} resolved={resolved}")
        return

    print("## Provider Health")
    for kind in ("email_finder", "enrichment", "crm", "inbox"):
        provider = active_provider(kind=kind, config=config)
        if provider is None:
            print(f"- {kind}: none")
            continue
        print(f"- {kind}: {provider.name} healthy={provider.health_check()}")


def cmd_workflow(args: argparse.Namespace, config: dict[str, Any], registry: dict[str, Any]) -> None:
    """Run one use case workflow with dry-run and provider override support."""
    from kiessclaw.workflow.runner import WorkflowRunner

    inputs = _load_json_inputs(args.json_input)
    runner = WorkflowRunner(config=config, registry=registry)
    result = runner.run(
        usecase_id=args.usecase_id,
        inputs=inputs,
        dry_run=args.dry_run,
        auto_enroll=args.auto_enroll,
        provider_name=args.provider,
    )
    _print_json(result.to_dict())


def cmd_integrations(args: argparse.Namespace, config: dict[str, Any], _: dict[str, Any]) -> None:
    """Integration commands for optional external services."""
    if args.integration == "slack" and args.action == "test":
        from kiessclaw.integrations import SlackIntegration

        slack_cfg = config.get("slack", {}) if isinstance(config, dict) else {}
        slack = SlackIntegration(
            webhook_url=slack_cfg.get("webhook_url") if isinstance(slack_cfg, dict) else None,
            bot_token=slack_cfg.get("bot_token") if isinstance(slack_cfg, dict) else None,
        )
        ok = slack.notify(
            "workflow_complete",
            {
                "usecase_id": "integration_test",
                "contacts_created": 0,
                "enrolled": 0,
                "errors": [],
            },
        )
        if ok:
            print("✅ Slack test notification sent")
        else:
            print("⚠️ Slack notification not sent (webhook missing or request failed)")
        return

    print("❌ Unknown integrations command")
    sys.exit(1)


def cmd_schedule(args: argparse.Namespace, config: dict[str, Any], registry: dict[str, Any]) -> None:
    """Manage persisted scheduled workflow jobs."""
    from kiessclaw.scheduler import KiessScheduler

    scheduler = KiessScheduler(config=config, registry=registry)
    if args.action == "list":
        _print_json({"jobs": scheduler.list_jobs()})
        return

    if args.action == "add":
        try:
            job_id = scheduler.add_job(
                usecase_id=args.usecase_id,
                cron=args.cron,
                inputs=_load_json_inputs(args.json_input),
                dry_run=args.dry_run,
                job_id=args.job_id,
            )
        except (ValueError, KeyError) as exc:
            print(f"❌ Failed to add schedule: {exc}")
            sys.exit(1)
        print(f"✅ Added schedule job: {job_id}")
        return

    if args.action == "remove":
        removed = scheduler.remove_job(args.job_id)
        if not removed:
            print(f"❌ Unknown job_id: {args.job_id}")
            sys.exit(1)
        print(f"✅ Removed schedule job: {args.job_id}")
        return

    if args.action == "run":
        try:
            result = scheduler.run_now(args.job_id)
        except ValueError as exc:
            print(f"❌ {exc}")
            sys.exit(1)
        _print_json(result.to_dict())
        return

    print("❌ Unknown schedule action")
    sys.exit(1)


def cmd_contact(args: argparse.Namespace, config: dict[str, Any], _: dict[str, Any]) -> None:
    """Create/list contacts without manual JSON edits."""
    memory = workspace_memory(config)

    if args.action == "list":
        contacts = memory.load_data("contacts")
        if not contacts:
            print("No contacts found.")
            return
        print("**Contacts**\n")
        for contact in contacts:
            print(f"- {contact['id']} | {contact['email']} | {contact.get('status', 'new')}")
        return

    from kiessclaw.models.account import Account
    from kiessclaw.models.contact import Contact

    if not args.email:
        print("❌ --email is required for `contact add`")
        sys.exit(1)

    email = args.email.strip().lower()
    contacts = memory.load_data("contacts")
    if any(item.get("email") == email for item in contacts):
        print(f"❌ Contact already exists: {email}")
        sys.exit(1)

    domain = args.domain.strip().lower() if args.domain else ""
    if not domain and "@" in email:
        domain = email.split("@", 1)[1]

    accounts = memory.load_data("accounts")
    account_id = ""
    if domain or args.company:
        account = next((a for a in accounts if a.get("domain") == domain and domain), None)
        if not account:
            new_account = Account(
                domain=domain,
                name=args.company or domain,
                industry=args.industry,
                employee_count=args.employee_count,
            )
            account = new_account.to_dict()
            accounts.append(account)
            memory.save_data("accounts", accounts)
        account_id = account["id"]

    contact = Contact(
        account_id=account_id,
        email=email,
        first_name=args.first_name or "",
        last_name=args.last_name or "",
        title=args.title,
    )
    contacts.append(contact.to_dict())
    memory.save_data("contacts", contacts)

    print(f"✅ Added contact: {email}")
    print(f"ID: {contact.id}")


def cmd_sequence(args: argparse.Namespace, config: dict[str, Any], registry: dict[str, Any]) -> None:
    """Sequence management commands."""
    kseq = registry.get("KSEQ")
    if not kseq:
        print("❌ KSEQ agent not available")
        sys.exit(1)

    action = args.action or "list"

    if action == "list":
        print(kseq.run("status"))
        return

    if action == "create":
        if not args.name:
            print("❌ --name is required")
            sys.exit(1)
        print(kseq.run("create", name=args.name, sender_email=args.sender_email))
        return

    if action == "add-step":
        if not args.id:
            print("❌ --id is required")
            sys.exit(1)
        print(
            kseq.run(
                "add_step",
                sequence_id=args.id,
                channel=args.channel,
                delay_days=args.delay_days,
                subject=args.subject or "",
                body=args.body or "",
            )
        )
        return

    memory = workspace_memory(config)
    sequences = memory.load_data("sequences")
    sequence = next((s for s in sequences if s["id"] == args.id), None)
    if not sequence:
        print(f"❌ Sequence {args.id} not found")
        sys.exit(1)

    if action == "activate":
        sequence["status"] = "active"
        sequence["started_at"] = datetime.now().isoformat()
        memory.save_data("sequences", sequences)
        print(f"✅ Activated sequence: {sequence['name']}")
        return

    if action == "pause":
        print(kseq.run("pause", sequence_id=args.id))
        return

    print("❌ Unknown sequence action")
    sys.exit(1)


def cmd_enroll(args: argparse.Namespace, _: dict[str, Any], registry: dict[str, Any]) -> None:
    """Enroll a contact into a sequence."""
    kseq = registry.get("KSEQ")
    if not kseq:
        print("❌ KSEQ agent not available")
        sys.exit(1)

    result = kseq.run(
        "enroll",
        sequence_id=args.sequence_id,
        contact_ids=[args.contact_id],
        start_immediately=args.start_now,
    )
    print(result)


def cmd_send(args: argparse.Namespace, _: dict[str, Any], registry: dict[str, Any]) -> None:
    """Process and send due emails."""
    kseq = registry.get("KSEQ")
    if not kseq:
        print("❌ KSEQ agent not available")
        sys.exit(1)

    print(f"📤 Processing sends {'(DRY RUN)' if args.dry_run else ''}...")
    print(kseq.run("send", dry_run=args.dry_run, limit=args.limit))


def cmd_reply(args: argparse.Namespace, config: dict[str, Any], registry: dict[str, Any]) -> None:
    """Simulate an inbound reply for a contact."""
    memory = workspace_memory(config)
    contacts = memory.load_data("contacts")
    contact = next((c for c in contacts if c["id"] == args.contact_id), None)
    if not contact:
        print(f"❌ Contact {args.contact_id} not found")
        sys.exit(1)

    messages = memory.load_data("messages")
    matching_messages = [m for m in messages if m.get("contact_id") == args.contact_id]
    latest_message = matching_messages[-1] if matching_messages else None

    replies = memory.load_data("replies")
    reply_record = {
        "id": f"reply_{len(replies) + 1}_{int(datetime.now().timestamp())}",
        "sent_message_id": latest_message.get("id") if latest_message else None,
        "contact_id": args.contact_id,
        "raw_text": args.text,
        "subject": args.subject or "",
        "from_email": contact["email"],
        "intent": "unknown",
        "intent_confidence": 0.0,
        "objection_type": None,
        "sentiment": None,
        "meeting_requested": False,
        "suggested_time": None,
        "referral_name": None,
        "referral_email": None,
        "processed": False,
        "processed_at": None,
        "processed_by": None,
        "actions_taken": [],
        "next_action": None,
        "received_at": datetime.now().isoformat(),
        "created_at": datetime.now().isoformat(),
    }
    replies.append(reply_record)
    memory.save_data("replies", replies)
    print(f"✅ Reply captured: {reply_record['id']}")

    if not args.no_process:
        kinb = registry.get("KINB")
        if not kinb:
            print("❌ KINB agent not available")
            sys.exit(1)
        print("\n" + kinb.run("process", limit=1))


def cmd_inbox(args: argparse.Namespace, _: dict[str, Any], registry: dict[str, Any]) -> None:
    """Inbox management commands."""
    kinb = registry.get("KINB")
    if not kinb:
        print("❌ KINB agent not available")
        sys.exit(1)

    if args.action == "status":
        print(kinb.run("status"))
    elif args.action == "process":
        print(kinb.run("process", limit=args.limit))
    elif args.action == "dnc":
        print(kinb.run("dnc", action=args.dnc_action, email=args.email, reason=args.reason))
    else:
        print("❌ Unknown inbox action")
        sys.exit(1)


def cmd_report(_: argparse.Namespace, __: dict[str, Any], registry: dict[str, Any]) -> None:
    """Generate a full weekly report."""
    kmet = registry.get("KMET")
    if not kmet:
        print("❌ KMET agent not available")
        sys.exit(1)
    print(kmet.run("report", save=True))


def cmd_analytics(args: argparse.Namespace, _: dict[str, Any], registry: dict[str, Any]) -> None:
    """Show analytics summary and optional markdown report."""
    kmet = registry.get("KMET")
    if not kmet:
        print("❌ KMET agent not available")
        sys.exit(1)

    sections = [
        kmet.run("pipeline"),
        kmet.run("deliverability", days=args.days),
    ]
    if args.report:
        sections.append(kmet.run("report", save=args.save))

    print("\n\n".join(sections))


def cmd_analyze(_: argparse.Namespace, __: dict[str, Any], registry: dict[str, Any]) -> None:
    """Analyze performance with LLM insights."""
    kmet = registry.get("KMET")
    if not kmet:
        print("❌ KMET agent not available")
        sys.exit(1)
    print(kmet.run("analyze"))


def cmd_pipeline(_: argparse.Namespace, __: dict[str, Any], registry: dict[str, Any]) -> None:
    """Show prospect pipeline stats."""
    kmet = registry.get("KMET")
    if not kmet:
        print("❌ KMET agent not available")
        sys.exit(1)
    print(kmet.run("pipeline"))


def cmd_run(args: argparse.Namespace, _: dict[str, Any], registry: dict[str, Any]) -> None:
    """Execute a single operational cycle: send + inbox + analytics summary."""
    kseq = registry.get("KSEQ")
    kinb = registry.get("KINB")
    kmet = registry.get("KMET")
    if not kseq or not kinb or not kmet:
        print("❌ Required agents KSEQ/KINB/KMET not available")
        sys.exit(1)

    print("## Send")
    print(kseq.run("send", dry_run=args.dry_run, limit=args.limit))

    if args.dry_run:
        print("\n## Inbox")
        print("[DRY RUN] Skipped inbox mutation step.")
    elif not args.skip_inbox:
        print("\n## Inbox")
        print(kinb.run("process", limit=args.inbox_limit))

    print("\n## Analytics")
    print(kmet.run("pipeline"))


def cmd_chat(_: argparse.Namespace, __: dict[str, Any], registry: dict[str, Any]) -> None:
    """Interactive chat with agents."""
    print("\n🦅 KIESSCLAW Interactive Mode")
    print("Type your request, or 'quit' to exit.")
    print("Prefix with agent codename to target: KPRO: analyze prospects")
    print("-" * 50)

    while True:
        try:
            user_input = input("\n> ").strip()
        except (EOFError, KeyboardInterrupt):
            print("\nGoodbye!")
            break

        if not user_input:
            continue
        if user_input.lower() in ("quit", "exit", "q"):
            print("Goodbye!")
            break

        target_agent = None
        message = user_input
        for codename in registry.keys():
            if user_input.upper().startswith(f"{codename}:"):
                target_agent = codename
                message = user_input[len(codename) + 1 :].strip()
                break
        if not target_agent:
            target_agent = "KPRO"

        agent = registry.get(target_agent)
        if not agent:
            print(f"Agent {target_agent} not found")
            continue

        print(f"\n[{target_agent}] Processing...")
        print(f"\n{agent.run(message)}")


def cmd_serve(args: argparse.Namespace, _: dict[str, Any], __: dict[str, Any]) -> None:
    """Start FastAPI service with uvicorn."""
    env = os.environ.copy()
    env["KIESSCLAW_CONFIG"] = args.config
    command = [
        "uvicorn",
        "kiessclaw.api.app:app",
        "--host",
        args.host,
        "--port",
        str(args.port),
    ]
    if args.reload:
        command.append("--reload")

    try:
        subprocess.run(command, env=env, check=True)  # noqa: S603
    except FileNotFoundError:
        print("❌ uvicorn not found. Install dependencies and retry (`pip install -e .`).")
        sys.exit(1)
    except subprocess.CalledProcessError as exc:
        print(f"❌ Failed to start service: {exc}")
        sys.exit(1)


def build_parser() -> argparse.ArgumentParser:
    """Build and return the root CLI parser."""
    parser = argparse.ArgumentParser(
        prog="kiessclaw",
        description="🦅 KIESSCLAW - CLI Prompt + Code Generator for Agentic Workflows",
    )
    parser.add_argument("--config", default="config/config.yaml", help="Config file path")
    subparsers = parser.add_subparsers(dest="command")

    subparsers.add_parser("status", help="Show agent status and data summary")

    import_parser = subparsers.add_parser("import", help="Import contacts from CSV")
    import_parser.add_argument("--file", "-f", required=True, help="CSV file path")

    audit_parser = subparsers.add_parser("audit", help="Run SEO audit and optional auto-enroll pipeline")
    audit_parser.add_argument("--url", required=True, help="Target website URL")
    audit_parser.add_argument("--auto-enroll", action="store_true", help="Auto-route low-score domains into SDR")
    audit_parser.add_argument("--max-pages", type=int, default=20, help="Maximum pages to crawl")

    usecase_parser = subparsers.add_parser("usecase", help="Use case registry commands")
    usecase_subparsers = usecase_parser.add_subparsers(dest="action", required=True)
    usecase_subparsers.add_parser("list", help="List built-in use cases")
    usecase_show = usecase_subparsers.add_parser("show", help="Show one use case spec")
    usecase_show.add_argument("usecase_id", help="Use case ID")

    prompt_parser = subparsers.add_parser("prompt", help="Prompt rendering commands")
    prompt_subparsers = prompt_parser.add_subparsers(dest="action", required=True)
    prompt_render = prompt_subparsers.add_parser("render", help="Render one prompt template")
    prompt_render.add_argument("usecase_id", help="Use case ID")
    prompt_render.add_argument("--template", required=True, choices=["system", "task", "guardrails", "checklist"])
    prompt_render.add_argument("--json-input", required=True, help="Path to JSON input payload")
    prompt_render.add_argument("--style", default="strict", help="Prompt style mode")
    prompt_render.add_argument("--deterministic", action="store_true", help="Force stable deterministic rendering")

    prompt_pack = prompt_subparsers.add_parser("pack", help="Render full prompt pack")
    prompt_pack.add_argument("usecase_id", help="Use case ID")
    prompt_pack.add_argument("--json-input", required=True, help="Path to JSON input payload")
    prompt_pack.add_argument("--out", help="Write prompt pack JSON to output path")
    prompt_pack.add_argument("--style", default="strict", help="Prompt style mode")
    prompt_pack.add_argument("--deterministic", action="store_true", help="Force stable deterministic rendering")

    scaffold_parser = subparsers.add_parser("scaffold", help="Code generation commands")
    scaffold_subparsers = scaffold_parser.add_subparsers(dest="action", required=True)
    scaffold_usecase = scaffold_subparsers.add_parser("usecase", help="Scaffold one use case")
    scaffold_usecase.add_argument("usecase_id", help="Use case ID")
    scaffold_usecase.add_argument("--json-input", required=True, help="Path to JSON input payload")
    scaffold_usecase.add_argument("--outdir", required=True, help="Output directory")
    scaffold_usecase.add_argument("--force", action="store_true", help="Overwrite existing files")

    scaffold_agent_parser = scaffold_subparsers.add_parser("agent", help="Scaffold one agent module")
    scaffold_agent_parser.add_argument("name", help="Agent name")
    scaffold_agent_parser.add_argument("--outdir", required=True, help="Output directory")
    scaffold_agent_parser.add_argument("--force", action="store_true", help="Overwrite existing files")

    scaffold_workflow_parser = scaffold_subparsers.add_parser("workflow", help="Scaffold one workflow module")
    scaffold_workflow_parser.add_argument("name", help="Workflow name")
    scaffold_workflow_parser.add_argument("--outdir", required=True, help="Output directory")
    scaffold_workflow_parser.add_argument("--force", action="store_true", help="Overwrite existing files")

    scaffold_skill_parser = scaffold_subparsers.add_parser("skill", help="Scaffold one skill module")
    scaffold_skill_parser.add_argument("name", help="Skill name")
    scaffold_skill_parser.add_argument("--outdir", required=True, help="Output directory")
    scaffold_skill_parser.add_argument("--force", action="store_true", help="Overwrite existing files")

    scaffold_tests_parser = scaffold_subparsers.add_parser("tests", help="Scaffold test stubs")
    scaffold_tests_parser.add_argument("kind", help="Test suite name")
    scaffold_tests_parser.add_argument("--outdir", required=True, help="Output directory")
    scaffold_tests_parser.add_argument("--force", action="store_true", help="Overwrite existing files")

    providers_parser = subparsers.add_parser("providers", help="Provider plugin commands")
    providers_parser.add_argument("action", nargs="?", default="list", choices=["list", "health"])

    workflow_parser = subparsers.add_parser("workflow", help="Workflow execution commands")
    workflow_subparsers = workflow_parser.add_subparsers(dest="action", required=True)
    workflow_run = workflow_subparsers.add_parser("run", help="Run one use case workflow")
    workflow_run.add_argument("usecase_id", help="Use case ID")
    workflow_run.add_argument("--json-input", required=True, help="Path to JSON input payload")
    workflow_run.add_argument("--dry-run", action="store_true", help="Simulate only; do not persist changes")
    workflow_run.add_argument("--auto-enroll", action="store_true", help="Enable enrollment behavior where applicable")
    workflow_run.add_argument("--provider", help="Provider override (mock|hunter|apollo|clay)")

    integrations_parser = subparsers.add_parser("integrations", help="Integration commands")
    integrations_subparsers = integrations_parser.add_subparsers(dest="integration", required=True)
    slack_parser = integrations_subparsers.add_parser("slack", help="Slack integration commands")
    slack_subparsers = slack_parser.add_subparsers(dest="action", required=True)
    slack_subparsers.add_parser("test", help="Send one test Slack notification")

    schedule_parser = subparsers.add_parser("schedule", help="Scheduled workflow commands")
    schedule_subparsers = schedule_parser.add_subparsers(dest="action", required=True)
    schedule_add = schedule_subparsers.add_parser("add", help="Add one scheduled workflow job")
    schedule_add.add_argument("--usecase", dest="usecase_id", required=True, help="Use case ID")
    schedule_add.add_argument("--cron", required=True, help='Cron expression, e.g. "0 9 * * 1-5"')
    schedule_add.add_argument("--json-input", required=True, help="Path to JSON input payload")
    schedule_add.add_argument("--dry-run", action="store_true", help="Run scheduled job in dry-run mode")
    schedule_add.add_argument("--job-id", help="Optional explicit job ID")

    schedule_subparsers.add_parser("list", help="List scheduled workflow jobs")

    schedule_remove = schedule_subparsers.add_parser("remove", help="Remove one scheduled workflow job")
    schedule_remove.add_argument("job_id", help="Scheduled job ID")

    schedule_run = schedule_subparsers.add_parser("run", help="Run one scheduled workflow job immediately")
    schedule_run.add_argument("job_id", help="Scheduled job ID")

    contact_parser = subparsers.add_parser("contact", help="Contact management")
    contact_parser.add_argument("action", choices=["add", "list"], help="Contact action")
    contact_parser.add_argument("--email", help="Contact email (required for add)")
    contact_parser.add_argument("--first-name", help="First name")
    contact_parser.add_argument("--last-name", help="Last name")
    contact_parser.add_argument("--title", help="Job title")
    contact_parser.add_argument("--company", help="Company name")
    contact_parser.add_argument("--domain", help="Company domain")
    contact_parser.add_argument("--industry", help="Company industry")
    contact_parser.add_argument("--employee-count", type=int, help="Company employee count")

    sequence_parser = subparsers.add_parser("sequence", help="Sequence management")
    sequence_parser.add_argument(
        "action",
        nargs="?",
        default="list",
        choices=["list", "create", "activate", "pause", "add-step"],
    )
    sequence_parser.add_argument("--name", "-n", help="Sequence name")
    sequence_parser.add_argument("--id", help="Sequence ID")
    sequence_parser.add_argument("--sender-email", help="Sender email")
    sequence_parser.add_argument("--channel", default="email", help="Step channel")
    sequence_parser.add_argument("--delay-days", type=int, default=3, help="Step delay in days")
    sequence_parser.add_argument("--subject", help="Email subject")
    sequence_parser.add_argument("--body", help="Email body")

    enroll_parser = subparsers.add_parser("enroll", help="Enroll one contact into one sequence")
    enroll_parser.add_argument("contact_id", help="Contact ID")
    enroll_parser.add_argument("sequence_id", help="Sequence ID")
    enroll_parser.add_argument("--start-now", action="store_true", help="Start immediately")

    send_parser = subparsers.add_parser("send", help="Process and send due emails")
    send_parser.add_argument("--dry-run", "-d", action="store_true", help="Simulate only")
    send_parser.add_argument("--limit", "-l", type=int, default=50, help="Max emails to process")

    run_parser = subparsers.add_parser("run", help="Execute one operational cycle")
    run_parser.add_argument("--dry-run", action="store_true", help="Simulate send cycle")
    run_parser.add_argument("--limit", type=int, default=50, help="Max emails to process")
    run_parser.add_argument("--inbox-limit", type=int, default=50, help="Max replies to process")
    run_parser.add_argument("--skip-inbox", action="store_true", help="Skip inbox processing")

    reply_parser = subparsers.add_parser("reply", help="Simulate inbound reply")
    reply_parser.add_argument("--contact-id", required=True, help="Contact ID")
    reply_parser.add_argument("--text", required=True, help="Reply body text")
    reply_parser.add_argument("--subject", help="Reply subject")
    reply_parser.add_argument("--no-process", action="store_true", help="Do not process immediately")

    inbox_parser = subparsers.add_parser("inbox", help="Inbox management")
    inbox_parser.add_argument("action", nargs="?", default="status", choices=["status", "process", "dnc"])
    inbox_parser.add_argument("--limit", type=int, default=50, help="Max replies to process")
    inbox_parser.add_argument("--dnc-action", default="list", choices=["list", "add", "remove", "check"])
    inbox_parser.add_argument("--email", help="Email for DNC operations")
    inbox_parser.add_argument("--reason", help="Reason for DNC add")

    subparsers.add_parser("report", help="Generate weekly report")

    analytics_parser = subparsers.add_parser("analytics", help="Analytics summary")
    analytics_parser.add_argument("--days", type=int, default=30, help="Deliverability period")
    analytics_parser.add_argument("--report", action="store_true", help="Include markdown report")
    analytics_parser.add_argument("--save", action="store_true", help="Save report file when --report is used")

    subparsers.add_parser("analyze", help="LLM performance analysis")
    subparsers.add_parser("pipeline", help="Show prospect pipeline")
    subparsers.add_parser("chat", help="Interactive chat with agents")
    subparsers.add_parser("doctor", help="Check config/workspace/dependency health")

    serve_parser = subparsers.add_parser("serve", help="Run FastAPI service layer")
    serve_parser.add_argument("--port", type=int, default=8000, help="Service port")
    serve_parser.add_argument("--host", default="127.0.0.1", help="Service host")
    serve_parser.add_argument("--reload", action="store_true", help="Enable uvicorn reload")

    return parser


def main() -> None:
    """CLI entrypoint."""
    parser = build_parser()
    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        sys.exit(0)

    lightweight_commands = {"usecase", "prompt", "scaffold"}
    if args.command in lightweight_commands:
        config: dict[str, Any] = {}
        registry: dict[str, Any] = {}
    elif args.command == "doctor":
        config = {}
        registry = {}
    else:
        try:
            config = load_config(args.config)
        except Exception as exc:  # noqa: BLE001
            print(f"❌ Failed to load config: {exc}")
            sys.exit(1)
        registry = build_agent_registry(config)

    commands = {
        "status": cmd_status,
        "import": cmd_import,
        "audit": cmd_audit,
        "usecase": cmd_usecase,
        "prompt": cmd_prompt,
        "scaffold": cmd_scaffold,
        "providers": cmd_providers,
        "workflow": cmd_workflow,
        "integrations": cmd_integrations,
        "schedule": cmd_schedule,
        "contact": cmd_contact,
        "sequence": cmd_sequence,
        "enroll": cmd_enroll,
        "send": cmd_send,
        "run": cmd_run,
        "reply": cmd_reply,
        "inbox": cmd_inbox,
        "report": cmd_report,
        "analytics": cmd_analytics,
        "analyze": cmd_analyze,
        "pipeline": cmd_pipeline,
        "chat": cmd_chat,
        "serve": cmd_serve,
        "doctor": cmd_doctor,
    }

    command_handler = commands.get(args.command)
    if not command_handler:
        parser.print_help()
        sys.exit(1)
    command_handler(args, config, registry)


if __name__ == "__main__":
    main()
