"""linear-canal init — Initialize a project for Linear Canal (non-interactive).

Like ``linear-canal setup`` but fully automatic — no prompts.
Auto-detects installed agents, installs skills + hooks + MCP config,
runs the indexing pipeline, and registers the project globally.

Use ``linear-canal setup`` for the interactive guided experience.
Use ``linear-canal init`` for scripted / CI / headless workflows.
"""

from __future__ import annotations

import json
import os
import shutil
import sys
from pathlib import Path
from typing import Optional

import click
from rich.console import Console
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.table import Table

console = Console()

# ── Data directory name ───────────────────────────────────────────────────────
LC_DATA_DIR = ".linear_canal_data"

# ── Agent detection table ─────────────────────────────────────────────────────
# Each entry maps an agent key to its detection heuristics and skills directory.
# The skills_dir is where ``lc-context/SKILL.md`` gets installed, relative to
# the project root.

_AGENTS = {
    "claude": {
        "name": "Claude Code",
        "detect_files": [".claude", "claude.json"],
        "detect_commands": ["claude"],
        "skills_dir": ".claude/skills",
        "mcp_config": ".mcp.json",
    },
    "gemini": {
        "name": "Gemini CLI",
        "detect_files": [".gemini"],
        "detect_commands": ["gemini"],
        "skills_dir": ".gemini/skills",
        "mcp_config": ".gemini/settings.json",
    },
    "cursor": {
        "name": "Cursor",
        "detect_files": [".cursor", ".cursorrc"],
        "detect_commands": ["cursor"],
        "skills_dir": ".cursor/rules",
        "mcp_config": ".cursor/mcp.json",
    },
    "opencode": {
        "name": "OpenCode",
        "detect_files": [".opencode"],
        "detect_commands": ["opencode"],
        "skills_dir": "skills",
        "mcp_config": ".opencode/config.json",
    },
    "kilo": {
        "name": "Kilo",
        "detect_files": [".kilo", "kilo.config.json"],
        "detect_commands": ["kilo"],
        "skills_dir": ".claude/skills",
        "mcp_config": ".mcp.json",
    },
    "cline": {
        "name": "Cline",
        "detect_files": [".cline"],
        "detect_commands": ["cline"],
        "skills_dir": ".cline/skills",
        "mcp_config": None,
    },
    "roo": {
        "name": "Roo",
        "detect_files": [".roo"],
        "detect_commands": [],
        "skills_dir": ".roo/rules",
        "mcp_config": None,
    },
    "windsurf": {
        "name": "Windsurf",
        "detect_files": [".windsurf"],
        "detect_commands": ["windsurf"],
        "skills_dir": ".windsurf/rules",
        "mcp_config": None,
    },
}


