"""Install QuickCall skills into a project's .claude/skills/ via symlinks.

Skills are shipped inside the qc_trace package and managed by the daemon
at ~/.quickcall-trace/skills/. This module symlinks them into the current
project for Claude Code to discover.

Usage:
    quickcall init skills           # interactive picker
    quickcall init skills --all     # install all skills
    quickcall init skills rules  # install specific skill
    quickcall cleanup skills        # remove broken symlinks
    quickcall refresh skills        # force-pull skills from platform
"""

from __future__ import annotations

import json
import os
import urllib.error
import urllib.request
from pathlib import Path

# ─── Colors ────────────────────────────────────────────

R = "\033[0m"
B = "\033[1m"
D = "\033[2m"
GRN = "\033[32m"
YLW = "\033[33m"
RED = "\033[31m"
CYN = "\033[36m"

QC_DIR = Path(os.environ.get("QC_TRACE_CONFIG_DIR", str(Path.home() / ".quickcall-trace")))
SKILLS_SOURCE = QC_DIR / "skills"


def ok(msg: str) -> None:
    print(f"  {GRN}{B}✓{R} {msg}")


def warn(msg: str) -> None:
    print(f"  {YLW}{B}!{R} {msg}")


def fail(msg: str) -> None:
    print(f"  {RED}{B}✗{R} {msg}")


def step(msg: str) -> None:
    print(f"\n  {CYN}{B}→{R} {msg}")


def _describe(name: str) -> str:
    """Read description from SKILL.md frontmatter."""
    skill_md = SKILLS_SOURCE / name / "SKILL.md"
    if not skill_md.exists():
        return name
    try:
        in_frontmatter = False
        for line in skill_md.read_text().splitlines():
            if line.strip() == "---":
                if in_frontmatter:
                    break
                in_frontmatter = True
                continue
            if in_frontmatter and line.startswith("description:"):
                desc = line.split(":", 1)[1].strip().strip("'\"")
                # Truncate to 80 chars
                return desc[:80] + ("..." if len(desc) > 80 else "")
    except OSError:
        pass
    return name


def _discover_skills() -> list[str]:
    """Find all valid skill directories in ~/.quickcall-trace/skills/."""
    if not SKILLS_SOURCE.exists():
        return []
    skills = []
    for d in sorted(SKILLS_SOURCE.iterdir()):
        if d.is_dir() and (d / "SKILL.md").exists():
            skills.append(d.name)
    return skills


def _symlink_skill(skill_name: str, target_dir: Path) -> bool:
    """Create symlink: target_dir/<skill_name> → SKILLS_SOURCE/<skill_name>.

    Returns True if created/updated, False if already correct.
    """
    source = SKILLS_SOURCE / skill_name
    link = target_dir / skill_name

    if link.is_symlink():
        if link.resolve() == source.resolve():
            return False  # already correct
        link.unlink()  # stale symlink, refresh

    if link.exists():
        warn(f"{link} exists and is not a symlink — skipping")
        return False

    link.symlink_to(source)
    return True


def init_skills(project_dir: Path, skill_names: list[str] | None = None, install_all: bool = False) -> int:
    """Install QuickCall skills into project's .claude/skills/.

    Args:
        project_dir: Project root (where .claude/ lives)
        skill_names: Specific skills to install (None = interactive)
        install_all: Install all available skills

    Returns exit code.
    """
    print()
    print(f"  {CYN}{B}quickcall skills{R} {D}by QuickCall{R}")
    print()

    # Check source exists
    available = _discover_skills()
    if not available:
        fail("No skills found in ~/.quickcall-trace/skills/")
        fail("Make sure the QuickCall daemon has run at least once.")
        return 1

    # Determine what to install
    selected: list[str] = []

    if install_all:
        selected = available
    elif skill_names:
        # Direct mode
        for name in skill_names:
            if name in available:
                selected.append(name)
            else:
                fail(f"Unknown skill: {name}")
                print(f"  {D}Available: {', '.join(available)}{R}")
                return 1
    else:
        # Interactive picker
        print(f"  {B}Available skills:{R}")
        print()
        for i, name in enumerate(available):
            desc = _describe(name)
            print(f"    {CYN}{B}{i + 1}{R}) {B}{name}{R}  {D}— {desc}{R}")
        print()
        print(f"    {CYN}{B}a{R}) {B}all{R}  {D}— install everything{R}")
        print()

        try:
            selection = input("  Select skills (e.g. 1 3, or a for all): ").strip()
        except (EOFError, KeyboardInterrupt):
            print()
            return 0

        if not selection:
            print(f"  {D}Nothing selected{R}")
            return 0

        if selection in ("a", "all"):
            selected = available
        else:
            for token in selection.split():
                if token.isdigit():
                    idx = int(token) - 1
                    if 0 <= idx < len(available):
                        selected.append(available[idx])
                    else:
                        warn(f"Invalid number: {token}")
                elif token in available:
                    selected.append(token)
                else:
                    warn(f"Unknown skill: {token}")

        if not selected:
            print(f"  {D}Nothing selected{R}")
            return 0

    # Install
    target_dir = project_dir / ".claude" / "skills"
    target_dir.mkdir(parents=True, exist_ok=True)

    step(f"Installing {len(selected)} skill(s) into {target_dir}")
    print()

    installed = 0
    skipped = 0
    for name in selected:
        if _symlink_skill(name, target_dir):
            ok(f"{B}{name}{R}  {D}→ {SKILLS_SOURCE / name}{R}")
            installed += 1
        else:
            ok(f"{B}{name}{R}  {D}(already installed){R}")
            skipped += 1

    # Wire AGENTS.md and CLAUDE.md
    agents_created = ensure_agents_md(project_dir)
    claude_wired = wire_claude_md(project_dir)

    if agents_created:
        ok("Created AGENTS.md with QuickCall rules section")
    if claude_wired:
        ok("Added @AGENTS.md import to CLAUDE.md")

    print()
    if installed > 0:
        ok(f"Installed {installed} skill(s). Restart Claude Code to activate.")
    if skipped > 0:
        print(f"  {D}{skipped} skill(s) already up to date{R}")
    print()
    return 0


