from __future__ import annotations

import sys
from pathlib import Path
from typing import Literal, cast

import typer
from rich.console import Console
from rich.table import Table

from ocr_pipeline.config import find_config_file, settings
from ocr_pipeline.demo import run_demo
from ocr_pipeline.doctor import exit_code_for, run_checks
from ocr_pipeline.doctor import render as render_doctor
from ocr_pipeline.onboarding import (
    default_config_location,
    tesseract_install_hint,
    tesseract_path,
    write_default_config,
)
from ocr_pipeline.pipeline import OCRPipeline
from ocr_pipeline.setup_steps import run_steps, steps_for
from ocr_pipeline.utils.logging import get_logger, setup_logging
from ocr_pipeline.utils.migrate import migrate_notebook

app = typer.Typer(
    name="ocr-pipeline",
    help="OCR pipeline for screenshots -> RAG-ready Markdown",
    add_completion=False,
)
console = Console()
logger = get_logger(__name__)


def _ensure_configured(input_dir: Path | None) -> None:
    """Stop early with guidance instead of scanning a default directory."""
    if input_dir is not None or find_config_file() is not None:
        return
    console.print("[yellow]No config.yaml found and no --input-dir given.[/yellow]")
    console.print("Get started:")
    console.print("  [bold]ocr-pipeline init[/bold]        create a config for this machine")
    console.print("  [bold]ocr-pipeline demo[/bold]        try the pipeline on a sample screenshot")
    console.print("  [bold]ocr-pipeline run -i DIR[/bold]  point at a directory directly")
    raise typer.Exit(code=2)


@app.callback()
def callback(
    config: Path | None = typer.Option(None, "--config", "-c", help="Config file path"),
    log_level: str = typer.Option("INFO", "--log-level", "-l", help="Log level"),
    log_file: Path | None = typer.Option(None, "--log-file", help="Log file path"),
):
    """OCR Pipeline - Extract text from screenshots for RAG"""
    setup_logging(log_level, log_file)

    if config:
        if not config.exists():
            console.print(f"[red]Config file not found:[/red] {config}")
            raise typer.Exit(code=2)
        settings.update_from_yaml(config)


@app.command()
def init(
    local: bool = typer.Option(
        False, "--local", help="Write ./config.yaml instead of the per-user config directory"
    ),
    input_dir: Path | None = typer.Option(
        None, "--input-dir", "-i", help="Screenshots directory to put in the config"
    ),
    force: bool = typer.Option(False, "--force", help="Overwrite an existing config file"),
):
    """Create a minimal config for this machine."""
    target_dir = default_config_location(local)
    config_path = target_dir / "config.yaml"
    if config_path.exists() and not force:
        console.print(f"[yellow]Config already exists:[/yellow] {config_path}")
        console.print("Use --force to overwrite it.")
        raise typer.Exit(code=1)

    written = write_default_config(target_dir, input_dir)
    console.print(f"[green]Config written:[/green] {written}")

    tess = tesseract_path()
    if tess:
        console.print(f"[green]Tesseract found:[/green] {tess}")
    else:
        console.print("[yellow]Tesseract not found on PATH.[/yellow]")
        console.print(f"  Install it with: [bold]{tesseract_install_hint()}[/bold]")
        console.print("  or run: [bold]ocr-pipeline setup[/bold]")

    console.print("\nNext steps:")
    console.print(
        "  [bold]ocr-pipeline demo[/bold]  verify everything works on a sample screenshot"
    )
    console.print("  [bold]ocr-pipeline run[/bold]   process your screenshots")


@app.command()
def demo():
    """Run the pipeline on a bundled sample screenshot to verify the install."""
    console.print("[green]Running demo on the bundled sample screenshot...[/green]")
    result = run_demo()

    table = Table(title="Demo Results")
    table.add_column("Metric", style="cyan")
    table.add_column("Value", style="green")
    table.add_row("Successful", str(result.successful))
    table.add_row("Failed", str(result.failed))
    table.add_row("Chunks Created", str(result.chunks_created))
    console.print(table)

    if result.failed > 0:
        for err in result.errors:
            console.print(f"  [red]{err['path']}: {err['error']}[/red]")
        console.print("Something is wrong with the install; run [bold]ocr-pipeline setup[/bold].")
        raise typer.Exit(code=1)

    markdown_files = [path for path in result.output_files if path.suffix == ".md"]
    if markdown_files:
        first = markdown_files[0]
        console.print(f"\n[green]Demo output:[/green] {first}")
        excerpt = first.read_text(encoding="utf-8")[:1200]
        console.print(f"\n[dim]{excerpt}[/dim]")
    console.print("\n[green]Install verified.[/green] Run [bold]ocr-pipeline init[/bold] next.")


