"""
navig.commands.mount — Universal Drive Mounting (Windows NTFS junctions).

Commands
--------
  navig mount add     <label> <source> [target]  — register and create junction
  navig mount list                                — list all registered mounts
  navig mount remove  <label>                     — delete junction + registry entry
  navig mount verify                              — check all junctions are alive
  navig mount sync                                — verify + regenerate mount-drive.ps1

Registry
--------
  %USERPROFILE%\\.navig\\registry\\drives.json

Helper script
-------------
  %USERPROFILE%\\.navig\\scripts\\mount-drive.ps1
  Regenerated by ``navig mount sync``; run on login to restore all junctions.
"""

from __future__ import annotations

import json
import platform
import subprocess
import time
from pathlib import Path
from typing import Any

import typer

mount_app = typer.Typer(
    name="mount",
    help="Manage drive junctions (Windows NTFS) via a persistent registry",
    invoke_without_command=True,
    no_args_is_help=False,
)


@mount_app.callback()
def _mount_callback(ctx: typer.Context) -> None:
    if ctx.invoked_subcommand is None:
        import os as _os  # noqa: PLC0415

        if _os.environ.get("NAVIG_LAUNCHER", "fuzzy") == "legacy":
            print(ctx.get_help())
            raise typer.Exit()
        from navig.cli.launcher import smart_launch  # noqa: PLC0415

        smart_launch("mount", mount_app)


# ── Registry path helpers ────────────────────────────────────


def _registry_path() -> Path:
    try:
        from navig.platform.paths import navig_config_dir

        return navig_config_dir() / "registry" / "drives.json"
    except Exception:
        return Path.home() / ".navig" / "registry" / "drives.json"


def _scripts_dir() -> Path:
    try:
        from navig.platform.paths import navig_config_dir

        return navig_config_dir() / "scripts"
    except Exception:
        return Path.home() / ".navig" / "scripts"


def _helper_script() -> Path:
    return _scripts_dir() / "mount-drive.ps1"


# ── Registry I/O ─────────────────────────────────────────────


def _load_registry() -> dict[str, Any]:
    path = _registry_path()
    if not path.is_file():
        return {"drives": {}}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {"drives": {}}


def _save_registry(data: dict[str, Any]) -> None:
    path = _registry_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")


# ── Junction helpers ──────────────────────────────────────────


def _is_windows() -> bool:
    return platform.system() == "Windows"


def _create_junction(source: Path, target: Path) -> str | None:
    """
    Create an NTFS junction from *target* → *source*.

    Returns None on success, error string on failure.
    Falls back to symlink on non-Windows for testing.
    """
    if not source.exists():
        return f"Source does not exist: {source}"

    if target.exists() or target.is_symlink():
        if target.is_junction() if hasattr(target, "is_junction") else False:
            return None  # already exists
        return f"Target already exists: {target}"

    target.parent.mkdir(parents=True, exist_ok=True)

    if _is_windows():
        try:
            subprocess.run(
                ["cmd", "/c", "mklink", "/J", str(target), str(source)],
                check=True,
                capture_output=True,
                text=True,
            )
        except subprocess.CalledProcessError as exc:
            return exc.stderr.strip() or str(exc)
    else:
        try:
            target.symlink_to(source.resolve())
        except Exception as exc:
            return str(exc)
    return None


def _remove_junction(target: Path) -> str | None:
    """
    Remove an NTFS junction at *target*.

    Returns None on success, error string on failure.
    """
    if not target.exists() and not target.is_symlink():
        return f"Target does not exist: {target}"

    if _is_windows():
        try:
            subprocess.run(
                ["cmd", "/c", "rmdir", str(target)],
                check=True,
                capture_output=True,
                text=True,
            )
        except subprocess.CalledProcessError as exc:
            return exc.stderr.strip() or str(exc)
    else:
        try:
            if target.is_symlink():
                target.unlink()
            else:
                target.rmdir()
        except Exception as exc:
            return str(exc)
    return None


def _junction_alive(target: Path) -> bool:
    """Return True if junction/symlink at target is accessible."""
    try:
        return target.exists() and target.is_dir()
    except Exception:
        return False