@click.command("init")
@click.option(
    "--project",
    "-p",
    type=click.Path(exists=True, file_okay=False),
    default=".",
    help="Path to the project to initialize",
)
@click.option(
    "--agents",
    "-a",
    type=str,
    default=None,
    help="Comma-separated list of agents (claude,gemini,cursor,opencode,...). Auto-detects if not specified.",
)
@click.option(
    "--profile",
    type=click.Choice(["standard", "minimal"]),
    default=None,
    help=(
        "Embedding profile: "
        "'standard' (nomic-embed-code, 768-dim) or "
        "'minimal' (bge-small, 384-dim). "
        "Auto-detected from system RAM if not set."
    ),
)
@click.option(
    "--force",
    is_flag=True,
    default=False,
    help="Overwrite existing config files",
)
@click.option(
    "--skip-index",
    is_flag=True,
    default=False,
    help="Skip the indexing pipeline (only write config files)",
)
@click.pass_context
def init_cmd(
    ctx: click.Context,
    project: str,
    agents: Optional[str],
    profile: Optional[str],
    force: bool,
    skip_index: bool,
) -> None:
    """Initialize a project for Linear Canal (non-interactive).

    Auto-detects installed AI agents, writes MCP configuration,
    installs skills + hooks, and runs the indexing pipeline.

    \b
    For interactive guided setup, use:  linear-canal setup
    """
    project_path = Path(project).resolve()
    console.print(
        Panel(
            f"[bold orange1]Linear Canal[/bold orange1]  "
            f"[grey74]Project Initialization[/grey74]\n"
            f"[dim]Project:[/dim] [white]{project_path}[/white]",
            border_style="orange1",
        )
    )

    # ── 1. Detect or parse agents ─────────────────────────────────────────
    if agents:
        agent_list = [a.strip().lower() for a in agents.split(",")]
    else:
        console.print("\n[dim]Detecting installed AI agents...[/dim]")
        agent_list = _detect_agents(project_path)

    if not agent_list:
        console.print("[yellow]No agents detected — defaulting to Claude Code.[/yellow]")
        agent_list = ["claude"]

    # Show detected agents
    table = Table(title="Detected Agents", show_header=True, header_style="bold orange1",
                  border_style="orange1", title_style="orange1")
    table.add_column("Agent", style="bold orange1")
    table.add_column("Skills Dir", style="grey74")
    table.add_column("Status", style="grey58")
    for agent_id in agent_list:
        info = _AGENTS.get(agent_id, {"name": agent_id.title(), "skills_dir": ""})
        table.add_row(
            info.get("name", agent_id),
            info.get("skills_dir", "") or "[dim]—[/dim]",
            "[white]✓ detected[/white]",
        )
    console.print(table)

    # ── 2. Create .linear_canal_data/ ─────────────────────────────────────
    lc_dir = project_path / LC_DATA_DIR
    lc_dir.mkdir(exist_ok=True)

    config = {
        "project_path": str(project_path),
        "agents": agent_list,
        "profile": profile or "auto",
        "version": "0.2.0",
    }
    config_path = lc_dir / "config.json"
    config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
    console.print(f"  [green]✓[/green] Config           [dim]→ {config_path.relative_to(project_path)}[/dim]")

    # ── 3. Install skills for each detected agent ─────────────────────────
    _install_skills(project_path, agent_list, force)

    # ── 4. Install hooks + MCP config (Claude Code plugin) ────────────────
    if "claude" in agent_list or "kilo" in agent_list:
        _install_claude_plugin(project_path, force)

    # ── 5. Write MCP config for all detected agents ───────────────────────
    for agent_id in agent_list:
        _write_mcp_config(project_path, agent_id, force)

    # ── 6. Generate .contextignore ────────────────────────────────────────
    ignore_path = lc_dir / ".contextignore"
    if not ignore_path.exists() or force:
        ignore_path.write_text(_default_contextignore(), encoding="utf-8")
        console.print(f"  [green]✓[/green] Context ignore   [dim]→ {ignore_path.relative_to(project_path)}[/dim]")

    # ── 7. Run indexing pipeline ──────────────────────────────────────────
    if skip_index:
        console.print("\n[dim]Skipping indexing pipeline (--skip-index).[/dim]")
    else:
        console.print("\n[dim]Running zero-config indexing pipeline...[/dim]")
        _run_indexing_pipeline(project_path, console, profile=profile)

    # ── 8. Print summary ─────────────────────────────────────────────────
    console.print()
    _print_summary(agent_list, project_path)


# ── Agent detection ───────────────────────────────────────────────────────────

def _detect_agents(project_path: Path) -> list[str]:
    """Detect installed AI agents by checking project files and PATH."""
    detected: list[str] = []
    for agent_id, info in _AGENTS.items():
        for fname in info.get("detect_files", []):
            if (project_path / fname).exists():
                detected.append(agent_id)
                break
        else:
            for cmd in info.get("detect_commands", []):
                if shutil.which(cmd):
                    detected.append(agent_id)
                    break
    return detected


# ── Skill installation ────────────────────────────────────────────────────────

def _install_skills(
    project_path: Path,
    agent_list: list[str],
    force: bool,
) -> None:
    """Install lc-context + using-linear-canal SKILL.md for each agent."""
    cli_root = Path(__file__).parent.parent  # cli/src/linear_canal/
    src_skills = cli_root / "skills"

    installed_dirs: set[str] = set()

    for agent_id in agent_list:
        info = _AGENTS.get(agent_id, {})
        skills_dir = info.get("skills_dir")
        if not skills_dir or skills_dir in installed_dirs:
            continue
        installed_dirs.add(skills_dir)

        for skill_name in ("lc-context", "using-linear-canal"):
            src = src_skills / skill_name / "SKILL.md"
            dst = project_path / skills_dir / skill_name / "SKILL.md"
            if src.exists() and (not dst.exists() or force):
                dst.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(src, dst)
                console.print(
                    f"  [green]✓[/green] Skill            "
                    f"[dim]→ {skills_dir}/{skill_name}/SKILL.md[/dim]"
                )


# ── Claude Code plugin (hooks + settings) ────────────────────────────────────

