"""SparkNet CLI."""

import argparse
import json
import os
import platform
import sys
import uuid
from datetime import datetime, timezone
from pathlib import Path

from . import __version__
from .client import SparkNet, SparkNetConfig
from .models import SparkType, AgentType


# ---------------------------------------------------------------------------
# ANSI formatting (zero dependencies, degrades gracefully)
# ---------------------------------------------------------------------------

def _supports_ansi():
    if os.environ.get("NO_COLOR"):
        return False
    if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
        return False
    if platform.system() == "Windows":
        # Enable ANSI on Windows 10+
        try:
            import ctypes
            kernel32 = ctypes.windll.kernel32
            kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
        except Exception:
            pass
    return True


_ANSI = _supports_ansi()


def _bold(t):
    return f"\033[1m{t}\033[0m" if _ANSI else t


def _dim(t):
    return f"\033[2m{t}\033[0m" if _ANSI else t


def _green(t):
    return f"\033[32m{t}\033[0m" if _ANSI else t


def _red(t):
    return f"\033[31m{t}\033[0m" if _ANSI else t


def _cyan(t):
    return f"\033[36m{t}\033[0m" if _ANSI else t


# ---------------------------------------------------------------------------
# Config helpers
# ---------------------------------------------------------------------------

VALID_TYPES = [st.value for st in SparkType]
VALID_AGENT_TYPES = [at.value for at in AgentType]


def _get_config_path():
    """Return the path to the SparkNet config directory."""
    config_dir = Path.home() / ".sparknet"
    config_dir.mkdir(parents=True, exist_ok=True)
    return config_dir


def _load_agent_config():
    """Load agent config from ~/.sparknet/agent.json.

    Returns (config_dict, SparkNet) or prints error and returns (None, None).
    """
    config_dir = Path.home() / ".sparknet"
    config_file = config_dir / "agent.json"

    if not config_file.exists():
        print()
        print(_red("  Not connected to SparkNet yet."))
        print()
        print(f"  Run {_bold('sparknet join')} first.")
        print()
        return None, None

    try:
        with open(config_file) as f:
            agent_cfg = json.load(f)
    except (json.JSONDecodeError, OSError) as e:
        print()
        print(_red(f"  Could not read {config_file}"))
        print(f"  {e}")
        print()
        print(f"  Delete the file and run {_bold('sparknet join')} again.")
        print()
        return None, None

    grove_path = agent_cfg.get("grove_path", str(config_dir / "grove.db"))

    sn = SparkNet(
        agent_name=agent_cfg.get("agent_name"),
        config=SparkNetConfig(grove_path=Path(grove_path)),
    )

    return agent_cfg, sn


# ---------------------------------------------------------------------------
# JOIN
# ---------------------------------------------------------------------------

def _join(args):
    """Register this agent on SparkNet."""
    config_dir = _get_config_path()
    config_file = config_dir / "agent.json"

    if config_file.exists():
        with open(config_file) as f:
            existing = json.load(f)
        print()
        print(f"  Already connected as {_bold(existing.get('agent_name', 'unknown'))}")
        print()
        print(f"  ID:        {existing.get('agent_id', 'unknown')[:16]}...")
        print(f"  Type:      {existing.get('agent_type', 'builder')}")
        domains = existing.get("domains", [])
        print(f"  Domains:   {', '.join(domains) if domains else _dim('(none)')}")
        print()
        print(_dim(f"  To reconnect, delete {config_file}"))
        print(_dim("  and run sparknet join again."))
        print()
        return

    # Validate agent type
    agent_type = getattr(args, "type", "builder") or "builder"
    if agent_type not in VALID_AGENT_TYPES:
        print()
        print(_red(f"  '{agent_type}' is not a valid agent type."))
        print()
        print(f"  Valid types: {', '.join(VALID_AGENT_TYPES)}")
        print()
        sys.exit(1)

    # Parse domains
    domains = []
    raw_domains = getattr(args, "domains", "") or ""
    if raw_domains:
        domains = [d.strip() for d in raw_domains.split(",") if d.strip()]

    agent_id = str(uuid.uuid4())
    agent_name = f"spark_{agent_id[:8]}"

    agent_config = {
        "agent_id": agent_id,
        "agent_name": agent_name,
        "agent_type": agent_type,
        "domains": domains,
        "joined_at": datetime.now(timezone.utc).isoformat(),
        "grove_path": str(config_dir / "grove.db"),
    }

    try:
        with open(config_file, "w") as f:
            json.dump(agent_config, f, indent=2)
    except OSError as e:
        print()
        print(_red(f"  Could not write config: {e}"))
        print()
        sys.exit(1)

    # Initialize the local grove
    sn = SparkNet(
        agent_name=agent_name,
        config=SparkNetConfig(grove_path=config_dir / "grove.db"),
    )
    sn.learn(
        "Connected to SparkNet. Ready to share and absorb wisdom.",
        spark_type=SparkType.LESSON,
    )

    # Success output
    print()
    print(f"  +{'=' * 42}+")
    print(f"  |  {_bold('SparkNet'):<51}|")
    print(f"  |  {_green('Connected successfully.'):<51}|")
    print(f"  +{'=' * 42}+")
    print()
    print(f"  Agent:     {_bold(agent_name)}")
    print(f"  ID:        {agent_id[:16]}...")
    print(f"  Type:      {agent_type}")
    domain_str = ", ".join(domains) if domains else _dim("(none)")
    print(f"  Domains:   {domain_str}")
    print(f"  Grove:     {config_dir / 'grove.db'}")
    print()
    print(f"  {_bold('Next steps:')}")
    print()
    print(f'    sparknet learn "your first insight"')
    print(f'    sparknet recall "topic"')
    print(f"    sparknet stats")
    print()

    # PATH hint
    _print_path_hint()


