#!/usr/bin/env python3
"""Plugin Manager CLI — scan, list, install, remove, and manage audio plugins.

Usage:
    plugin-mgr scan                   Full scan and registry sync
    plugin-mgr list                   List all registered plugins
    plugin-mgr list --format=lv2      List by format
    plugin-mgr list --category=reverb List by category
    plugin-mgr search <query>         Search plugins
    plugin-mgr info <uri|name>        Show plugin details
    plugin-mgr install-local <path>   Install from local path
    plugin-mgr install-nam <path>     Install local .nam model
    plugin-mgr download-nam <url>     Download .nam model
    plugin-mgr remove <uri|name>      Remove plugin (registry only)
    plugin-mgr remove --files <uri>   Remove plugin + delete files
    plugin-mgr enable <uri>           Enable a disabled plugin
    plugin-mgr disable <uri>          Disable a plugin
    plugin-mgr blacklist <uri>        Blacklist a plugin
    plugin-mgr unblacklist <uri>      Remove from blacklist
    plugin-mgr blacklist-list         Show all blacklist rules
    plugin-mgr blacklist-add <pat>    Add a user blacklist rule
    plugin-mgr nam-list               List installed NAM models
    plugin-mgr stats                  Show registry statistics
    plugin-mgr vacuum                 Compact/optimize the database
"""

from __future__ import annotations

import argparse
import os
import sys
import textwrap
from pathlib import Path

# Ensure the src directory is on the path
_project_root = Path(__file__).resolve().parent.parent
_src = _project_root / "src"
if str(_src) not in sys.path:
    sys.path.insert(0, str(_src))

from plugin import (
    PluginManager,
    PluginCategory,
    PluginFormat,
    PluginStatus,
    PluginBlacklist,
    BlacklistEntry,
    CATEGORY_LABELS,
    BUILTIN_BLACKLIST,
)


# ── CLI argument definitions ─────────────────────────────────────────────────

def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="plugin-mgr",
        description="RPi Mixer Plugin Manager — scan, register, and manage audio plugins",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=textwrap.dedent("""\
            Examples:
              plugin-mgr scan
              plugin-mgr list --format=lv2
              plugin-mgr list --category=reverb
              plugin-mgr search "tube amp"
              plugin-mgr install-nam ~/downloads/my-tone.nam
              plugin-mgr stats
        """),
    )
    sp = parser.add_subparsers(dest="command", help="Command to execute")

    # scan
    sp.add_parser("scan", help="Scan filesystem and sync registry")

    # list
    list_p = sp.add_parser("list", help="List registered plugins")
    list_p.add_argument("--format", "-f", help="Filter by format (lv2, vst3, ladspa, nam)")
    list_p.add_argument("--category", "-c", help="Filter by category (reverb, delay, etc.)")
    list_p.add_argument("--loadable", "-l", action="store_true", help="Only show loadable plugins")
    list_p.add_argument("--verbose", "-v", action="store_true", help="Show ports and metadata")

    # search
    search_p = sp.add_parser("search", help="Search plugins by name/description")
    search_p.add_argument("query", help="Search query")
    search_p.add_argument("--verbose", "-v", action="store_true", help="Verbose output")

    # info
    info_p = sp.add_parser("info", help="Show detailed plugin info")
    info_p.add_argument("plugin", help="Plugin URI or name")

    # install-local
    install_p = sp.add_parser("install-local", help="Install plugin from local path")
    install_p.add_argument("path", help="Path to plugin bundle directory or file")
    install_p.add_argument("--format", "-f", default="lv2", help="Plugin format (default: lv2)")

    # install-nam
    nam_inst_p = sp.add_parser("install-nam", help="Install local .nam model")
    nam_inst_p.add_argument("path", help="Path to .nam file")
    nam_inst_p.add_argument("--name", "-n", help="Name for the model")

    # download-nam
    nam_dl_p = sp.add_parser("download-nam", help="Download .nam model from URL")
    nam_dl_p.add_argument("url", help="Download URL")
    nam_dl_p.add_argument("--name", "-n", help="Name for the model")

    # nam-list
    sp.add_parser("nam-list", help="List installed NAM models")

    # remove
    remove_p = sp.add_parser("remove", help="Remove plugin from registry")
    remove_p.add_argument("plugin", help="Plugin URI or name")
    remove_p.add_argument("--files", action="store_true", help="Also delete plugin files from disk")

    # enable / disable
    enable_p = sp.add_parser("enable", help="Enable a disabled plugin")
    enable_p.add_argument("plugin", help="Plugin URI or name")
    disable_p = sp.add_parser("disable", help="Disable a plugin")
    disable_p.add_argument("plugin", help="Plugin URI or name")

    # blacklist
    bl_p = sp.add_parser("blacklist", help="Blacklist a plugin")
    bl_p.add_argument("plugin", help="Plugin URI or name")
    unbl_p = sp.add_parser("unblacklist", help="Remove plugin from blacklist")
    unbl_p.add_argument("plugin", help="Plugin URI or name")

    # blacklist-list
    bl_list = sp.add_parser("blacklist-list", help="Show all blacklist rules")
    bl_list.add_argument("--builtin", action="store_true", help="Only show built-in rules")
    bl_list.add_argument("--user", action="store_true", help="Only show user-defined rules")

    # blacklist-add
    bl_add = sp.add_parser("blacklist-add", help="Add a user blacklist rule")
    bl_add.add_argument("pattern", help="Glob pattern to match (e.g., '*BrokenPlugin*')")
    bl_add.add_argument("--field", default="name", help="Field to match: uri, name, path (default: name)")
    bl_add.add_argument("--reason", "-r", default="User-defined blacklist", help="Reason for blacklisting")
    bl_add.add_argument("--severity", default="block", choices=["block", "warn"], help="Severity (default: block)")

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

    # vacuum
    sp.add_parser("vacuum", help="Compact and optimize the database")

    return parser


