"""
neurocli — AI-powered terminal coding agent
"""
import os
import sys
import click
from pathlib import Path
from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
from prompt_toolkit.styles import Style
from prompt_toolkit.formatted_text import HTML

from .config import load_config, save_config, CONFIG_DIR
from .session import create_session, get_messages, list_sessions, load_stats, track
from .memory import get_all_memory, remember, set_preference
from .tools import undo_last
from .agent import run_agent
from .tui import (
    console, print_banner, print_header, print_user_input,
    print_tool_start, print_tool_result, print_ai_response,
    print_error, print_success, print_info, spinner,
    print_stats, print_memory
)

HISTORY_FILE = CONFIG_DIR / "history"

PROMPT_STYLE = Style.from_dict({
    "prompt":        "ansicyan bold",
    "rprompt":       "ansibrightblack",
    "bottom-toolbar": "bg:#0a0a0a #444444",
})


# ─── CLI Group ────────────────────────────────────────────────────────────────

@click.group(invoke_without_command=True)
@click.option("--dir", "-d", "directory", default=".", help="Project directory")
@click.option("--session", "-s", "session_id", default=None, type=int, help="Resume session ID")
@click.option("--dry-run", is_flag=True, help="Preview actions without executing")
@click.option("--tier", default=None, type=click.Choice(["read-only", "write-no-exec", "full-auto"]), help="Permission tier")
@click.option("--model", default=None, type=click.Choice(["auto", "gemini-flash", "gemini-pro", "groq"]), help="Force model")
@click.option("--share", is_flag=True, help="Share session publicly")
@click.pass_context
def cli(ctx, directory, session_id, dry_run, tier, model, share):
    """⚡ neurocli — AI-powered terminal coding agent"""
    if ctx.invoked_subcommand is not None:
        return

    cfg = load_config()
    if dry_run:
        cfg["dry_run"] = True
    if tier:
        cfg["permission_tier"] = tier
    if model:
        cfg["default_model"] = model

    cwd = str(Path(directory).resolve())
    project = Path(cwd).name

    # Session
    if session_id:
        messages = get_messages(session_id)
        sid = session_id
    else:
        sid = create_session(project)
        messages = []
        track("sessions_total")

    print_banner(
        project=project,
        session_id=sid,
        model=cfg.get("default_model", "auto"),
        tier=cfg.get("permission_tier", "write-no-exec"),
    )

    print_header(
        project=project,
        session_id=sid,
        model=cfg.get("default_model", "auto"),
        tier=cfg.get("permission_tier", "write-no-exec"),
        dry_run=cfg.get("dry_run", False),
    )

    # Share
    broadcaster = None
    if share:
        from .share import create_broadcaster
        broadcaster = create_broadcaster(cfg, sid, project)
        if broadcaster:
            url = broadcaster.start()
            if url:
                print_success(f"Sharing session at: {url}")
            else:
                print_info("Share server unavailable — continuing locally")

    # Prompt session
    CONFIG_DIR.mkdir(exist_ok=True)
    prompt_session = PromptSession(
        history=FileHistory(str(HISTORY_FILE)),
        style=PROMPT_STYLE,
    )

    # ─── REPL ─────────────────────────────────────────────────────────────────
    while True:
        try:
            raw = prompt_session.prompt(
                HTML(f"<prompt> ❯ </prompt>"),
                rprompt=HTML(f"<rprompt>#{sid}  {project}  {cfg.get('permission_tier','')}</rprompt>"),
            )
        except (KeyboardInterrupt, EOFError):
            console.print(f"\n[grey50]bye.[/]")
            if broadcaster:
                broadcaster.stop()
            break

        user_input = raw.strip()
        if not user_input:
            continue

        # ─── Built-in commands ────────────────────────────────────────────────
        if user_input.lower() in ("/quit", "/exit", "/q"):
            console.print("[grey50]bye.[/]")
            break

        elif user_input.lower() == "/undo":
            ok, msg = undo_last(sid)
            (print_success if ok else print_error)(msg)
            continue

        elif user_input.lower() == "/memory":
            print_memory(get_all_memory())
            continue

        elif user_input.lower() == "/stats":
            print_stats(load_stats())
            continue

        elif user_input.lower() == "/dry":
            cfg["dry_run"] = not cfg.get("dry_run", False)
            state = "ON" if cfg["dry_run"] else "OFF"
            print_info(f"Dry-run: {state}")
            continue

        elif user_input.lower().startswith("/tier "):
            new_tier = user_input.split(" ", 1)[1].strip()
            if new_tier in ["read-only", "write-no-exec", "full-auto"]:
                cfg["permission_tier"] = new_tier
                print_info(f"Permission tier: {new_tier}")
            else:
                print_error("Invalid tier. Choose: read-only | write-no-exec | full-auto")
            continue

        elif user_input.lower() == "/commit":
            user_input = "Generate a conventional commit message for all current git changes and commit them."

        elif user_input.lower() == "/watch":
            _start_watch_mode(cfg, sid, project, cwd)
            continue

        elif user_input.lower().startswith("/bounty"):
            parts = user_input.split(" ", 1)
            target = parts[1].strip() if len(parts) > 1 else None
            _run_bounty(cfg, sid, project, cwd, target)
            continue

        elif user_input.lower().startswith("/remember "):
            val = user_input[10:].strip()
            remember("note", val, project)
            print_success(f"Remembered: {val}")
            continue

        elif user_input.lower().startswith("/pref "):
            parts = user_input[6:].strip().split("=", 1)
            if len(parts) == 2:
                set_preference(parts[0].strip(), parts[1].strip())
                print_success(f"Preference set: {parts[0].strip()} = {parts[1].strip()}")
            continue

        # ─── Agent call ───────────────────────────────────────────────────────
        print_user_input(user_input)

        if broadcaster:
            broadcaster.emit("user_message", {"text": user_input})

        with spinner("thinking...") as prog:
            task = prog.add_task("", total=None)

            def on_tool_start(name, args):
                prog.stop()
                print_tool_start(name, args)
                if broadcaster:
                    broadcaster.emit("tool_start", {"name": name, "args": str(args)})

            def on_tool_result(name, result):
                print_tool_result(name, result)
                if broadcaster:
                    broadcaster.emit("tool_result", {"name": name, "ok": result.get("ok")})

            result = run_agent(
                user_input=user_input,
                messages=messages,
                cfg=cfg,
                session_id=sid,
                project=project,
                cwd=cwd,
                dry_run=cfg.get("dry_run", False),
                on_text=None,
                on_tool_start=on_tool_start,
                on_tool_result=on_tool_result,
            )

        messages = result.get("messages", messages)
        text = result.get("text", "")
        model_used = result.get("model_used", "unknown")

        if text:
            print_ai_response(text, model_used)
            if broadcaster:
                broadcaster.emit("ai_response", {"text": text, "model": model_used})

        # auto-suggest commit after writes
        if "write_file" in result.get("used_tools", []):
            if cfg.get("permission_tier") == "full-auto" and not cfg.get("dry_run"):
                _maybe_auto_commit(cfg, sid, cwd)