@app.command()
def run(
    input_dir: Path | None = typer.Option(None, "--input-dir", "-i", help="Input directory"),
    output_dir: Path | None = typer.Option(None, "--output-dir", "-o", help="Output directory"),
    workers: int | None = typer.Option(None, "--workers", "-w", help="Number of worker processes"),
    engine: str | None = typer.Option(
        None, "--engine", "-e", help="OCR engine (paddleocr/tesseract/auto)"
    ),
    exclude: list[str] = typer.Option([], "--exclude", help="Glob pattern to exclude; repeatable"),
    force: bool = typer.Option(
        False, "--force", help="Reprocess files already recorded as processed"
    ),
):
    """Run OCR pipeline on all screenshots"""
    _ensure_configured(input_dir)
    if input_dir:
        settings.input.paths = [str(input_dir)]
    if output_dir:
        settings.output.base_directory = str(output_dir)
    if workers:
        settings.processing.workers = workers
    if engine:
        if engine not in ("paddleocr", "tesseract", "auto"):
            console.print(
                f"[red]Unknown engine:[/red] {engine} (choose paddleocr, tesseract, or auto)"
            )
            raise typer.Exit(code=2)
        settings.ocr.engine = cast(Literal["paddleocr", "tesseract", "auto"], engine)
    if exclude:
        settings.input.exclude_patterns = exclude
    if force:
        settings.processing.skip_existing = False

    pipeline = OCRPipeline()
    result = pipeline.process_all()

    table = Table(title="Pipeline Results")
    table.add_column("Metric", style="cyan")
    table.add_column("Value", style="green")

    table.add_row("Total Images", str(result.total_images))
    table.add_row("Successful", str(result.successful))
    table.add_row("Skipped (already processed)", str(result.skipped))
    table.add_row("Failed", str(result.failed))
    table.add_row("Chunks Created", str(result.chunks_created))
    table.add_row("Output Files", str(len(result.output_files)))

    console.print(table)

    if result.errors:
        console.print("\n[red]Errors:[/red]")
        for err in result.errors:
            console.print(f"  {err['path']}: {err['error']}")

    if result.failed > 0:
        sys.exit(1)


@app.command()
def watch():
    """Watch for new screenshots and process automatically"""
    _ensure_configured(None)
    pipeline = OCRPipeline()
    console.print("[green]Starting watch mode...[/green]")
    console.print("Press Ctrl+C to stop")
    try:
        pipeline.watch()
    except KeyboardInterrupt:
        console.print("\n[yellow]Stopping watcher...[/yellow]")


@app.command()
def reprocess(
    notebook: Path = typer.Argument(..., help="Path to Jupyter notebook"),
):
    """Reprocess screenshots from a Jupyter notebook"""
    pipeline = OCRPipeline()
    console.print(f"[green]Migrating from notebook: {notebook}[/green]")
    result = migrate_notebook(notebook, pipeline)

    table = Table(title="Migration Results")
    table.add_column("Metric", style="cyan")
    table.add_column("Value", style="green")

    for key, value in result.items():
        if key != "errors":
            table.add_row(key.capitalize(), str(value))

    console.print(table)

    if result.get("errors"):
        console.print("\n[red]Errors:[/red]")
        for err in result["errors"]:
            console.print(f"  {err['path']}: {err['error']}")


@app.command()
def status():
    """Show pipeline status and statistics"""
    pipeline = OCRPipeline()
    stats = pipeline.get_stats()

    table = Table(title="Pipeline Status")
    table.add_column("Metric", style="cyan")
    table.add_column("Value", style="green")

    for key, value in stats.items():
        table.add_row(key.replace("_", " ").title(), str(value))

    console.print(table)


@app.command()
def stats(days: int = typer.Option(30, "--days", "-d", help="Number of days to analyze")):
    """Show processing statistics"""
    pipeline = OCRPipeline()
    stats = pipeline.get_stats(days)

    table = Table(title=f"Statistics (last {days} days)")
    table.add_column("Metric", style="cyan")
    table.add_column("Value", style="green")

    for key, value in stats.items():
        table.add_row(key.replace("_", " ").title(), str(value))

    console.print(table)


@app.command()
def setup(
    target: str = typer.Argument("basic", help="What to set up: basic or full"),
    yes: bool = typer.Option(False, "--yes", "-y", help="Run all steps without asking"),
    dry_run: bool = typer.Option(False, "--dry-run", help="Print the steps without running them"),
):
    """Install system/optional dependencies, executing each step with confirmation."""
    try:
        steps = steps_for(target)
    except ValueError as exc:
        console.print(f"[red]{exc}[/red]")
        raise typer.Exit(code=2) from exc
    if dry_run:
        console.print(f"[cyan]Setup steps for {target} (dry run):[/cyan]")
    returncode = run_steps(steps, assume_yes=yes, dry_run=dry_run, console=console)
    if returncode != 0:
        raise typer.Exit(code=returncode)
    if not dry_run:
        console.print("[green]Setup complete.[/green]")


@app.command()
def doctor():
    """Check the install: engines, ML models, config, database, output dir."""
    results = run_checks()
    render_doctor(results, console)
    code = exit_code_for(results)
    if code != 0:
        raise typer.Exit(code=code)


@app.command()
def config():
    """Show current configuration"""
    source = find_config_file()
    if source:
        console.print(f"[dim]Loaded from: {source}[/dim]")
    else:
        console.print("[dim]No config.yaml found; using built-in defaults.[/dim]")
    console.print("[cyan]Current Configuration:[/cyan]")
    console.print(settings.model_dump_json(indent=2))


def main():
    app()


if __name__ == "__main__":
    main()
