from __future__ import annotations

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

import typer

from iac.cli.formatting import build_table, emit_structured, ensure_output_format
from iac.runbook.discovery import (
    list_modules,
    parse_book_dirs,
    parse_module_dirs,
    resolve_book,
    resolve_module_file,
    split_book_step,
)
from iac.runbook.loader import load_book, resolve_inputs
from iac.runbook.runner import resolve_step
from iac.runbook.sensitive import SecretRedactor, sensitive_values

app = typer.Typer(
    add_completion=False,
    no_args_is_help=True,
    help="Get details for a book or step (no execution)",
)


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)


def _render_book(bp: Path, *, output_fmt: str) -> None:
    b = load_book(bp)

    steps_payload = [
        {
            "name": s.name,
            "desc": s.desc,
            "executor": ("fn" if s.mode == "module" else "sh"),
            "sh": s.argv if s.mode == "argv" else None,
            "fn": s.module if s.mode == "module" else None,
            "vars": s.vars_map,
            "outputs": s.outputs_map,
            "cwd": s.cwd,
            "on_fail": s.on_fail,
            "when": s.when,
            "timeout": s.timeout,
            "retries": s.retries,
            "retry_delay": s.retry_delay,
            "tags": s.tags,
        }
        for s in b.steps
    ]

    payload = {
        "kind": "book",
        "path": str(bp),
        "meta": {
            "kind": b.meta.kind,
            "name": b.meta.name,
            "title": b.meta.title,
            "description": b.meta.description,
            "tags": b.meta.tags,
        },
        "inputs": b.inputs,
        "packages": {
            "os": [
                {
                    "name": r.name,
                    "min_version": r.min_version,
                    "check": r.check,
                    "install": r.install_cmd,
                }
                for r in b.requires
                if r.req_type == "os"
            ],
            "py": [
                {
                    "name": r.name,
                    "min_version": r.min_version,
                    "check": r.check,
                    "install": r.install_cmd,
                }
                for r in b.requires
                if r.req_type == "py"
            ],
        },
        "outputs": b.outputs,
        "hooks": {
            k: {"command": v.command, "on_fail": v.on_fail} for k, v in b.hooks.items()
        },
        "steps": steps_payload,
    }

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

    meta_table = build_table(
        ["FIELD", "VALUE"],
        [
            ["Path", str(bp)],
            ["Kind", b.meta.kind],
            ["Name", b.meta.name or ""],
            ["Title", b.meta.title or ""],
            ["Description", b.meta.description or ""],
            ["Tags", ", ".join(b.meta.tags)],
            ["Step Count", len(b.steps)],
        ],
    )
    inputs_table = build_table(
        ["INPUT", "TYPE", "DEFAULT", "DESCRIPTION"],
        [
            [
                key,
                spec.get("type", ""),
                spec.get("default", ""),
                spec.get("description", ""),
            ]
            for key, spec in b.inputs.items()
        ],
    )
    packages_table = build_table(
        ["PACKAGE", "TYPE", "MIN_VERSION", "CHECK", "INSTALL"],
        [
            [
                r.name,
                r.req_type,
                r.min_version or "",
                r.check or "",
                r.install_cmd or "",
            ]
            for r in b.requires
        ],
    )
    steps_table = build_table(
        ["STEP", "EXECUTOR", "WHEN", "TIMEOUT", "VARS", "OUTPUTS"],
        [
            [
                s.name,
                ("fn" if s.mode == "module" else "sh"),
                s.when or "",
                s.timeout if s.timeout is not None else "",
                ", ".join(sorted(s.vars_map.keys())),
                ", ".join(sorted(s.outputs_map.keys())),
            ]
            for s in b.steps
        ],
    )
    outputs_table = build_table(
        ["OUTPUT", "TYPE", "WHEN", "VALUE"],
        [
            [
                name,
                str(spec.get("type", "")),
                str(spec.get("when", "")),
                str(spec.get("value", "")),
            ]
            for name, spec in b.outputs.items()
        ],
    )
    hooks_table = build_table(
        ["HOOK", "ON_FAIL", "COMMAND"],
        [[k, h.on_fail, h.command] for k, h in b.hooks.items()],
    )

    typer.echo("Book")
    typer.echo(meta_table.get_string())
    typer.echo("\nInputs")
    typer.echo(inputs_table.get_string() if b.inputs else "(none)")
    typer.echo("\nPackages")
    typer.echo(packages_table.get_string() if b.requires else "(none)")
    typer.echo("\nOutputs")
    typer.echo(outputs_table.get_string() if b.outputs else "(none)")
    typer.echo("\nHooks")
    typer.echo(hooks_table.get_string() if b.hooks else "(none)")
    typer.echo("\nSteps")
    typer.echo(steps_table.get_string())
    raise typer.Exit(0)