# ─── Sub-commands ─────────────────────────────────────────────────────────────

@cli.command()
@click.option("--gemini-key", default=None, help="Set Gemini API key")
@click.option("--groq-key", default=None, help="Set Groq API key")
@click.option("--tier", default=None, type=click.Choice(["read-only", "write-no-exec", "full-auto"]))
@click.option("--model", default=None, type=click.Choice(["auto", "gemini-flash", "gemini-pro", "groq"]))
@click.option("--show", is_flag=True, help="Show current config")
def config(gemini_key, groq_key, tier, model, show):
    """Configure neurocli (API keys, permissions, model)"""
    from .config import get_config_path
    cfg = load_config()

    if show:
        console.print(f"[grey50]Config file: {get_config_path()}[/]\n")
        console.print("[bright_cyan]neurocli config:[/]")
        for k, v in cfg.items():
            if "key" in k and v:
                v = v[:8] + "..." + v[-4:]
            console.print(f"  [grey50]{k}:[/] [bright_green]{v}[/]")
        return

    if gemini_key:
        cfg["gemini_api_key"] = gemini_key
        print_success(f"Gemini API key saved → {get_config_path()}")
    if groq_key:
        cfg["groq_api_key"] = groq_key
        print_success(f"Groq API key saved → {get_config_path()}")
    if tier:
        cfg["permission_tier"] = tier
        print_success(f"Permission tier: {tier}")
    if model:
        cfg["default_model"] = model
        print_success(f"Default model: {model}")

    if not any([gemini_key, groq_key, tier, model]):
        console.print(f"[grey50]Config file: {get_config_path()}[/]")
        console.print("[grey50]Usage: neurocli config --gemini-key YOUR_KEY[/]")
        return

    save_config(cfg)
    console.print(f"[grey50]Saved to: {get_config_path()}[/]")