# ── Output helpers ───────────────────────────────────────────────────────────

def _fmt_plugin_short(p) -> str:
    """One-line plugin summary."""
    status_icon = {
        PluginStatus.ACTIVE:      "[+]",
        PluginStatus.DISABLED:    "[-]",
        PluginStatus.BLACKLISTED: "[!]",
        PluginStatus.STALE:       "[?]",
        PluginStatus.ERROR:       "[E]",
        PluginStatus.REMOVED:     "   ",
    }.get(p.status, "[ ]")

    cats = ", ".join(c.value for c in p.categories[:3])
    if len(p.categories) > 3:
        cats += ", …"

    io_str = f"{p.audio_inputs}i/{p.audio_outputs}o"
    if p.midi_inputs or p.midi_outputs:
        io_str += f" MIDI:{p.midi_inputs}i/{p.midi_outputs}o"

    nam_tag = ""
    if p.nam_model_size:
        compat = "✓" if p.rpi4b_known_good else "⚠" if p.rpi4b_known_broken else "?"
        nam_tag = f" [NAM/{p.nam_model_size} {compat}]"

    cpu_str = f" CPU~{p.estimated_cpu_pct:.0f}%" if p.estimated_cpu_pct else ""
    rpi_tag = " [RPi4B✓]" if p.rpi4b_known_good else " [RPi4B⚠]" if p.rpi4b_known_broken else ""

    return (
        f"{status_icon} {p.meta.name:<30} "
        f"{p.meta.format.value:<6} {io_str:<12} "
        f"[{cats}]{nam_tag}{rpi_tag}{cpu_str}"
    )


def _fmt_plugin_verbose(p) -> str:
    """Detailed plugin info block."""
    lines = [
        f"Name:        {p.meta.name}",
        f"URI:         {p.meta.uri}",
        f"Format:      {p.meta.format.value}",
        f"Version:     {p.meta.version or '—'}",
        f"Author:      {p.meta.author or '—'}",
        f"License:     {p.meta.license or '—'}",
        f"Description: {p.meta.description or '—'}",
        f"Homepage:    {p.meta.homepage or '—'}",
        f"Project:     {p.meta.project or '—'}",
        f"Categories:  {', '.join(CATEGORY_LABELS.get(c, c.value) for c in p.categories)}",
        f"Status:      {p.status.value}",
        "",
        f"Audio I/O:   {p.audio_inputs} in, {p.audio_outputs} out",
        f"MIDI I/O:    {p.midi_inputs} in, {p.midi_outputs} out",
        f"Bundle:      {p.bundle_path or '—'}",
        f"Library:     {p.library_path or '—'}",
        f"Arch:        {p.meta.arch or '—'} (ARM optimized: {'yes' if p.meta.arm_optimized else 'no'})",
        "",
    ]
    if p.rpi4b_known_good:
        lines.append("RPi4B:       ✓ Verified working")
    elif p.rpi4b_known_broken:
        lines.append("RPi4B:       ⚠ Known broken")
    if p.estimated_cpu_pct:
        lines.append(f"Est. CPU:    ~{p.estimated_cpu_pct:.1f}%")
    if p.estimated_ram_mb:
        lines.append(f"Est. RAM:    ~{p.estimated_ram_mb:.0f} MB")

    if p.nam_model_path:
        lines.append(f"")
        lines.append(f"NAM Model:   {p.nam_model_path}")
        lines.append(f"Model Size:  {p.nam_model_size}")

    if p.control_ports:
        lines.append(f"")
        lines.append(f"Control Ports ({len(p.control_ports)}):")
        for port in p.control_ports[:20]:
            lines.append(f"  [{port.index:3d}] {port.name:<25} {port.direction:6} "
                        f"{port.minimum:7.2f} .. {port.maximum:7.2f} (def: {port.default:.2f})")
        if len(p.control_ports) > 20:
            lines.append(f"  … and {len(p.control_ports) - 20} more")

    if p.error_message:
        lines.append(f"")
        lines.append(f"Error:       {p.error_message}")

    if p.scanned_at:
        from datetime import datetime
        lines.append(f"")
        lines.append(f"Scanned:     {datetime.fromtimestamp(p.scanned_at).strftime('%Y-%m-%d %H:%M:%S')}")

    return "\n".join(lines)