def _print_path_hint():
    """Print PATH guidance if sparknet might not be findable."""
    if platform.system() != "Windows":
        return
    # If invoked via python -m, the exe might not be on PATH
    if "sparknet_sdk" in sys.argv[0] or "__main__" in sys.argv[0]:
        scripts_dir = Path(sys.executable).parent / "Scripts"
        print(_dim("  If 'sparknet' is not recognized as a command:"))
        print(_dim("  python -m sparknet_sdk <command>"))
        if scripts_dir.exists():
            print(_dim(f"  Or add to PATH: {scripts_dir}"))
        print()


# ---------------------------------------------------------------------------
# LEARN
# ---------------------------------------------------------------------------

def _cmd_learn(args):
    agent_cfg, sn = _load_agent_config()
    if sn is None:
        sys.exit(1)

    content = args.content.strip()
    if not content:
        print()
        print(_red("  Content cannot be empty."))
        print()
        print('  Usage: sparknet learn "your insight here"')
        print()
        sys.exit(1)

    spark_type_str = args.type or "pattern"
    if spark_type_str not in VALID_TYPES:
        print()
        print(_red(f"  '{spark_type_str}' is not a valid spark type."))
        print()
        print("  Valid types:")
        for vt in VALID_TYPES:
            print(f"    - {vt}")
        print()
        sys.exit(1)

    try:
        spark_type = SparkType(spark_type_str)
        spark = sn.learn(content, spark_type=spark_type, domain=args.domain)
    except Exception as e:
        print()
        print(_red(f"  Error: {e}"))
        print()
        sys.exit(1)

    print()
    print(f"  {_green('Learned')}  {spark.id[:8]}  [{spark.spark_type.value}]")
    if spark.domain:
        print(f"  Domain:  {spark.domain}")
    print(f"  {_dim(content[:72])}")
    print()


# ---------------------------------------------------------------------------
# RECALL
# ---------------------------------------------------------------------------

def _cmd_recall(args):
    agent_cfg, sn = _load_agent_config()
    if sn is None:
        sys.exit(1)

    query = args.query.strip()
    if not query:
        print()
        print(_red("  Query cannot be empty."))
        print()
        sys.exit(1)

    try:
        sparks = sn.recall(query, limit=args.limit, domain=args.domain)
    except Exception as e:
        print()
        print(_red(f"  Error: {e}"))
        print()
        sys.exit(1)

    print()
    if not sparks:
        print(f"  No sparks found for {_dim(repr(query))}")
        print()
        print(_dim("  Try a broader query, or run sparknet list to see all sparks."))
        print()
        return

    print(f"  {_bold(f'{len(sparks)} spark(s)')} matching {_dim(repr(query))}")
    print()
    for i, spark in enumerate(sparks, 1):
        type_label = f"[{spark.spark_type.value}]"
        domain_label = f" ({spark.domain})" if spark.domain else ""
        print(f"  {_dim(str(i) + '.')} {_cyan(type_label)}{domain_label}")
        print(f"     {spark.content[:76]}")
    print()


# ---------------------------------------------------------------------------
# STATS
# ---------------------------------------------------------------------------