def _install_claude_plugin(project_path: Path, force: bool) -> None:
    """Install hooks, settings.json, and .mcp.json for Claude Code."""
    claude_dir = project_path / ".claude"
    claude_dir.mkdir(exist_ok=True)

    # ── Hook scripts ──────────────────────────────────────────────────────
    cli_root = Path(__file__).parent.parent
    src_hooks = cli_root / "hooks"
    if not src_hooks.exists():
        src_hooks = Path(__file__).parents[4] / "hooks"
    if not src_hooks.exists():
        src_hooks = Path(__file__).parents[4] / ".claude-plugin" / "hooks"

    hooks_dir = claude_dir / "hooks"
    hooks_dir.mkdir(exist_ok=True)

    is_windows = sys.platform == "win32"

    if is_windows:
        hook_scripts    = ("session-start.ps1", "prompt-reminder.ps1", "read-interceptor.ps1", "semantic-inject.py")
        _cmd_session    = "pwsh -NoProfile -NonInteractive -ExecutionPolicy Bypass -File .claude/hooks/session-start.ps1"
        _cmd_reminder   = "pwsh -NoProfile -NonInteractive -ExecutionPolicy Bypass -File .claude/hooks/prompt-reminder.ps1"
        _cmd_read       = "pwsh -NoProfile -NonInteractive -ExecutionPolicy Bypass -File .claude/hooks/read-interceptor.ps1"
    else:
        hook_scripts    = ("session-start.py", "prompt-reminder.py", "read-interceptor.py", "semantic-inject.py")
        _cmd_session    = "python3 .claude/hooks/session-start.py"
        _cmd_reminder   = "python3 .claude/hooks/prompt-reminder.py"
        _cmd_read       = "python3 .claude/hooks/read-interceptor.py"

    for script_name in hook_scripts:
        src = src_hooks / script_name
        dst = hooks_dir / script_name
        if src.exists() and (not dst.exists() or force):
            shutil.copy2(src, dst)
            if not is_windows:
                dst.chmod(dst.stat().st_mode | 0o111)
            console.print(f"  [green]✓[/green] Hook script      [dim]→ .claude/hooks/{script_name}[/dim]")

    # ── Merge hook config into .claude/settings.json ──────────────────────
    settings_path = claude_dir / "settings.json"
    if settings_path.exists():
        try:
            settings = json.loads(settings_path.read_text(encoding="utf-8"))
        except json.JSONDecodeError:
            settings = {}
    else:
        settings = {}

    lc_hooks = {
        "UserPromptSubmit": [
            {
                "hooks": [
                    {
                        "type": "command",
                        "command": _cmd_reminder,
                    }
                ]
            }
        ],
        "PreToolUse": [
            {
                "matcher": "Glob",
                "hooks": [{"type": "command", "command": _cmd_session}],
            },
            {
                "matcher": "Grep",
                "hooks": [{"type": "command", "command": _cmd_session}],
            },
            {
                "matcher": "Read",
                "hooks": [{"type": "command", "command": _cmd_read}],
            },
        ],
    }

    existing_hooks = settings.get("hooks", {})
    for event, entries in lc_hooks.items():
        existing_hooks[event] = entries
    settings["hooks"] = existing_hooks
    settings_path.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
    console.print("  [green]✓[/green] Hook config      [dim]→ .claude/settings.json[/dim]")


# ── MCP config files ──────────────────────────────────────────────────────────

def _write_mcp_config(project_path: Path, agent_id: str, force: bool) -> None:
    """Write the MCP config file for an agent at its required system path."""
    info = _AGENTS.get(agent_id, {})
    mcp_rel = info.get("mcp_config")
    if not mcp_rel:
        return

    target = project_path / mcp_rel

    if agent_id in ("claude", "kilo"):
        # Merge into existing .mcp.json
        if target.exists():
            try:
                mcp_cfg = json.loads(target.read_text(encoding="utf-8"))
            except json.JSONDecodeError:
                mcp_cfg = {}
        else:
            mcp_cfg = {}

        servers = mcp_cfg.setdefault("mcpServers", {})
        servers["linear-canal"] = {
            "type": "stdio",
            "command": "linear-canal",
            "args": ["serve", "--project", "."],
        }
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text(json.dumps(mcp_cfg, indent=2) + "\n", encoding="utf-8")
        console.print(f"  [green]✓[/green] MCP config       [dim]→ {mcp_rel} (auto-start on session open)[/dim]")

    elif agent_id == "gemini":
        target.parent.mkdir(parents=True, exist_ok=True)
        if not target.exists() or force:
            config = {
                "mcpServers": [
                    {
                        "name": "linear-canal",
                        "command": "linear-canal",
                        "args": ["serve", "--project", "."],
                    }
                ]
            }
            target.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
            console.print(f"  [green]✓[/green] MCP config       [dim]→ {mcp_rel}[/dim]")

    elif agent_id == "cursor":
        target.parent.mkdir(parents=True, exist_ok=True)
        if not target.exists() or force:
            config = {
                "mcpServers": {
                    "linear-canal": {
                        "command": "linear-canal",
                        "args": ["serve", "--project", "."],
                    }
                }
            }
            target.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
            console.print(f"  [green]✓[/green] MCP config       [dim]→ {mcp_rel}[/dim]")

    elif agent_id == "opencode":
        target.parent.mkdir(parents=True, exist_ok=True)
        if not target.exists() or force:
            config = {
                "mcp": {
                    "command": "linear-canal",
                    "args": ["serve", "--project", "."],
                    "name": "linear-canal",
                }
            }
            target.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
            console.print(f"  [green]✓[/green] MCP config       [dim]→ {mcp_rel}[/dim]")


