"""
Documentation generation and serving utilities.
"""

from __future__ import annotations

import shutil
import subprocess
import webbrowser
from pathlib import Path
from typing import Optional

import click
from rich.console import Console

console = Console()

def _emit_docs_error(*, message: str, details: str | None = None) -> None:
    from relationalai.util.error import Diagnostic, Part, RichEmitter
    import sys

    parts: list[Part] = [details] if details else []
    diag = Diagnostic(name="docs", message=message, parts=parts, severity="error")
    sys.stderr.write(RichEmitter().emit(diag).rstrip() + "\n")


def find_docs_dir() -> Path:
    """Find the documentation source directory."""
    # Try multiple possible locations
    possible_paths = [
        Path("docs"),  # Root level
        Path(__file__).parent.parent.parent.parent / "docs",  # From this file
        Path.cwd() / "docs",  # Current working directory
    ]

    for path in possible_paths:
        if path.exists() and (path / "package.json").exists():
            return path.resolve()

    raise click.ClickException(
        "Documentation source directory not found. "
        "Expected 'docs/' directory with package.json"
    )


def has_nodejs() -> bool:
    """Check if Node.js is installed."""
    try:
        subprocess.run(
            ["node", "--version"],
            capture_output=True,
            text=True,
            check=True,
        )
        return True
    except (subprocess.CalledProcessError, FileNotFoundError):
        return False


def has_npm() -> bool:
    """Check if npm is installed."""
    try:
        subprocess.run(
            ["npm", "--version"],
            capture_output=True,
            text=True,
            check=True,
        )
        return True
    except (subprocess.CalledProcessError, FileNotFoundError):
        return False


def print_generation_success(output_path: Path) -> None:
    """
    Print success message after documentation generation.

    Args:
        output_path: Path to the generated documentation output directory
    """
    console.print("[green]✓ Documentation generated")
    console.print()
    console.print("[cyan]The documentation site is ready to host![/cyan]")
    console.print(f"[dim]Output location: {output_path}[/dim]")
    console.print()
    console.print("[yellow]You can serve it locally with:[/yellow]")
    console.print("  [bold]rai docs serve[/bold]")
    console.print()
    console.print("[yellow]Or host the dist folder directly:[/yellow]")
    console.print(
        "[dim]The dist folder contains a complete static website. You can copy it to any "
        "web server (Nginx, Apache, Flask, etc.), upload it to a CDN, or deploy it to "
        "static hosting services like GitHub Pages, Netlify, or Vercel. All files use "
        "relative paths, so it works regardless of where it's hosted.[/dim]"
    )