@cli.command()
@click.argument("prompt")
@click.option("--dir", "-d", "directory", default=".", help="Project directory")
@click.option("--dry-run", is_flag=True)
@click.option("--tier", default=None, type=click.Choice(["read-only", "write-no-exec", "full-auto"]))
def ask(prompt, directory, dry_run, tier):
    """Run a single one-shot AI prompt (non-interactive)"""
    cfg = load_config()
    if dry_run:
        cfg["dry_run"] = True
    if tier:
        cfg["permission_tier"] = tier

    cwd = str(Path(directory).resolve())
    project = Path(cwd).name
    sid = create_session(project)

    console.print(f"[bright_cyan]neurocli ask:[/] {prompt}\n")

    result = run_agent(
        user_input=prompt,
        messages=[],
        cfg=cfg,
        session_id=sid,
        project=project,
        cwd=cwd,
        dry_run=cfg.get("dry_run", False),
        on_tool_start=lambda n, a: print_tool_start(n, a),
        on_tool_result=lambda n, r: print_tool_result(n, r),
    )
    if result.get("text"):
        print_ai_response(result["text"], result.get("model_used", "unknown"))


@cli.command()
@click.option("--dir", "-d", "directory", default=".", help="Directory to scan")
@click.option("--target", "-t", default=None, help="Target specific file/folder")
def bounty(directory, target):
    """🎯 Bug bounty mode — AI scans your codebase for bugs & security issues"""
    cfg = load_config()
    cwd = str(Path(directory).resolve())
    project = Path(cwd).name
    sid = create_session(project)
    _run_bounty(cfg, sid, project, cwd, target)


@cli.command()
@click.option("--dir", "-d", "directory", default=".", help="Directory to watch")
def watch(directory):
    """👁 Watch mode — AI reviews files on every save"""
    cfg = load_config()
    cwd = str(Path(directory).resolve())
    project = Path(cwd).name
    sid = create_session(project)
    _start_watch_mode(cfg, sid, project, cwd)


@cli.command()
def stats():
    """📊 Show your coding stats dashboard"""
    print_stats(load_stats())


@cli.command()
def memory():
    """🧠 Show agent memory"""
    print_memory(get_all_memory())


@cli.command()
def sessions():
    """List recent sessions"""
    rows = list_sessions(limit=20)
    if not rows:
        print_info("No sessions yet.")
        return
    table_data = [(str(r["id"]), r["project"], r["created_at"][:16], r["updated_at"][:16]) for r in rows]
    from rich.table import Table
    from rich import box
    t = Table(box=box.SIMPLE, style="grey50")
    t.add_column("ID", style="bright_cyan")
    t.add_column("Project", style="bright_green")
    t.add_column("Created", style="grey50")
    t.add_column("Updated", style="grey50")
    for row in table_data:
        t.add_row(*row)
    console.print(t)


# ─── Helpers ──────────────────────────────────────────────────────────────────

def _run_bounty(cfg, sid, project, cwd, target=None):
    from .bounty import collect_files, build_bounty_prompt
    from .models import call_ai

    console.print(f"\n[bright_red]🎯 BUG BOUNTY MODE[/] [grey50]scanning {cwd}...[/]\n")
    files = collect_files(cwd)
    if not files:
        print_error("No code files found.")
        return

    console.print(f"[grey50]  scanning {len(files)} files...[/]")
    prompt = build_bounty_prompt(files, target)

    system = "You are an elite security engineer and code auditor. Be thorough, specific, and actionable."
    with spinner("analyzing codebase for bugs & vulnerabilities...") as prog:
        prog.add_task("", total=None)
        result = call_ai(
            [{"role": "user", "content": prompt}],
            system,
            cfg,
        )

    if result.get("text"):
        print_ai_response(result["text"], result.get("model_used", "unknown"))
        track("bugs_found", result["text"].count("🔴") + result["text"].count("🟡"))


def _start_watch_mode(cfg, sid, project, cwd):
    from .watch import start_watch, make_watch_callback

    console.print(f"\n[bright_yellow]◉ WATCH MODE[/] [grey50]watching {cwd} — Ctrl+C to stop[/]\n")

    def agent_fn(**kwargs):
        return run_agent(**kwargs)

    import types
    tui_mod = types.SimpleNamespace(console=console)
    cb = make_watch_callback(agent_fn, tui_mod)

    observer = start_watch(cwd, cb, cfg, sid, project, cwd)
    try:
        import time
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
        observer.join()
        console.print("\n[grey50]Watch mode stopped.[/]")


def _maybe_auto_commit(cfg, sid, cwd):
    """In full-auto mode, offer to auto-commit after writes."""
    from .context import get_git_context
    git = get_git_context()
    if not git.get("available") or not git.get("status"):
        return

    console.print(f"\n[grey50]  💡 Changes detected. Run /commit to auto-commit with AI message.[/]")


# ─── __init__ ─────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    cli()