_AGENTS_MD_SENTINEL_START = "<!-- quickcall:rules-start -->"
_AGENTS_MD_SENTINEL_END = "<!-- quickcall:rules-end -->"

_AGENTS_MD_TEMPLATE = f"""# AGENTS.md

{_AGENTS_MD_SENTINEL_START}
{_AGENTS_MD_SENTINEL_END}
"""

_CLAUDE_MD_IMPORT_LINE = "@AGENTS.md <!-- QuickCall recommendations — accepted rules for AI assistants -->"


def ensure_agents_md(project_dir: Path) -> bool:
    """Create or update AGENTS.md with QuickCall sentinel section.

    Returns True if the file was created or modified.
    """
    agents_md = project_dir / "AGENTS.md"

    if not agents_md.exists():
        agents_md.write_text(_AGENTS_MD_TEMPLATE)
        return True

    content = agents_md.read_text()
    if _AGENTS_MD_SENTINEL_START in content:
        return False

    # Append sentinel section
    suffix = f"\n{_AGENTS_MD_SENTINEL_START}\n{_AGENTS_MD_SENTINEL_END}\n"
    agents_md.write_text(content + suffix)
    return True


def wire_claude_md(project_dir: Path) -> bool:
    """Add @AGENTS.md import to CLAUDE.md if it exists and doesn't reference it.

    Returns True if CLAUDE.md was modified.
    """
    claude_md = project_dir / "CLAUDE.md"

    if not claude_md.exists():
        return False

    content = claude_md.read_text()
    if "agents.md" in content.lower():
        return False

    claude_md.write_text(content.rstrip("\n") + "\n" + _CLAUDE_MD_IMPORT_LINE + "\n")
    return True


def cleanup_skills(project_dir: Path) -> int:
    """Scan .claude/skills/ for broken symlinks and remove them.

    Args:
        project_dir: Project root (where .claude/ lives)

    Returns exit code.
    """
    print()
    print(f"  {CYN}{B}quickcall cleanup skills{R}")
    print()

    skills_dir = project_dir / ".claude" / "skills"
    if not skills_dir.exists():
        warn("No .claude/skills/ directory found")
        print()
        return 0

    removed = 0
    for entry in sorted(skills_dir.iterdir()):
        if entry.is_symlink() and not entry.exists():
            entry.unlink()
            ok(f"Removed broken symlink: {B}{entry.name}{R}")
            removed += 1

    if removed == 0:
        ok("No broken symlinks found")
    else:
        ok(f"Cleaned up {removed} broken symlink(s)")
    print()
    return 0


def refresh_skills() -> int:
    """Force-pull skills from platform via the local daemon server.

    Reads device_id and api_key from config.json, then POSTs to
    localhost:19777/api/pull-skills.

    Returns exit code.
    """
    print()
    print(f"  {CYN}{B}quickcall refresh skills{R}")
    print()

    config_path = QC_DIR / "config.json"
    if not config_path.exists():
        fail("No config.json found. Run 'quickcall login' first.")
        print()
        return 1

    try:
        config_data = json.loads(config_path.read_text())
    except (json.JSONDecodeError, OSError) as e:
        fail(f"Failed to read config.json: {e}")
        print()
        return 1

    device_id = config_data.get("device_id")
    api_key = config_data.get("api_key")

    if not device_id:
        # Try .device_id file
        device_id_file = QC_DIR / ".device_id"
        if device_id_file.exists():
            device_id = device_id_file.read_text().strip()

    if not device_id:
        fail("No device_id found. Run 'quickcall login' first.")
        print()
        return 1

    url = "http://localhost:19777/api/pull-skills"
    payload = json.dumps({"device_id": device_id}).encode()
    req = urllib.request.Request(url, data=payload, method="POST")
    req.add_header("Content-Type", "application/json")
    if api_key:
        req.add_header("Authorization", f"Bearer {api_key}")

    step("Requesting skills refresh from daemon...")
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            result = json.loads(resp.read())
            if result.get("ok"):
                ok(f"Skills refresh queued (command_id={result.get('command_id')})")
            else:
                fail(f"Server responded with: {result}")
                print()
                return 1
    except urllib.error.HTTPError as e:
        body = e.read().decode(errors="replace")
        fail(f"Server error ({e.code}): {body}")
        print()
        return 1
    except (urllib.error.URLError, OSError) as e:
        fail(f"Could not reach daemon server: {e}")
        warn("Make sure the daemon is running ('quickcall status')")
        print()
        return 1

    print()
    return 0
