"""Kanoniv CLI - Identity resolution from the command line.

Offline commands (validate, compile, hash, diff, plan) use the local
Rust engine via ``_native``. Cloud commands (ask, agent, login) use
httpx to call the Kanoniv API.

Entry point: ``kanoniv`` (installed via pip).
"""
from __future__ import annotations

import argparse
import sys

import kanoniv


def _build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="kanoniv",
        description="Kanoniv - Identity resolution as code",
    )
    p.add_argument(
        "--version", action="version", version=f"kanoniv {kanoniv.__version__}"
    )
    p.add_argument(
        "--api-key", dest="api_key_flag", default=None,
        help="API key (overrides env/stored creds)",
    )
    p.add_argument(
        "--api-url", dest="api_url_flag", default=None,
        help="API base URL (default: https://api.kanoniv.com)",
    )
    p.add_argument(
        "--format", dest="format", choices=["json", "table"], default="table",
        help="Output format (default: table)",
    )

    sub = p.add_subparsers(dest="command")

    # ── Offline commands ────────────────────────────────────────────

    sp_validate = sub.add_parser("validate", help="Validate a YAML spec")
    sp_validate.add_argument("path", help="Path to spec YAML file")

    sp_compile = sub.add_parser("compile", help="Compile spec to IR")
    sp_compile.add_argument("path", help="Path to spec YAML file")
    sp_compile.add_argument("-o", "--output", default=None, help="Output file path")

    sp_hash = sub.add_parser("hash", help="Compute spec hash")
    sp_hash.add_argument("path", help="Path to spec YAML file")

    sp_diff = sub.add_parser("diff", help="Diff two spec files")
    sp_diff.add_argument("v1", help="Path to first spec YAML")
    sp_diff.add_argument("v2", help="Path to second spec YAML")

    sp_plan = sub.add_parser("plan", help="Generate execution plan")
    sp_plan.add_argument("path", help="Path to spec YAML file")

    # ── Ingest command ────────────────────────────────────────────────

    sp_ingest = sub.add_parser(
        "ingest", help="Ingest CSV/JSON files into the Cloud"
    )
    sp_ingest.add_argument(
        "files", nargs="+",
        help="CSV/JSON files, glob patterns, or directories",
    )
    sp_ingest.add_argument(
        "--id-column", dest="id_column", default=None,
        help="Column to use as external_id (auto-detected if not set)",
    )
    sp_ingest.add_argument(
        "--entity-type", dest="entity_type", default=None,
        help="Only ingest files with identity fields for this type (person, company, product)",
    )

    # ── Cloud engine commands ────────────────────────────────────────

    sp_autodetect = sub.add_parser(
        "autodetect", help="Profile data, detect entity types and signals"
    )
    sp_autodetect.add_argument(
        "--bootstrap", action="store_true",
        help="Bootstrap an identity plan from detected signals",
    )
    sp_autodetect.add_argument(
        "--entity-type", dest="entity_type", default=None,
        help="Entity type to profile: person, product, company, transaction",
    )
    sp_autodetect.add_argument(
        "--version", default=None,
        help="Identity plan version name for bootstrap",
    )
    sp_autodetect.add_argument(
        "--sample-size", dest="sample_size", type=int, default=None,
        help="Number of rows to sample (default: 500)",
    )

    sp_reconcile = sub.add_parser("reconcile", help="Run reconciliation")
    sp_reconcile.add_argument(
        "--wait", action="store_true",
        help="Wait for reconciliation to complete",
    )
    sp_reconcile.add_argument(
        "--dry-run", dest="dry_run", action="store_true",
        help="Dry run (evaluate without persisting)",
    )
    sp_reconcile.add_argument(
        "--entity-type", dest="entity_type", default=None,
        help="Entity type to reconcile (uses {type}_v1 plan version)",
    )

    sp_stats = sub.add_parser("stats", help="Show dashboard statistics")
    sp_stats.add_argument(
        "--entity-type", dest="entity_type", default=None,
        help="Filter stats to a specific entity type (person, product, company)",
    )

    sp_autotune = sub.add_parser("autotune", help="Optimize matching thresholds")
    sp_autotune.add_argument(
        "--wait", action="store_true",
        help="Wait for autotune to complete (default: true)",
    )
    sp_autotune.add_argument(
        "--max-iterations", dest="max_iterations", type=int, default=50,
        help="Maximum optimization iterations (default: 50)",
    )
    sp_autotune.add_argument(
        "--version", default=None,
        help="Identity plan version to optimize",
    )

    sp_jobs = sub.add_parser("jobs", help="List recent jobs")
    sp_jobs.add_argument(
        "--limit", type=int, default=20, help="Max results (default: 20)"
    )

    # ── Purge command ────────────────────────────────────────────────

    sp_purge = sub.add_parser("purge", help="Delete all tenant data")
    sp_purge.add_argument(
        "--confirm", action="store_true",
        help="Skip interactive confirmation",
    )

    # ── Export command ──────────────────────────────────────────────

    sp_export = sub.add_parser("export", help="Export entities or memberships")
    export_sub = sp_export.add_subparsers(dest="export_target")

    sp_export_ent = export_sub.add_parser(
        "entities", help="Export resolved canonical entities"
    )
    sp_export_ent.add_argument(
        "-o", "--output", required=True, help="Output file path"
    )
    sp_export_ent.add_argument(
        "--entity-type", dest="entity_type", default=None,
        help="Filter by entity type (person, company, product)",
    )
    sp_export_ent.add_argument(
        "--reveal-pii", dest="reveal_pii", action="store_true",
        help="Include unmasked PII fields (admin only)",
    )
    sp_export_ent.add_argument(
        "--format", dest="export_format", choices=["csv", "json"],
        default="csv", help="Output format (default: csv)",
    )

    sp_export_mem = export_sub.add_parser(
        "memberships", help="Export source-to-entity memberships"
    )
    sp_export_mem.add_argument(
        "-o", "--output", required=True, help="Output file path"
    )
    sp_export_mem.add_argument(
        "--format", dest="export_format", choices=["csv", "json"],
        default="csv", help="Output format (default: csv)",
    )

    # ── Auth commands ───────────────────────────────────────────────

    sub.add_parser("login", help="Store API credentials")
    sub.add_parser("logout", help="Remove stored credentials")
    sub.add_parser("context", help="Show current auth context")

    # ── Ask (LLM chat) ─────────────────────────────────────────────

    sp_ask = sub.add_parser("ask", help="Chat with the Kanoniv LLM")
    sp_ask.add_argument("message", help="Message to send")
    sp_ask.add_argument(
        "--conversation", default=None, help="Continue existing conversation"
    )
    sp_ask.add_argument(
        "--new", dest="new_conversation", action="store_true",
        help="Start a new conversation (ignore last conversation)",
    )
    sp_ask.add_argument(
        "--plan", dest="plan", action="store_true",
        help="Force plan generation mode",
    )
    sp_ask.add_argument(
        "--generate-spec", dest="generate_spec", action="store_true",
        help="Generate an identity spec",
    )
    sp_ask.add_argument(
        "--sources", default=None,
        help="Source names for spec generation (comma-separated)",
    )
    sp_ask.add_argument(
        "--entity-ids", dest="entity_ids", default=None,
        help="Entity IDs for explain (comma-separated UUIDs)",
    )

    # ── Agent management ────────────────────────────────────────────

    sp_agent = sub.add_parser("agent", help="Manage AI agents")
    agent_sub = sp_agent.add_subparsers(dest="agent_action")

    agent_sub.add_parser("list", help="List agent configurations")

    sp_enable = agent_sub.add_parser("enable", help="Enable an agent")
    sp_enable.add_argument("agent_type", help="Agent type (e.g. auto_merge)")
    sp_enable.add_argument("--settings", default=None, help="Settings as JSON")

    sp_disable = agent_sub.add_parser("disable", help="Disable an agent")
    sp_disable.add_argument("agent_type", help="Agent type")

    sp_trigger = agent_sub.add_parser("trigger", help="Trigger an agent run")
    sp_trigger.add_argument("agent_type", help="Agent type")

    sp_runs = agent_sub.add_parser("runs", help="List agent runs")
    sp_runs.add_argument("--type", default=None, help="Filter by agent type")
    sp_runs.add_argument("--limit", type=int, default=20, help="Max results")

    sp_actions = agent_sub.add_parser("actions", help="List agent actions")
    sp_actions.add_argument(
        "--status", dest="status_filter", default=None,
        help="Filter by status (pending, approved, rejected)",
    )
    sp_actions.add_argument("--limit", type=int, default=20, help="Max results")

    sp_approve = agent_sub.add_parser("approve", help="Approve a pending action")
    sp_approve.add_argument("action_id", help="Action ID")

    sp_reject = agent_sub.add_parser("reject", help="Reject a pending action")
    sp_reject.add_argument("action_id", help="Action ID")

    return p


