from __future__ import annotations

import os
from pathlib import Path
from typing import Annotated, List, Optional

import typer

from iac.runbook.discovery import (
    list_modules,
    parse_module_dirs,
    parse_book_dirs,
    list_books,
    resolve_book,
    book_id_for_path,
)
from iac.runbook.loader import load_book
from iac.cli.formatting import ensure_output_format, emit_structured, build_table

app = typer.Typer(
    add_completion=False,
    no_args_is_help=True,
    help="List available resources (no execution)",
)


def _truncate(value: object, *, limit: int) -> str:
    s = "" if value is None else str(value)
    if limit <= 3 or len(s) <= limit:
        return s
    return s[: limit - 3] + "..."


def _book_dirs() -> List[Path]:
    env = os.environ.get("IAC_BOOK_DIRS")
    return parse_book_dirs(env)


def _module_dirs() -> List[Path]:
    env = os.environ.get("IAC_MODULE_DIRS")
    return parse_module_dirs(env)


@app.command("books")
def list_books_cmd(
    names: Annotated[
        bool, typer.Option("--names", help="Print only filenames (not full paths)")
    ] = False,
    verbose: Annotated[
        bool,
        typer.Option("--verbose", "-v", help="Include extended diagnostics columns"),
    ] = False,
    show_errors: Annotated[
        bool,
        typer.Option(
            "--show-errors", help="Show only books that failed to load/validate"
        ),
    ] = False,
    output: Annotated[
        str, typer.Option("--output", help="Output format: table|json|yaml")
    ] = "table",
) -> None:
    """List available books discovered in configured book directories."""
    dirs = _book_dirs()
    books = list_books(dirs)
    output_fmt = ensure_output_format(output)

    if names:
        for p in books:
            typer.echo(p.name)
        raise typer.Exit(0)

    rows = []
    for p in books:
        row = {
            "id": book_id_for_path(p),
            "path": str(p),
            "steps": None,
            "requires": None,
            "hooks": None,
            "title": "",
            "description": "",
            "tags": [],
            "error": "",
        }
        try:
            b = load_book(p)
            row["steps"] = len(b.steps)
            row["requires"] = len(b.requires)
            row["hooks"] = len(b.hooks)
            row["kind"] = b.meta.kind
            row["title"] = b.meta.title or ""
            row["description"] = b.meta.description or ""
            row["tags"] = b.meta.tags
        except Exception as e:
            row["error"] = str(e)
        rows.append(row)

    if show_errors:
        rows = [r for r in rows if r["error"]]

    payload = {"book_dirs": [str(d) for d in dirs], "books": rows}

    if output_fmt in ("json", "yaml"):
        emit_structured(payload, output_fmt)
        raise typer.Exit(0)

    if verbose:
        table = build_table(
            [
                "REF",
                "KIND",
                "TITLE",
                "DESCRIPTION",
                "STEPS",
                "REQUIRES",
                "HOOKS",
                "TAGS",
                "PATH",
                "ERROR",
            ],
            [
                [
                    _truncate(r["id"], limit=24),
                    _truncate(r.get("kind", ""), limit=22),
                    _truncate(r["title"], limit=36),
                    _truncate(r["description"], limit=72),
                    r["steps"] if r["steps"] is not None else "-",
                    r["requires"] if r["requires"] is not None else "-",
                    r["hooks"] if r["hooks"] is not None else "-",
                    _truncate(
                        ", ".join(r["tags"]) if isinstance(r["tags"], list) else "",
                        limit=30,
                    ),
                    _truncate(r["path"], limit=72),
                    _truncate(r["error"], limit=72),
                ]
                for r in rows
            ],
        )
    elif show_errors:
        table = build_table(
            ["REF", "TITLE", "DESCRIPTION", "STEPS", "ERROR"],
            [
                [
                    _truncate(r["id"], limit=24),
                    _truncate(r["title"], limit=36),
                    _truncate(r["description"], limit=50),
                    r["steps"] if r["steps"] is not None else "-",
                    _truncate(r["error"], limit=72),
                ]
                for r in rows
            ],
        )
    else:
        table = build_table(
            ["REF", "TITLE", "STEPS"],
            [
                [
                    _truncate(r["id"], limit=24),
                    _truncate(r["title"], limit=40),
                    r["steps"] if r["steps"] is not None else "-",
                ]
                for r in rows
            ],
        )
    typer.echo(f"Books ({len(rows)})")
    typer.echo(table.get_string())