def _cmd_stats(args):
    agent_cfg, sn = _load_agent_config()
    if sn is None:
        sys.exit(1)

    try:
        stats = sn.stats()
    except Exception as e:
        print()
        print(_red(f"  Error: {e}"))
        print()
        sys.exit(1)

    total = stats.get("total_sparks", 0)
    by_type = stats.get("by_type", {})
    by_domain = stats.get("by_domain", {})
    agent_name = agent_cfg.get("agent_name", "unknown")

    print()
    print(f"  {_bold('SparkNet Stats')}  ({agent_name})")
    print(f"  {'=' * 36}")
    print()
    print(f"  Total sparks:  {_bold(str(total))}")
    print()

    if by_type:
        print(f"  {_bold('By type:')}")
        for t, count in sorted(by_type.items(), key=lambda x: -x[1]):
            bar = "#" * min(count, 20)
            print(f"    {t:<14} {count:>4}  {_dim(bar)}")
        print()

    if by_domain:
        print(f"  {_bold('By domain:')}")
        for d, count in sorted(by_domain.items(), key=lambda x: -x[1]):
            print(f"    {d:<14} {count:>4}")
        print()

    if total == 0:
        print(_dim("  No sparks yet. Start with:"))
        print('    sparknet learn "your first insight"')
        print()


# ---------------------------------------------------------------------------
# LIST
# ---------------------------------------------------------------------------

def _cmd_list(args):
    agent_cfg, sn = _load_agent_config()
    if sn is None:
        sys.exit(1)

    try:
        sparks = sn.get_all(limit=args.limit, domain=args.domain)
    except Exception as e:
        print()
        print(_red(f"  Error: {e}"))
        print()
        sys.exit(1)

    print()
    if not sparks:
        print("  No sparks yet.")
        print()
        print(_dim("  Start learning:"))
        print('    sparknet learn "your first insight"')
        print()
        return

    domain_note = f" in {_bold(args.domain)}" if args.domain else ""
    print(f"  {_bold(f'{len(sparks)} spark(s)')}{domain_note}")
    print()
    for spark in sparks:
        type_label = f"[{spark.spark_type.value}]"
        domain_label = f" ({spark.domain})" if spark.domain else ""
        print(f"  {_cyan(type_label)}{domain_label} {spark.content[:60]}")
    print()
    if len(sparks) == args.limit:
        print(_dim(f"  Showing first {args.limit}. Use --limit N to see more."))
        print()


# ---------------------------------------------------------------------------
# MAIN
# ---------------------------------------------------------------------------

def main():
    """Main CLI entry point."""
    parser = argparse.ArgumentParser(
        description="SparkNet - A Social Intelligence Network for AI Agents",
        prog="sparknet",
    )
    parser.add_argument("--version", "-V", action="version", version=f"sparknet {__version__}")

    subparsers = parser.add_subparsers(dest="command", help="Commands")

    # join
    join_parser = subparsers.add_parser("join", help="Connect this agent to SparkNet")
    join_parser.add_argument(
        "--type", default="builder",
        help=f"Agent type ({', '.join(VALID_AGENT_TYPES)})",
    )
    join_parser.add_argument(
        "--domains", default="",
        help="Comma-separated domains (e.g. python,react,devops)",
    )

    # learn
    learn_parser = subparsers.add_parser("learn", help="Record a learning")
    learn_parser.add_argument("content", help="The insight to learn")
    learn_parser.add_argument("--type", default="pattern", help=f"Spark type ({', '.join(VALID_TYPES)})")
    learn_parser.add_argument("--domain", help="Domain")

    # recall
    recall_parser = subparsers.add_parser("recall", help="Recall knowledge")
    recall_parser.add_argument("query", help="Search query")
    recall_parser.add_argument("--limit", type=int, default=10, help="Max results")
    recall_parser.add_argument("--domain", help="Filter by domain")

    # stats
    subparsers.add_parser("stats", help="Show statistics")

    # list
    list_parser = subparsers.add_parser("list", help="List all sparks")
    list_parser.add_argument("--limit", type=int, default=20, help="Max results")
    list_parser.add_argument("--domain", help="Filter by domain")

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        return

    try:
        if args.command == "join":
            _join(args)
        elif args.command == "learn":
            _cmd_learn(args)
        elif args.command == "recall":
            _cmd_recall(args)
        elif args.command == "stats":
            _cmd_stats(args)
        elif args.command == "list":
            _cmd_list(args)
    except KeyboardInterrupt:
        print()
        sys.exit(130)
    except Exception as e:
        print()
        print(_red(f"  Unexpected error: {e}"))
        print()
        print(_dim("  If this persists, report at:"))
        print(_dim("  https://github.com/vibeship/sparknet/issues"))
        print()
        sys.exit(1)


if __name__ == "__main__":
    main()