def _render_step(
    bp: Path,
    step_name: str,
    *,
    inputs_cli: List[str],
    resolved: bool,
    output_fmt: str,
) -> None:
    b = load_book(bp)
    step = next((s for s in b.steps if s.name == step_name), None)
    if step is None:
        typer.echo(f"Unknown step '{step_name}' in book {bp}", err=True)
        raise typer.Exit(2)
    payload = {
        "kind": "step",
        "book_path": str(bp),
        "ref": f"{bp.name}@{step.name}",
        "step": {
            "name": step.name,
            "desc": step.desc,
            "executor": ("fn" if step.mode == "module" else "sh"),
            "cwd": step.cwd,
            "when": step.when,
            "on_fail": step.on_fail,
            "tags": step.tags,
            "sh": step.argv if step.mode == "argv" else None,
            "fn": step.module if step.mode == "module" else None,
            "vars": step.vars_map,
            "outputs": step.outputs_map,
            "timeout": step.timeout,
            "retries": step.retries,
            "retry_delay": step.retry_delay,
        },
    }

    if resolved:
        resolved_inputs = resolve_inputs(b.inputs, input_cli=inputs_cli)
        argv, display, cwd, env, tmo, when_expr = resolve_step(
            step,
            book_path=bp,
            vars_map=resolved_inputs,
            module_dirs=_module_dirs(),
        )
        redactor = SecretRedactor.from_values(
            sensitive_values(
                b, vars_map={k: str(v) for k, v in resolved_inputs.items()}
            )
        )
        payload["resolved"] = {
            "cmd": redactor.redact_text(display),
            "argv": [redactor.redact_text(a) for a in argv],
            "cwd": cwd,
            "env_keys": sorted(env.keys()),
            "timeout": tmo,
            "when": when_expr,
            "warnings": [],
        }

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

    step_table = build_table(
        ["FIELD", "VALUE"],
        [
            ["Ref", f"{bp.name}@{step.name}"],
            ["Book Path", str(bp)],
            ["Name", step.name],
            ["Description", step.desc or ""],
            ["Executor", ("fn" if step.mode == "module" else "sh")],
            ["SH", shlex.join(step.argv or []) if step.mode == "argv" else ""],
            ["Fn", step.module or ""],
            ["CWD", step.cwd or ""],
            ["On Fail", step.on_fail],
            ["When", step.when or ""],
            ["Timeout", f"{step.timeout}s" if step.timeout is not None else ""],
            ["Retries", step.retries],
            ["Retry Delay", f"{step.retry_delay}s"],
            ["Vars Keys", ", ".join(sorted(step.vars_map.keys()))],
            ["Output Keys", ", ".join(sorted(step.outputs_map.keys()))],
            ["Tags", ", ".join(step.tags)],
        ],
    )
    typer.echo("Step")
    typer.echo(step_table.get_string())

    if resolved and "resolved" in payload:
        r = payload["resolved"]
        typer.echo("")
        resolved_table = build_table(
            ["FIELD", "VALUE"],
            [
                ["Command", r.get("cmd", "")],
                ["CWD", r.get("cwd", "")],
                ["Timeout", r.get("timeout", "")],
                ["When", r.get("when", "")],
                [
                    "Argv",
                    (
                        shlex.join(r.get("argv", []))
                        if isinstance(r.get("argv"), list)
                        else ""
                    ),
                ],
                ["Env Keys", ", ".join(r.get("env_keys", []))],
            ],
        )
        typer.echo("Resolved")
        typer.echo(resolved_table.get_string())


@app.callback()
def get_default() -> None:
    """Use explicit subcommands: `iac get book|step|module ...`."""
    return None


@app.command("book")
def get_book_cmd(
    ref: Annotated[str, typer.Argument(help="Book ref (name or path).")],
    output: Annotated[
        str, typer.Option("--output", help="Output format: table|json|yaml")
    ] = "table",
) -> None:
    output_fmt = ensure_output_format(output)
    bp = resolve_book(ref, _book_dirs())
    _render_book(bp, output_fmt=output_fmt)
    raise typer.Exit(0)


@app.command("step")
def get_step_cmd(
    ref: Annotated[str, typer.Argument(help="Step ref in the form '<book>@<step>'.")],
    input: Annotated[
        List[str],
        typer.Option(
            "--input",
            help="Provide runbook inputs (repeatable): --input key=value",
            show_default=False,
        ),
    ] = [],
    resolved: Annotated[
        bool,
        typer.Option(
            "--resolved/--raw", help="Show resolved command/cwd/env for steps"
        ),
    ] = True,
    output: Annotated[
        str, typer.Option("--output", help="Output format: table|json|yaml")
    ] = "table",
) -> None:
    output_fmt = ensure_output_format(output)
    book_ref, step_name = split_book_step(ref)
    bp = resolve_book(book_ref, _book_dirs())
    _render_step(
        bp,
        step_name,
        inputs_cli=input,
        resolved=resolved,
        output_fmt=output_fmt,
    )
    raise typer.Exit(0)


@app.command("module")
def get_module_cmd(
    ref: Annotated[str, typer.Argument(help="Module name (for example ops.deploy).")],
    output: Annotated[
        str, typer.Option("--output", help="Output format: table|json|yaml")
    ] = "table",
) -> None:
    output_fmt = ensure_output_format(output)
    module_dirs = _module_dirs()
    mp = resolve_module_file(ref, module_dirs)
    modules = {m.module: m for m in list_modules(module_dirs)}
    info = modules.get(ref)
    if info is None:
        info = next(
            (
                m
                for m in list_modules(module_dirs)
                if Path(m.path).resolve() == mp.resolve()
            ),
            None,
        )
    if info is None:
        payload = {"kind": "module", "module": ref, "path": str(mp), "functions": []}
    else:
        payload = {
            "kind": "module",
            "module": info.module,
            "path": info.path,
            "error": info.error,
            "functions": [{"name": f.name, "doc": f.doc} for f in info.functions],
        }

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

    table = build_table(
        ["FIELD", "VALUE"],
        [
            ["Module", payload.get("module", ref)],
            ["Path", payload.get("path", "")],
            ["Error", payload.get("error", "")],
            [
                "Functions",
                ", ".join(
                    [f["name"] for f in payload.get("functions", [])]
                    if isinstance(payload.get("functions", []), list)
                    else []
                ),
            ],
        ],
    )
    typer.echo("Module")
    typer.echo(table.get_string())