def _require_cloud_client(args: argparse.Namespace):
    """Build an HTTP client for cloud commands, or exit with instructions."""
    from kanoniv.cli.config import resolve_api_key, resolve_api_url

    api_key = resolve_api_key(args.api_key_flag)
    if not api_key:
        sys.stderr.write(
            "No API key found. Set one with:\n"
            "  kanoniv login\n"
            "  --api-key <key>\n"
            "  KANONIV_API_KEY=<key>\n"
        )
        sys.exit(1)

    api_url = resolve_api_url(args.api_url_flag)

    from kanoniv.cli.http import CliHttpClient
    return CliHttpClient(api_url, api_key)


def main() -> None:
    parser = _build_parser()
    args = parser.parse_args()

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

    try:
        _dispatch(args, parser)
    except KeyboardInterrupt:
        sys.stderr.write("\n")
        sys.exit(130)
    except Exception as exc:
        sys.stderr.write(f"error: {exc}\n")
        sys.exit(1)


def _dispatch(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None:
    # ── Offline commands (no network) ──────────────────────────────

    if args.command in ("validate", "compile", "hash", "diff", "plan"):
        from kanoniv.cli.commands.offline import (
            cmd_validate, cmd_compile, cmd_hash, cmd_diff, cmd_plan,
        )
        dispatch = {
            "validate": cmd_validate,
            "compile": cmd_compile,
            "hash": cmd_hash,
            "diff": cmd_diff,
            "plan": cmd_plan,
        }
        dispatch[args.command](args)
        return

    # ── Ingest command ────────────────────────────────────────────

    if args.command == "ingest":
        client = _require_cloud_client(args)
        try:
            from kanoniv.cli.commands.ingest import cmd_ingest
            cmd_ingest(args, client)
        finally:
            client.close()
        return

    # ── Purge command ──────────────────────────────────────────────

    if args.command == "purge":
        client = _require_cloud_client(args)
        try:
            from kanoniv.cli.commands.purge import cmd_purge
            cmd_purge(args, client)
        finally:
            client.close()
        return

    # ── Export command ────────────────────────────────────────────

    if args.command == "export":
        if not getattr(args, "export_target", None):
            sys.stderr.write(
                "usage: kanoniv export {entities,memberships}\n"
            )
            sys.exit(1)
        client = _require_cloud_client(args)
        try:
            from kanoniv.cli.commands.export import (
                cmd_export_entities, cmd_export_memberships,
            )
            if args.export_target == "entities":
                cmd_export_entities(args, client)
            else:
                cmd_export_memberships(args, client)
        finally:
            client.close()
        return

    # ── Cloud engine commands ─────────────────────────────────────

    if args.command in ("autodetect", "reconcile", "stats", "jobs", "autotune"):
        client = _require_cloud_client(args)
        try:
            from kanoniv.cli.commands.engine import (
                cmd_autodetect, cmd_reconcile, cmd_stats, cmd_jobs,
                cmd_autotune,
            )
            dispatch = {
                "autodetect": cmd_autodetect,
                "reconcile": cmd_reconcile,
                "stats": cmd_stats,
                "jobs": cmd_jobs,
                "autotune": cmd_autotune,
            }
            dispatch[args.command](args, client)
        finally:
            client.close()
        return

    # ── Auth commands ──────────────────────────────────────────────

    if args.command in ("login", "logout", "context"):
        from kanoniv.cli.commands.auth import cmd_login, cmd_logout, cmd_context
        dispatch = {
            "login": cmd_login,
            "logout": cmd_logout,
            "context": cmd_context,
        }
        dispatch[args.command](args)
        return

    # ── Cloud commands (require API key + httpx) ──────────────────

    if args.command == "ask":
        client = _require_cloud_client(args)
        try:
            from kanoniv.cli.commands.ask import cmd_ask
            cmd_ask(args, client)
        finally:
            client.close()
        return

    if args.command == "agent":
        if not args.agent_action:
            sys.stderr.write("usage: kanoniv agent {list,enable,disable,trigger,runs,actions,approve,reject}\n")
            sys.exit(1)
        client = _require_cloud_client(args)
        try:
            from kanoniv.cli.commands.agent import cmd_agent
            cmd_agent(args, client)
        finally:
            client.close()
        return

    parser.print_help()
    sys.exit(1)


if __name__ == "__main__":
    main()
