"""Image processing pipeline for imagemine."""

from __future__ import annotations

import pathlib
import shutil
import sys
import time
from contextlib import contextmanager
from typing import TYPE_CHECKING

from rich.markdown import Markdown
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
from rich.text import Text

from ._album import _add_to_photos_album, _random_photo_from_album
from ._db import insert_run, update_run
from ._describe import _get_description
from ._display import _print_summary
from ._generate import _run_generation
from ._image import resize_image
from ._styles import increment_style_count, least_used_style, random_style

if TYPE_CHECKING:
    import sqlite3
    from collections.abc import Callable, Generator

    from rich.console import Console


def _validate_input(image_path: str, err: Callable[[str], None]) -> str:
    """Validate image_path exists and is a file; return its resolved str path."""
    p = pathlib.Path(image_path)
    if not p.exists():
        err(f"Input file not found: {image_path}")
        sys.exit(1)
    if not p.is_file():
        err(f"Not a file: {image_path}")
        sys.exit(1)
    return str(p.resolve())


def _resolve_input(
    image_path: str | None,
    input_album: str | None,
    *,
    log: Callable[[str], None],
    err: Callable[[str], None],
) -> tuple[str, str | None, pathlib.Path | None]:
    """Return (resolved_input_path, album_photo_id_or_none, temp_export_dir)."""
    if image_path:
        return _validate_input(image_path, err), None, None
    if input_album:
        log(f"Picking random photo from album: {input_album}")
        try:
            path, photo_id, export_dir = _random_photo_from_album(input_album)
        except Exception as e:
            err(f"Failed to fetch photo from album {input_album!r}: {e}")
            sys.exit(1)
        log(f"Selected: {path} (id: {photo_id})")
        return path, photo_id, export_dir
    err("Provide an image path or configure INPUT_ALBUM")
    sys.exit(1)


@contextmanager
def _step_progress(
    console: Console,
    spinner: str,
    color: str,
) -> Generator[Callable[[str], None]]:
    with Progress(
        SpinnerColumn(spinner_name=spinner),
        TextColumn("{task.description}"),
        TimeElapsedColumn(),
        console=console,
        transient=True,
    ) as progress:
        task = progress.add_task(f"[bold {color}]Preparing...", total=None)

        def log(msg: str) -> None:
            progress.update(task, description=f"[bold {color}]{msg}")

        yield log


def run_pipeline(  # noqa: C901, PLR0912, PLR0913, PLR0915
    conn: sqlite3.Connection,
    console: Console,
    err: Callable[[str], None],
    t_start: float,
    output_dir: pathlib.Path,
    *,
    image_path: str | None,
    input_album: str | None,
    destination_album: str | None,
    desc_temp: float,
    img_temp: float,
    claude_model: str,
    gemini_model: str,
    anthropic_api_key: str,
    gemini_api_key: str,
    story: str | None,
    style: str | None,
    fresh: bool,
    session_svg: bool,
    desc_prompt_suffix: str | None = None,
    gen_prompt_suffix: str | None = None,
    aspect_ratio: str | None = None,
) -> None:
    """Run the full resize → describe → style → generate pipeline."""
    console.rule("[bold magenta]imagemine[/]")

    # ── Step 1: Resolve input ──────────────────────────────────────────────
    input_path, input_album_photo_id, input_export_dir = _resolve_input(
        image_path,
        input_album,
        log=lambda msg: console.print(f"  [dim]{msg}[/]"),
        err=err,
    )
    resized_path: pathlib.Path | None = None
    try:
        run_id = insert_run(conn, input_path)
        if input_album_photo_id:
            update_run(conn, run_id, input_album_photo_id=input_album_photo_id)

        # ── Step 2: Resize ────────────────────────────────────────────────
        try:
            image, resized_path = resize_image(input_path, output_dir)
        except Exception as e:
            err(f"Failed to open image: {e}")
            sys.exit(1)
        update_run(conn, run_id, resized_file_path=str(resized_path))

        # ── Step 3: Describe ──────────────────────────────────────────────
        console.rule("[dim]Describe[/]", style="dim")

        with _step_progress(console, "moon", "cyan") as log_describe:
            description = _get_description(
                conn,
                run_id,
                image,
                desc_temp=desc_temp,
                api_key=anthropic_api_key,
                model=claude_model,
                story=story,
                prompt_suffix=desc_prompt_suffix,
                log=log_describe,
                err=err,
            )

        console.print(
            Panel(
                Markdown(description),
                title=None,
                border_style="cyan",
                padding=(1, 2),
            ),
        )

        # ── Step 4: Style ─────────────────────────────────────────────────
        console.rule("[dim]Style[/]", style="dim")
        style_name: str | None
        if style:
            style_name = None
            content = Text()
            content.append(f"✦ {style}", style="bold magenta")
            console.print(
                Panel(content, title="[bold]Style (custom)[/]", border_style="magenta"),
            )
            description = f"{description}\n\nStyle: {style}"
            update_run(conn, run_id, style=style)
        else:
            style_name, style_desc = (
                least_used_style(conn) if fresh else random_style(conn)
            )
            style = f"{style_name}: {style_desc}" if style_name else None

            if style and style_name:
                content = Text()
                content.append(f"✦ {style_name}", style="bold magenta")
                if style_desc:
                    content.append(f"  —  {style_desc}", style="dim")
                console.print(
                    Panel(
                        content,
                        title=None,
                        border_style="magenta",
                    ),
                )
                description = f"{description}\n\nStyle: {style}"
                update_run(conn, run_id, style=style)
                increment_style_count(conn, style_name)

        if gen_prompt_suffix:
            description = f"{description}\n\n{gen_prompt_suffix}"

        # ── Step 5: Generate ──────────────────────────────────────────────
        console.rule("[dim]Generate[/]", style="dim")

        with _step_progress(console, "smiley", "yellow") as log_generate:
            output_path = _run_generation(
                conn,
                run_id,
                description,
                image,
                img_temp=img_temp,
                api_key=gemini_api_key,
                output_dir=output_dir,
                model=gemini_model,
                aspect_ratio=aspect_ratio or "4:3",
                log=log_generate,
                err=err,
            )

        # ── Step 6: Add to Photos album (optional) ────────────────────────
        if destination_album:
            try:
                _add_to_photos_album(output_path, destination_album, description)
            except Exception as e:
                err(f"Failed to add to Photos album {destination_album!r}: {e}")
            else:
                console.print(
                    f"  [dim]Added to Photos album:[/] [cyan]{destination_album}[/]",
                )

        # ── Summary ───────────────────────────────────────────────────────
        total_s = time.monotonic() - t_start
        _print_summary(
            console,
            conn,
            run_id=run_id,
            total_s=total_s,
            input_path=input_path,
            input_album=input_album,
            output_path=output_path,
        )

        # ── Session SVG (optional) ────────────────────────────────────────
        if session_svg:
            svg_path = output_dir / f"imagemine_{run_id}.svg"
            console.save_svg(str(svg_path), title="imagemine")
            console.print(f"  [dim]Session saved:[/] [cyan]{svg_path}[/]")
    finally:
        if resized_path is not None:
            resized_path.unlink(missing_ok=True)
        if input_export_dir is not None:
            shutil.rmtree(input_export_dir, ignore_errors=True)
