import shutil
import subprocess
from pathlib import Path

import click

from nextpy_server.build import (
    build_css,
    ensure_deps,
    read_missing_json,
    run_build,
    search_registries,
    write_config,
)

TEMPLATE_DIR = Path(__file__).parent / "ui_template"
PROJECT_DIR = Path(".")
APP_DIR = Path("src/app")
BUILD_DIR = Path("build")


@click.group()
@click.option("--debug", is_flag=True, help="Enable debug logging.")
def cli(debug: bool):
    if debug:
        import os

        os.environ["N6K_DEBUG"] = "1"


def _scaffold_project(target: Path):
    """Scaffold project from template if it doesn't have a package.json."""
    if (target / "package.json").exists():
        click.echo("package.json already exists, skipping scaffolding.")
    else:
        shutil.copytree(TEMPLATE_DIR, target, dirs_exist_ok=True)
        click.echo(f"Created project scaffolding in {target}.")


def _init():
    """Scaffold project and install dependencies."""
    _scaffold_project(PROJECT_DIR)
    click.echo("Checking dependencies...")
    ensure_deps(PROJECT_DIR)
    click.echo("Dependencies ready.")


def _resolve_missing_components():
    """Check for missing components and offer to install them."""
    missing_exports = read_missing_json(BUILD_DIR)
    if not missing_exports:
        click.echo("Build reported missing components but missing.json not found.", err=True)
        raise SystemExit(1)

    click.echo(f"Missing exports: {sorted(missing_exports)}")
    click.echo("Searching registries...")

    resolved, unresolved = search_registries(missing_exports, PROJECT_DIR)

    if unresolved:
        click.echo(f"Could not find components for: {unresolved}", err=True)

    if not resolved:
        click.echo("No components to install.", err=True)
        raise SystemExit(1)

    cmd = f"bun run --cwd {PROJECT_DIR} shadcn add {' '.join(resolved)}"
    if click.confirm(f"Install {resolved}?\n  `{cmd}`"):
        result = subprocess.run(
            ["bun", "run", "--cwd", str(PROJECT_DIR), "shadcn", "add"] + resolved,
            text=True,
        )
        if result.returncode != 0:
            raise SystemExit(1)

        (BUILD_DIR / "missing.json").unlink(missing_ok=True)

        click.echo("Re-building to verify...")
        verify = run_build(PROJECT_DIR)
        if verify.returncode != 0:
            click.echo(f"Build still failing:\n{verify.stderr}", err=True)
            raise SystemExit(1)

        click.echo("Build succeeded.")


def _build_js():
    """Build JS via esbuild, resolving missing components if needed."""
    click.echo("Building JS...")
    build_result = run_build(PROJECT_DIR)

    if build_result.returncode == 0:
        click.echo("Build succeeded.")
        return

    if build_result.returncode not in (404, 148):
        click.echo(f"Build failed:\n{build_result.stderr}", err=True)
        raise SystemExit(1)

    _resolve_missing_components()


def _run_build_directory(*, discover_fn=None, config_fn=None):
    """Build pipeline (assumes init has been run)."""
    if discover_fn is None:
        from nextpy_server.discover import discover_app

        discover_fn = discover_app

    app_dir_path = APP_DIR.resolve()
    click.echo(f"Discovering app from {app_dir_path}...")
    app = discover_fn(app_dir_path)

    page_count = len(app.state.pages)
    click.echo(f"Discovered {page_count} page(s).")

    write_config(
        PROJECT_DIR,
        bindings=app.state.bindings,
        app_dir=str(app_dir_path),
        entry_points=app.state._n6k_entry_points,
        config_fn=config_fn,
    )
    click.echo("Wrote build/n6k.config.json.")

    click.echo("Building CSS...")
    build_css(PROJECT_DIR)
    click.echo("CSS built → build/styles.css")

    _build_js()
    click.echo("Done.")


def _run_build(*, discover_fn=None, config_fn=None):
    """Run the full build pipeline."""
    _run_build_directory(discover_fn=discover_fn, config_fn=config_fn)


@cli.command()
@click.argument("path", default=".")
def init(path: str):
    """Scaffold a new project from template and install dependencies.

    PATH is '.' for current directory or a name to create.
    """
    target = Path(path)
    _scaffold_project(target)
    click.echo("Checking dependencies...")
    ensure_deps(target)
    click.echo("Dependencies ready.")


@cli.command()
def build():
    """Discover app, write config, and build CSS/JS.

    Discovers from src/app/. Requires 'nextpy init' to have been run first.
    """
    _run_build()


@cli.command()
@click.option("--host", default="127.0.0.1", help="Bind host.")
@click.option("--port", default=8000, type=int, help="Bind port.")
@click.option("--log-level", default="info", type=click.Choice(["debug", "info", "warning", "error", "critical"]), help="Log level.")
def dev(host: str, port: int, log_level: str):
    """Init then start uvicorn with --reload and live browser refresh."""
    import os
    import uvicorn

    _init()

    os.environ["N6K_DEV"] = "1"
    click.echo("Starting dev server (live reload)...")

    app_dir = str(APP_DIR.resolve())
    os.environ["N6K_APP_DIR"] = app_dir
    uvicorn.run(
        "nextpy_server._dir_app:app",
        host=host,
        port=port,
        log_level=log_level,
        reload=True,
        reload_dirs=["src", app_dir],
        reload_includes=["*.py"],
        timeout_graceful_shutdown=1,
    )


@cli.command()
@click.option("--host", default="0.0.0.0", help="Bind host.")
@click.option("--port", default=8000, type=int, help="Bind port.")
@click.option("--workers", default=1, type=int, help="Number of worker processes.")
@click.option("--log-level", default="info", type=click.Choice(["debug", "info", "warning", "error", "critical"]), help="Log level.")
def serve(host: str, port: int, workers: int, log_level: str):
    """Start uvicorn for production (no build, no reload)."""
    import os
    import uvicorn

    click.echo("Starting server...")

    app_dir = str(APP_DIR.resolve())
    os.environ["N6K_APP_DIR"] = app_dir
    uvicorn.run(
        "nextpy_server._dir_app:app",
        host=host,
        port=port,
        workers=workers,
        log_level=log_level,
    )