# ── Commands ──────────────────────────────────────────────────


@mount_app.command("add")
def cmd_add(
    label: str = typer.Argument(..., help="Short name for this mount (e.g. 'projects')"),
    source: str = typer.Argument(..., help="Source directory to expose"),
    target: str | None = typer.Argument(
        None, help="Junction target path (default: %USERPROFILE%\\mnt\\<label>)"
    ),
    no_create: bool = typer.Option(
        False, "--no-create", help="Register only; don't create junction"
    ),
) -> None:
    """Register a drive junction and create it immediately."""
    source_path = Path(source).expanduser().resolve()
    if not source_path.exists():
        typer.secho(f"Source does not exist: {source_path}", fg=typer.colors.RED)
        raise typer.Exit(1)

    if target:
        target_path = Path(target).expanduser().resolve()
    else:
        mnt_root = Path.home() / "mnt"
        target_path = mnt_root / label

    data = _load_registry()
    drives = data.setdefault("drives", {})

    if label in drives:
        typer.secho(
            f"Label '{label}' already registered → {drives[label]['target']}",
            fg=typer.colors.YELLOW,
        )
        raise typer.Exit(1)

    entry: dict[str, Any] = {
        "label": label,
        "source": str(source_path),
        "target": str(target_path),
        "created_at": time.time(),
        "alive": False,
    }

    if not no_create:
        err = _create_junction(source_path, target_path)
        if err:
            typer.secho(f"Failed to create junction: {err}", fg=typer.colors.RED)
            raise typer.Exit(1)
        entry["alive"] = True
        typer.secho(f"✓ Junction created: {target_path} → {source_path}", fg=typer.colors.GREEN)
    else:
        typer.secho(f"✓ Registered (no junction created): {label}", fg=typer.colors.YELLOW)

    drives[label] = entry
    _save_registry(data)
    typer.echo(f"  Registry: {_registry_path()}")