@app.command("steps")
def list_steps_cmd(
    book: Annotated[
        Optional[str],
        typer.Option("--book", help="Limit to a specific book (name or path)"),
    ] = None,
    names: Annotated[
        bool, typer.Option("--names", help="Print only step refs (one per line)")
    ] = False,
    verbose: Annotated[
        bool,
        typer.Option(
            "--verbose", "-v", help="Include extended step diagnostics columns"
        ),
    ] = False,
    output: Annotated[
        str, typer.Option("--output", help="Output format: table|json|yaml")
    ] = "table",
) -> None:
    """
    List steps across all discovered books, or within one book.

    Step refs are emitted as: <book-file>@<step>
      e.g. deploy.yaml@validate
    """
    dirs = _book_dirs()
    output_fmt = ensure_output_format(output)

    rows: List[dict] = []

    if book:
        bp = resolve_book(book, dirs)
        b = load_book(bp)
        for s in b.steps:
            rows.append(
                {
                    "ref": f"{book_id_for_path(bp)}@{s.name}",
                    "book": str(bp),
                    "step": s.name,
                    "mode": s.mode,
                    "cwd": s.cwd,
                    "desc": s.desc,
                    "on_fail": s.on_fail,
                    "when": s.when,
                    "timeout": s.timeout,
                    "retries": s.retries,
                    "executor": ("fn" if s.mode == "module" else "sh"),
                }
            )
    else:
        for bp in list_books(dirs):
            try:
                b = load_book(bp)
            except Exception:
                # Skip unreadable books in list mode; users can `iac get <book>` to see the error.
                continue
            for s in b.steps:
                rows.append(
                    {
                        "ref": f"{book_id_for_path(bp)}@{s.name}",
                        "book": str(bp),
                        "step": s.name,
                        "mode": s.mode,
                        "cwd": s.cwd,
                        "desc": s.desc,
                        "on_fail": s.on_fail,
                        "when": s.when,
                        "timeout": s.timeout,
                        "retries": s.retries,
                        "executor": ("fn" if s.mode == "module" else "sh"),
                    }
                )

    if names:
        for r in rows:
            typer.echo(r["ref"])
        raise typer.Exit(0)

    payload = {"steps": rows}
    if output_fmt in ("json", "yaml"):
        emit_structured(payload, output_fmt)
        raise typer.Exit(0)

    if verbose:
        table = build_table(
            [
                "STEP",
                "MODE",
                "ON_FAIL",
                "WHEN",
                "TIMEOUT",
                "RETRIES",
                "EXECUTOR",
                "CWD",
                "DESC",
                "BOOK",
            ],
            [
                [
                    r["ref"],
                    r["mode"],
                    r["on_fail"],
                    r["when"],
                    r.get("timeout"),
                    r.get("retries"),
                    r.get("executor"),
                    r["cwd"],
                    r["desc"],
                    r["book"],
                ]
                for r in rows
            ],
        )
    else:
        table = build_table(
            ["STEP", "WHEN", "DESCRIPTION", "BOOK"],
            [
                [
                    r["ref"],
                    r["when"] or "",
                    (
                        (r["desc"][:117] + "...")
                        if isinstance(r["desc"], str) and len(r["desc"]) > 120
                        else r["desc"]
                    ),
                    r["book"],
                ]
                for r in rows
            ],
        )
    typer.echo(f"Steps ({len(rows)})")
    typer.echo(table.get_string())


@app.command("modules")
def list_modules_cmd(
    names: Annotated[
        bool, typer.Option("--names", help="Print only module names")
    ] = False,
    output: Annotated[
        str, typer.Option("--output", help="Output format: table|json|yaml")
    ] = "table",
) -> None:
    module_dirs = _module_dirs()
    output_fmt = ensure_output_format(output)
    mods = list_modules(module_dirs)
    rows = [
        {
            "module": m.module,
            "path": m.path,
            "functions": [f.name for f in m.functions],
            "function_count": len(m.functions),
            "error": m.error,
        }
        for m in mods
    ]

    if names:
        for r in rows:
            typer.echo(r["module"])
        raise typer.Exit(0)

    payload = {"module_dirs": [str(d) for d in module_dirs], "modules": rows}
    if output_fmt in ("json", "yaml"):
        emit_structured(payload, output_fmt)
        raise typer.Exit(0)

    table = build_table(
        ["MODULE", "# OF FUNCTIONS", "PATH", "ERROR"],
        [[r["module"], r["function_count"], r["path"], r["error"]] for r in rows],
    )
    typer.echo(f"Modules ({len(rows)})")
    typer.echo(table.get_string())