# ── Context ignore ────────────────────────────────────────────────────────────

def _default_contextignore() -> str:
    """Generate default .contextignore content."""
    return """# Linear Canal — Context Ignore
# Files matching these patterns will be excluded from indexing.
# Syntax is similar to .gitignore.

# Dependencies
node_modules/
vendor/
.venv/
venv/
__pycache__/

# Build output
dist/
build/
out/
.next/
target/

# IDE and editor files
.idea/
.vscode/
*.swp
*.swo

# Generated files
*.min.js
*.min.css
*.map
*.bundle.*

# Large binary files
*.png
*.jpg
*.jpeg
*.gif
*.ico
*.svg
*.woff
*.woff2
*.ttf
*.eot
*.mp4
*.mp3

# Lock files
package-lock.json
yarn.lock
pnpm-lock.yaml
Pipfile.lock
poetry.lock

# Test fixtures
*.snap
*.fixture.*
"""


# ── Indexing pipeline ─────────────────────────────────────────────────────────

def _run_indexing_pipeline(
    project_path: Path, con: Console, profile: Optional[str] = None
) -> None:
    """Run the zero-config pipeline (same as ``linear-canal setup``)."""
    from linear_canal.embed_config import resolve_embedding_config
    from linear_canal.pipeline import count_project_files, estimate_seconds

    project_id = project_path.name
    resolved = resolve_embedding_config(profile)

    con.print("  Scanning project size...", end="\r")
    file_count = count_project_files(project_path)
    est_secs = estimate_seconds(file_count)
    est_str = f"{est_secs}s" if est_secs < 60 else f"{est_secs // 60}m {est_secs % 60}s"

    con.print(
        f"  [grey58]Profile :[/grey58]  [grey74]{resolved.profile}[/grey74]  "
        f"[dim]({resolved.model_name}, {resolved.dimension}-dim)[/dim]"
    )
    con.print(
        f"  [grey58]Files   :[/grey58]  [orange1]{file_count:,}[/orange1]  [dim]indexable[/dim]  "
        f"[grey58]Est. time:[/grey58]  [orange1]~{est_str}[/orange1]"
    )
    con.print()

    with Progress(
        SpinnerColumn(),
        TextColumn("[progress.description]{task.description}"),
        console=con,
        transient=True,
    ) as progress:
        task = progress.add_task(
            f"Indexing '{project_id}' ({file_count:,} files, ~{est_str})...",
            total=None,
        )
        try:
            from linear_canal.pipeline import run_pipeline

            run_pipeline(
                target_dir=project_path,
                project_id=project_id,
                profile=profile,
                verbose=False,
            )
        except Exception as exc:
            con.print(f"[yellow][WARN] Indexing pipeline failed: {exc}[/yellow]")

        progress.update(task, completed=True)

    con.print(
        "[bold white]✓  Indexing complete[/bold white]  "
        "[grey58]— codebase is ready for your agents[/grey58]"
    )


# ── Summary ───────────────────────────────────────────────────────────────────

def _print_summary(agent_list: list[str], project_path: Path) -> None:
    """Print a short summary with next-step hints."""
    agents_str = ", ".join(
        _AGENTS.get(a, {}).get("name", a.title()) for a in agent_list
    )
    console.print(
        Panel(
            f"[bold white]Initialization Complete[/bold white]\n\n"
            f"  [grey58]Project :[/grey58]  [white]{project_path}[/white]\n"
            f"  [grey58]Agents  :[/grey58]  [orange1]{agents_str}[/orange1]\n\n"
            "  [grey58]Next steps:[/grey58]\n"
            "  [dim]·[/dim] Open the project in your AI agent — MCP will auto-connect\n"
            '  [dim]·[/dim] [dim]Try:[/dim] [italic]"Use lc_search to find authentication code"[/italic]\n'
            "  [dim]·[/dim] Run  [grey74]linear-canal watch[/grey74]  to keep context up-to-date\n"
            "  [dim]·[/dim] Run  [grey74]linear-canal db[/grey74]     to browse the index",
            title="[bold orange1] Done [/bold orange1]",
            border_style="orange1",
        )
    )