@mount_app.command("list")
def cmd_list(
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """List all registered drive junctions."""
    data = _load_registry()
    drives = data.get("drives", {})

    if not drives:
        typer.echo("No drive junctions registered.")
        return

    if json_output:
        typer.echo(json.dumps(list(drives.values()), indent=2))
        return

    for label, entry in drives.items():
        target = Path(entry["target"])
        alive = _junction_alive(target)
        status_color = typer.colors.GREEN if alive else typer.colors.RED
        status = "✓ alive" if alive else "✗ dead"
        typer.secho(f"  [{status}] {label}", fg=status_color)
        typer.echo(f"     source : {entry['source']}")
        typer.echo(f"     target : {entry['target']}")


@mount_app.command("remove")
def cmd_remove(
    label: str = typer.Argument(..., help="Label of the junction to remove"),
    no_delete: bool = typer.Option(
        False, "--no-delete", help="Remove from registry only; don't delete junction"
    ),
    yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
) -> None:
    """Remove a registered drive junction."""
    data = _load_registry()
    drives = data.get("drives", {})

    if label not in drives:
        typer.secho(f"Label '{label}' not found in registry.", fg=typer.colors.RED)
        raise typer.Exit(1)

    entry = drives[label]
    target_path = Path(entry["target"])

    if not yes:
        typer.confirm(
            f"Remove junction '{label}' ({target_path})?",
            abort=True,
        )

    if not no_delete:
        err = _remove_junction(target_path)
        if err:
            typer.secho(f"Warning: could not remove junction: {err}", fg=typer.colors.YELLOW)
        else:
            typer.secho(f"✓ Junction removed: {target_path}", fg=typer.colors.GREEN)

    del drives[label]
    _save_registry(data)
    typer.echo(f"  Removed '{label}' from registry.")


@mount_app.command("verify")
def cmd_verify(
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """Verify all registered junctions are alive and update registry status."""
    data = _load_registry()
    drives = data.get("drives", {})

    if not drives:
        if json_output:
            typer.echo("[]")
        else:
            typer.echo("No drive junctions to verify.")
        return

    dead: list[str] = []
    results = []

    for label, entry in drives.items():
        target = Path(entry["target"])
        alive = _junction_alive(target)
        entry["alive"] = alive
        results.append({"label": label, "alive": alive, "target": entry["target"]})
        if not alive:
            dead.append(label)

    _save_registry(data)

    if json_output:
        typer.echo(json.dumps(results, indent=2))
        return

    for r in results:
        color = typer.colors.GREEN if r["alive"] else typer.colors.RED
        icon = "✓" if r["alive"] else "✗"
        typer.secho(f"  {icon} {r['label']}: {r['target']}", fg=color)

    if dead:
        typer.secho(f"\n{len(dead)} dead junction(s): {', '.join(dead)}", fg=typer.colors.YELLOW)
    else:
        typer.secho(f"\nAll {len(results)} junction(s) alive.", fg=typer.colors.GREEN)


@mount_app.command("sync")
def cmd_sync(
    dry_run: bool = typer.Option(False, "--dry-run", help="Preview without changes"),
) -> None:
    """
    Verify all junctions and regenerate mount-drive.ps1 helper script.

    Run mount-drive.ps1 on Windows login or after reboot to restore
    all junctions automatically.
    """
    # First verify
    typer.echo("Verifying junctions…")
    # Run verify inline (without invoking subprocess)
    data = _load_registry()
    drives = data.get("drives", {})
    dead: list[str] = []

    for label, entry in drives.items():
        target = Path(entry["target"])
        alive = _junction_alive(target)
        entry["alive"] = alive
        if not alive:
            dead.append(label)

    if not dry_run:
        _save_registry(data)

    for label in dead:
        typer.secho(f"  ✗ dead: {label}", fg=typer.colors.YELLOW)

    # Regenerate script
    script_path = _helper_script()
    script_content = _generate_ps1(drives)

    if dry_run:
        typer.echo(f"\nWould write: {script_path}")
        typer.echo("--- Script preview ---")
        typer.echo(script_content[:1000])
        return

    script_path.parent.mkdir(parents=True, exist_ok=True)
    script_path.write_text(script_content, encoding="utf-8")
    typer.secho(f"\n✓ Script written: {script_path}", fg=typer.colors.GREEN)

    if dead:
        typer.secho(
            f"\nWarning: {len(dead)} dead junction(s). Run 'navig mount verify' to see details.",
            fg=typer.colors.YELLOW,
        )
    else:
        typer.secho(f"All {len(drives)} junction(s) healthy.", fg=typer.colors.GREEN)


# ── Script generator ──────────────────────────────────────────


def _generate_ps1(drives: dict[str, Any]) -> str:
    lines = [
        "# Auto-generated by `navig mount sync`",
        "# Run on login to restore NTFS junctions.",
        "# DO NOT EDIT — regenerate with: navig mount sync",
        "",
        "$ErrorActionPreference = 'Continue'",
        "",
    ]
    if not drives:
        lines.append("# No junctions registered.")
        return "\n".join(lines) + "\n"

    for label, entry in drives.items():
        source = entry["source"].replace("\\", "\\\\")
        target = entry["target"].replace("\\", "\\\\")
        lines += [
            f"# --- {label} ---",
            f'if (-not (Test-Path "{target}")) {{',
            f'    Write-Host "Creating junction: {label}"',
            f'    cmd /c mklink /J "{target}" "{source}"',
            "} else {",
            f'    Write-Host "Junction already exists: {label}"',
            "}",
            "",
        ]

    lines += [
        'Write-Host "Done. Run: navig mount verify"',
    ]
    return "\n".join(lines) + "\n"


# ── Daemon bootstrap helper ───────────────────────────────────


def verify_on_startup() -> list[str]:
    """
    Called by the daemon on startup to verify all registered junctions.

    Returns a list of dead labels (empty if all healthy).
    """
    data = _load_registry()
    drives = data.get("drives", {})
    dead = []
    changed = False

    for label, entry in drives.items():
        alive = _junction_alive(Path(entry["target"]))
        if entry.get("alive") != alive:
            entry["alive"] = alive
            changed = True
        if not alive:
            dead.append(label)

    if changed:
        _save_registry(data)

    return dead
