from __future__ import annotations

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

import typer

from iac.cli.formatting import build_table, emit_structured, ensure_output_format
from iac.runbook.discovery import parse_book_dirs, resolve_book, split_book_step
from iac.runbook.loader import load_book, merge_vars
from iac.runbook.runner import resolve_step
from iac.runbook.sensitive import SecretRedactor, redact_vars_map, sensitive_values
from iac.runbook.template import TemplateEngine

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") or os.environ.get("IAC_BOOKS_DIRS")
    return parse_book_dirs(env)


def _template() -> TemplateEngine:
    return TemplateEngine.default()


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

    payload = {
        "kind": "book",
        "path": str(bp),
        "meta": {
            "name": b.meta.name,
            "title": b.meta.title,
            "description": b.meta.description,
            "tags": b.meta.tags,
            "version": b.meta.version,
        },
        "vars": redact_vars_map(b, b.vars),
        "sensitive": b.sensitive,
        "requires": [
            {
                "name": r.name,
                "min_version": r.min_version,
                "check": r.check,
                "install_keys": sorted(r.install.keys()),
            }
            for r in b.requires
        ],
        "hooks": {
            k: {"command": v.command, "on_fail": v.on_fail} for k, v in b.hooks.items()
        },
        "steps": [
            {
                "name": s.name,
                "desc": s.desc,
                "mode": s.mode,
                "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,
                "requires": s.requires,
                "stdin_file": s.stdin_file,
                "stdout_file": s.stdout_file,
                "stderr_file": s.stderr_file,
                "file_mode": s.file_mode,
            }
            for s in b.steps
        ],
    }

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

    meta_table = build_table(
        ["FIELD", "VALUE"],
        [
            ["Path", str(bp)],
            ["Name", b.meta.name or ""],
            ["Title", b.meta.title or ""],
            ["Description", b.meta.description or ""],
            ["Tags", ", ".join(b.meta.tags)],
            ["Version", b.meta.version],
            ["Step Count", len(b.steps)],
        ],
    )
    vars_table = build_table(
        ["VAR", "VALUE"], [[k, v] for k, v in redact_vars_map(b, b.vars).items()]
    )
    sensitive_table = build_table(
        ["SENSITIVE VAR KEYS"],
        [[", ".join(b.sensitive)]],
    )
    steps_table = build_table(
        [
            "STEP",
            "MODE",
            "CWD",
            "ON_FAIL",
            "WHEN",
            "TIMEOUT",
            "RETRIES",
            "RETRY_DELAY",
            "REQUIRES",
            "FILE_MODE",
            "DESC",
            "TAGS",
        ],
        [
            [
                s.name,
                s.mode,
                s.cwd,
                s.on_fail,
                s.when,
                s.timeout,
                s.retries,
                s.retry_delay,
                ", ".join(s.requires),
                s.file_mode or "",
                s.desc,
                ", ".join(s.tags),
            ]
            for s in b.steps
        ],
    )
    reqs_table = build_table(
        ["REQUIREMENT", "MIN_VERSION", "CHECK", "INSTALL_TARGETS"],
        [
            [
                r.name,
                r.min_version or "",
                r.check or "",
                ", ".join(sorted(r.install.keys())),
            ]
            for r in b.requires
        ],
    )
    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("")
    typer.echo("Vars")
    typer.echo(vars_table.get_string() if b.vars else "(none)")
    typer.echo("")
    typer.echo("Sensitive")
    typer.echo(sensitive_table.get_string() if b.sensitive else "(none)")
    typer.echo("")
    typer.echo("Requires")
    typer.echo(reqs_table.get_string() if b.requires else "(none)")
    typer.echo("")
    typer.echo("Hooks")
    typer.echo(hooks_table.get_string() if b.hooks else "(none)")
    typer.echo("")
    typer.echo("Steps")
    typer.echo(steps_table.get_string())
    raise typer.Exit(0)


def _render_step(
    bp: Path,
    step_name: str,
    *,
    var: List[str],
    vars_env: Optional[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,
            "mode": step.mode,
            "cwd": step.cwd,
            "when": step.when,
            "on_fail": step.on_fail,
            "tags": step.tags,
            "argv": step.argv,
            "shell": step.shell,
            "timeout": step.timeout,
            "requires": step.requires,
            "retries": step.retries,
            "retry_delay": step.retry_delay,
            "stdin_file": step.stdin_file,
            "stdout_file": step.stdout_file,
            "stderr_file": step.stderr_file,
            "file_mode": step.file_mode,
        },
    }

    if resolved:
        vars_map = merge_vars(b.vars, vars_env=vars_env, vars_cli=var)
        template = _template()
        argv, display, cwd, env, tmo, when_expr = resolve_step(
            b, step, book_path=bp, vars_map=vars_map, template=template
        )
        redactor = SecretRedactor.from_values(sensitive_values(b, vars_map=vars_map))
        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,
        }

    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 ""],
            ["Mode", step.mode],
            ["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"],
            ["Tags", ", ".join(step.tags)],
            ["Requires", ", ".join(step.requires)],
            ["Stdin File", step.stdin_file or ""],
            ["Stdout File", step.stdout_file or ""],
            ["Stderr File", step.stderr_file or ""],
            ["File Mode", step.file_mode or ""],
        ],
    )
    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 <ref>` or `iac get step <book@step>`."""
    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>'.")],
    var: Annotated[
        List[str],
        typer.Option(
            "--var", help="Override vars (repeatable): --var k=v", show_default=False
        ),
    ] = [],
    vars_env: Annotated[
        Optional[str],
        typer.Option(
            "--vars-env",
            envvar="IAC_VARS",
            help="Vars from env (IAC_VARS): 'k=v,k2=v2' or whitespace-separated",
        ),
    ] = None,
    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,
        var=var,
        vars_env=vars_env,
        resolved=resolved,
        output_fmt=output_fmt,
    )
    raise typer.Exit(0)