def generate_docs(
    output_dir: Optional[str] = None,
    docs_dir: Optional[Path] = None,
) -> Path:
    """
    Generate static documentation site.

    Args:
        output_dir: Optional output directory (defaults to docs/dist)
        docs_dir: Optional path to docs directory

    Returns:
        Path to the generated output directory
    """
    if docs_dir is None:
        docs_dir = find_docs_dir()

    # Check prerequisites
    if not has_nodejs():
        raise click.ClickException(
            "Node.js is not installed. "
            "Please install Node.js from https://nodejs.org/"
        )

    if not has_npm():
        raise click.ClickException(
            "npm is not installed. "
            "npm should come with Node.js installation."
        )

    # Check if package.json exists
    package_json = docs_dir / "package.json"
    if not package_json.exists():
        raise click.ClickException(
            f"package.json not found in {docs_dir}. "
            "This doesn't appear to be a valid SolidJS project."
        )

    # Install dependencies if node_modules doesn't exist
    node_modules = docs_dir / "node_modules"
    if not node_modules.exists():
        console.print("[blue]Installing dependencies...[/blue]")
        try:
            subprocess.run(
                ["npm", "install"],
                cwd=docs_dir,
                check=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            console.print("[green]✓ Dependencies installed[/green]")
        except subprocess.CalledProcessError as e:
            error_msg = e.stderr.decode() if e.stderr else "Unknown error"
            raise click.ClickException(
                f"Failed to install dependencies: {error_msg}"
            )

    # Build the documentation
    console.print("[blue]Building documentation site...[/blue]")
    try:
        subprocess.run(
            ["npm", "run", "build"],
            cwd=docs_dir,
            check=True,
            capture_output=True,
            text=True,
        )
        console.print("[green]✓ Build completed successfully[/green]")
    except subprocess.CalledProcessError as e:
        error_msg = e.stderr if e.stderr else e.stdout if e.stdout else "Unknown error"
        _emit_docs_error(message="Documentation build failed.", details=error_msg)
        raise click.ClickException("Build failed.")

    # Determine output directory
    build_output = docs_dir / "dist"
    if not build_output.exists():
        raise click.ClickException(
            "Build output directory not found. "
            "Build may have failed."
        )

    # Copy to custom output directory if specified
    if output_dir:
        output_path = Path(output_dir).resolve()
        if output_path.exists():
            console.print(f"[yellow]Output directory '{output_path}' exists. Cleaning...[/yellow]")
            shutil.rmtree(output_path)
        shutil.copytree(build_output, output_path)
        print_generation_success(output_path)
        return output_path

    print_generation_success(build_output)
    return build_output


def serve_docs(
    build_dir: str,
    host: str = "127.0.0.1",
    port: int = 8000,
    open_browser: bool = True,
) -> None:
    """
    Serve generated documentation site.

    Args:
        build_dir: Directory containing built documentation
        host: Host to bind to
        port: Port to serve on
        open_browser: Whether to open browser automatically
    """
    build_path = Path(build_dir).resolve()

    if not build_path.exists():
        raise click.ClickException(
            "Build directory does not exist. "
            "Run 'rai docs generate' first."
        )

    index_html = build_path / "index.html"
    if not index_html.exists():
        raise click.ClickException(
            "index.html not found. "
            "This doesn't appear to be a valid build directory. "
            "Run 'rai docs generate' first."
        )

    # Import FastAPI here to avoid requiring it if not using server
    try:
        from fastapi import FastAPI
        from fastapi.staticfiles import StaticFiles
        import uvicorn
    except ImportError:
        raise click.ClickException(
            "FastAPI and uvicorn are required for serving documentation. "
            "Install with: pip install fastapi uvicorn"
        )

    app = FastAPI(title="RAI Documentation")
    app.mount("/", StaticFiles(directory=str(build_path), html=True), name="static")

    url = f"http://{host}:{port}"
    console.print(f"[green]✓ Documentation server running at {url}[/green]")
    console.print("[yellow]Press Ctrl+C to stop the server[/yellow]")

    if open_browser:
        try:
            webbrowser.open(url)
        except Exception:
            console.print(f"[yellow]Could not open browser automatically. Visit {url}[/yellow]")

    try:
        uvicorn.run(app, host=host, port=port, log_level="warning")
    except KeyboardInterrupt:
        console.print("\n[yellow]Server stopped[/yellow]")
    except OSError as e:
        if "Address already in use" in str(e):
            raise click.ClickException(
                f"Port {port} is already in use. "
                "Try a different port with --port option."
            )
        raise


def dev_docs(
    docs_dir: Optional[Path] = None,
    host: str = "127.0.0.1",
    port: int = 5173,
    open_browser: bool = True,
) -> None:
    """
    Start development server with hot module replacement.

    This runs Vite's dev server, which provides:
    - Hot module replacement (HMR) - changes reflect instantly
    - Fast refresh - no need to rebuild on every change
    - Source maps for debugging

    Args:
        docs_dir: Optional path to docs directory
        host: Host to bind to
        port: Port to serve on (default: 5173, Vite's default)
        open_browser: Whether to open browser automatically
    """
    if docs_dir is None:
        docs_dir = find_docs_dir()

    # Check prerequisites
    if not has_nodejs():
        raise click.ClickException(
            "Node.js is not installed. "
            "Please install Node.js from https://nodejs.org/"
        )

    if not has_npm():
        raise click.ClickException(
            "npm is not installed. "
            "npm should come with Node.js installation."
        )

    # Check if package.json exists
    package_json = docs_dir / "package.json"
    if not package_json.exists():
        raise click.ClickException(
            f"package.json not found in {docs_dir}. "
            "This doesn't appear to be a valid SolidJS project."
        )

    # Install dependencies if node_modules doesn't exist
    node_modules = docs_dir / "node_modules"
    if not node_modules.exists():
        console.print("[blue]Installing dependencies...[/blue]")
        try:
            subprocess.run(
                ["npm", "install"],
                cwd=docs_dir,
                check=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            console.print("[green]✓ Dependencies installed[/green]")
        except subprocess.CalledProcessError as e:
            error_msg = e.stderr.decode() if e.stderr else "Unknown error"
            raise click.ClickException(
                f"Failed to install dependencies: {error_msg}"
            )

    # Start dev server
    url = f"http://{host}:{port}"
    console.print("[green]✓ Starting development server...[/green]")
    console.print(f"[blue]Server will be available at {url}[/blue]")
    console.print("[yellow]Press Ctrl+C to stop the server[/yellow]")
    console.print(
        "[dim]Note: Changes to files will automatically reload in the browser[/dim]"
    )

    if open_browser:
        # Open browser after a short delay to let server start
        import threading
        import time

        def open_browser_delayed():
            time.sleep(1.5)  # Give server time to start
            try:
                webbrowser.open(url)
            except Exception:
                console.print(f"[yellow]Could not open browser automatically. Visit {url}[/yellow]")

        threading.Thread(target=open_browser_delayed, daemon=True).start()

    try:
        # Run npm run dev with custom host/port
        # Vite accepts --host and --port flags directly
        subprocess.run(
            ["npm", "run", "dev", "--", "--host", host, "--port", str(port)],
            cwd=docs_dir,
            check=True,
        )
    except KeyboardInterrupt:
        console.print("\n[yellow]Development server stopped[/yellow]")
    except subprocess.CalledProcessError as e:
        error_msg = e.stderr.decode() if e.stderr else e.stdout.decode() if e.stdout else "Unknown error"
        _emit_docs_error(message="Documentation dev server failed.", details=error_msg)
        raise click.ClickException("Dev server failed.")
    except OSError as e:
        if "Address already in use" in str(e):
            raise click.ClickException(
                f"Port {port} is already in use. "
                "Try a different port with --port option."
            )
        raise


def cleanup_docs(docs_dir: Optional[Path] = None) -> None:
    """
    Clean up generated files and dependencies.

    Silently removes:
    - node_modules/ directory
    - dist/ directory (build output)

    Args:
        docs_dir: Optional path to docs directory
    """
    if docs_dir is None:
        docs_dir = find_docs_dir()

    removed_items = []

    # Remove node_modules
    node_modules = docs_dir / "node_modules"
    if node_modules.exists() and node_modules.is_dir():
        try:
            shutil.rmtree(node_modules)
            removed_items.append("node_modules")
        except Exception as e:
            raise click.ClickException(f"Failed to remove node_modules: {e}")

    # Remove dist
    dist = docs_dir / "dist"
    if dist.exists() and dist.is_dir():
        try:
            shutil.rmtree(dist)
            removed_items.append("dist")
        except Exception as e:
            raise click.ClickException(f"Failed to remove dist: {e}")