# ── Resolve plugin by URI or name ────────────────────────────────────────────

def _resolve(mgr: PluginManager, identifier: str):
    """Resolve a plugin by URI or name (case-insensitive)."""
    plugin = mgr.registry.get(identifier)
    if plugin:
        return plugin
    plugin = mgr.registry.get_by_name(identifier)
    if plugin:
        return plugin
    # Try partial name match
    for p in mgr.registry.list_all():
        if identifier.lower() in p.meta.name.lower():
            return p
    return None


# ── Main ─────────────────────────────────────────────────────────────────────

def main():
    parser = build_parser()
    args = parser.parse_args()

    if args.command is None:
        parser.print_help()
        sys.exit(1)

    mgr = PluginManager()

    # ── scan ─────────────────────────────────────────────────────────────
    if args.command == "scan":
        print("Scanning filesystem for audio plugins…")
        result = mgr.sync()
        print(f"  New:     {result['inserted']}")
        print(f"  Updated: {result['updated']}")
        print(f"  Stale:   {result['stale']}")
        print(f"Done.")

    # ── list ─────────────────────────────────────────────────────────────
    elif args.command == "list":
        fmt = PluginFormat(args.format) if args.format else None
        cat = PluginCategory(args.category) if args.category else None

        if fmt:
            plugins = mgr.registry.list_by_format(fmt)
        elif cat:
            plugins = mgr.registry.list_by_category(cat)
        else:
            plugins = mgr.registry.list_all()

        if args.loadable:
            plugins = [p for p in plugins if p.is_loadable]

        if not plugins:
            print("No plugins found.")
            return

        for p in plugins:
            if args.verbose:
                print(_fmt_plugin_verbose(p))
                print("-" * 72)
            else:
                print(_fmt_plugin_short(p))
        print(f"\n{len(plugins)} plugin(s)")

    # ── search ───────────────────────────────────────────────────────────
    elif args.command == "search":
        plugins = mgr.registry.search(args.query)
        if not plugins:
            print(f"No plugins matching '{args.query}'.")
            return
        for p in plugins:
            if args.verbose:
                print(_fmt_plugin_verbose(p))
                print("-" * 72)
            else:
                print(_fmt_plugin_short(p))
        print(f"\n{len(plugins)} match(es)")

    # ── info ─────────────────────────────────────────────────────────────
    elif args.command == "info":
        plugin = _resolve(mgr, args.plugin)
        if plugin is None:
            print(f"Plugin not found: {args.plugin}")
            sys.exit(1)
        print(_fmt_plugin_verbose(plugin))

    # ── install-local ────────────────────────────────────────────────────
    elif args.command == "install-local":
        fmt = PluginFormat(args.format)
        result = mgr.install_local(args.path, fmt)
        if result:
            print(f"Installed: {result.meta.name} ({result.meta.uri})")
        else:
            print("Installation failed.")
            sys.exit(1)

    # ── install-nam ──────────────────────────────────────────────────────
    elif args.command == "install-nam":
        result = mgr.nam_install_local(args.path, args.name)
        if result:
            print(f"Installed NAM model: {result.meta.name} ({result.nam_model_size})")
            if result.rpi4b_known_broken:
                print("⚠  Warning: This model may cause xruns on RPi4B (standard size).")
        else:
            print("Installation failed.")
            sys.exit(1)

    # ── download-nam ─────────────────────────────────────────────────────
    elif args.command == "download-nam":
        print(f"Downloading NAM model from {args.url}…")
        result = mgr.nam_download(args.url, args.name)
        if result:
            print(f"Downloaded: {result.meta.name} ({result.nam_model_size})")
            if result.rpi4b_known_broken:
                print("⚠  Warning: This model may cause xruns on RPi4B (standard size).")
        else:
            print("Download failed.")
            sys.exit(1)

    # ── nam-list ─────────────────────────────────────────────────────────
    elif args.command == "nam-list":
        models = mgr.nam.list_models()
        if not models:
            print("No NAM models installed.")
            return
        for m in models:
            compat = "✓ RPi4B" if m.rpi4b_compatible else "⚠ xruns likely"
            print(f"  [{m.size_category:8}] {m.name:<30} {m.file_size_mb:6.1f} MB  {compat}")
        counts = mgr.nam.count_models()
        print(f"\n  nano:{counts['nano']} feather:{counts['feather']} "
              f"standard:{counts['standard']} custom:{counts['custom']}")

    # ── remove ───────────────────────────────────────────────────────────
    elif args.command == "remove":
        plugin = _resolve(mgr, args.plugin)
        if plugin is None:
            print(f"Plugin not found: {args.plugin}")
            sys.exit(1)
        if mgr.remove(plugin.meta.uri, delete_files=args.files):
            action = "Removed (files deleted)" if args.files else "Removed (registry only)"
            print(f"{action}: {plugin.meta.name}")
        else:
            print("Remove failed.")
            sys.exit(1)

    # ── enable / disable ─────────────────────────────────────────────────
    elif args.command == "enable":
        plugin = _resolve(mgr, args.plugin)
        if plugin is None:
            print(f"Plugin not found: {args.plugin}")
            sys.exit(1)
        if mgr.enable_plugin(plugin.meta.uri):
            print(f"Enabled: {plugin.meta.name}")
        else:
            print("Failed.")
            sys.exit(1)

    elif args.command == "disable":
        plugin = _resolve(mgr, args.plugin)
        if plugin is None:
            print(f"Plugin not found: {args.plugin}")
            sys.exit(1)
        if mgr.disable_plugin(plugin.meta.uri):
            print(f"Disabled: {plugin.meta.name}")
        else:
            print("Failed.")
            sys.exit(1)

    # ── blacklist / unblacklist ──────────────────────────────────────────
    elif args.command == "blacklist":
        plugin = _resolve(mgr, args.plugin)
        if plugin is None:
            print(f"Plugin not found: {args.plugin}")
            sys.exit(1)
        if mgr.blacklist_plugin(plugin.meta.uri):
            print(f"Blacklisted: {plugin.meta.name}")
        else:
            print("Failed.")
            sys.exit(1)

    elif args.command == "unblacklist":
        plugin = _resolve(mgr, args.plugin)
        if plugin is None:
            print(f"Plugin not found: {args.plugin}")
            sys.exit(1)
        if mgr.unblacklist_plugin(plugin.meta.uri):
            print(f"Removed from blacklist: {plugin.meta.name}")
        else:
            print("Failed.")
            sys.exit(1)

    # ── blacklist-list ───────────────────────────────────────────────────
    elif args.command == "blacklist-list":
        if args.builtin:
            entries = mgr.blacklist.list_builtin()
        elif args.user:
            entries = mgr.blacklist.list_user()
        else:
            entries = mgr.blacklist.list_all()

        for e in entries:
            tag = "blk" if e.severity == "block" else "warn"
            src = "builtin" if e.added_by == "builtin" else "user"
            print(f"  [{tag}] [{src}] {e.field}:{e.pattern}")
            if e.reason:
                print(f"         {e.reason}")
        print(f"\n{len(entries)} rule(s)")

    # ── blacklist-add ────────────────────────────────────────────────────
    elif args.command == "blacklist-add":
        import time
        entry = BlacklistEntry(
            pattern=args.pattern,
            field=args.field,
            reason=args.reason,
            severity=args.severity,
            added_by="user",
            added_at=time.time(),
        )
        mgr.blacklist.add_user_entry(entry)
        print(f"Added blacklist rule: {args.field}:{args.pattern} [{args.severity}]")

    # ── stats ────────────────────────────────────────────────────────────
    elif args.command == "stats":
        stats = mgr.registry.stats()
        print(f"Plugin Registry Statistics")
        print(f"  Total plugins:     {stats['total']}")
        print(f"  By format:")
        for fmt, count in stats["by_format"].items():
            print(f"    {fmt:<8} {count}")
        print(f"  Blacklisted:       {stats['blacklisted']}")
        print(f"  Stale:             {stats['stale']}")
        print(f"  RPi4B verified:    {stats['rpi4b_verified']}")
        print(f"  NAM models:        {stats['nam_models']}")

    # ── vacuum ───────────────────────────────────────────────────────────
    elif args.command == "vacuum":
        print("Compacting database…")
        mgr.registry.vacuum()
        print("Done.")


if __name__ == "__main__":
    main()
