from __future__ import annotations

import json
from pathlib import Path

import yaml

from iac.cli import app
from conftest import runner, write_book


def _env(book_dir: Path) -> dict[str, str]:
    return {"IAC_BOOK_DIRS": str(book_dir)}


def test_list_get_init_commands(tmp_path: Path) -> None:
    books = tmp_path / "books"
    write_book(books / "sample.yaml", name="sample")

    r = runner.invoke(app, ["list", "books", "--output", "table"], env=_env(books))
    assert r.exit_code == 0
    assert "sample" in r.stdout

    r = runner.invoke(
        app, ["list", "books", "--verbose", "--output", "json"], env=_env(books)
    )
    assert r.exit_code == 0
    assert '"books"' in r.stdout

    r = runner.invoke(app, ["list", "steps", "--output", "table"], env=_env(books))
    assert r.exit_code == 0
    assert "sample@prepare" in r.stdout

    r = runner.invoke(
        app, ["get", "book", "sample", "--output", "table"], env=_env(books)
    )
    assert r.exit_code == 0
    assert "Sample Book" in r.stdout

    r = runner.invoke(
        app, ["get", "step", "sample@echo_secret", "--output", "json"], env=_env(books)
    )
    assert r.exit_code == 0
    assert '"step"' in r.stdout

    # init in isolated cwd so default books/ path is local
    with runner.isolated_filesystem(temp_dir=str(tmp_path)):
        r = runner.invoke(app, ["init", "book", "newbook"])
        assert r.exit_code == 0
        assert Path("books/newbook.yaml").exists()


def test_list_show_errors(tmp_path: Path) -> None:
    books = tmp_path / "books"
    books.mkdir(parents=True, exist_ok=True)
    write_book(books / "ok.yaml", name="ok")
    (books / "bad.yaml").write_text("version: 1\nsteps: [", encoding="utf-8")

    r = runner.invoke(
        app, ["list", "books", "--show-errors", "--output", "table"], env=_env(books)
    )
    assert r.exit_code == 0
    assert "ERROR" in r.stdout
    assert "bad" in r.stdout


def test_version_command() -> None:
    r = runner.invoke(app, ["version"])
    assert r.exit_code == 0
    assert "iac version:" in r.stdout


def test_get_step_resolved_warns_on_blank_cwd(tmp_path: Path) -> None:
    books = tmp_path / "books"
    books.mkdir(parents=True, exist_ok=True)
    data = {
        "kind": "pyplines.runbook.v1",
        "name": "blank_cwd",
        "steps": [
            {
                "name": "hello",
                "cwd": "",
                "sh": ["bash", "-lc", "echo hi"],
            }
        ],
    }
    (books / "blank_cwd.yaml").write_text(
        yaml.safe_dump(data, sort_keys=False), encoding="utf-8"
    )

    r = runner.invoke(
        app, ["get", "step", "blank_cwd@hello", "--output", "table"], env=_env(books)
    )
    assert r.exit_code == 0
    assert "Resolved" in r.stdout

    r = runner.invoke(
        app, ["get", "step", "blank_cwd@hello", "--output", "json"], env=_env(books)
    )
    assert r.exit_code == 0
    payload = json.loads(r.stdout)
    assert payload["resolved"]["cwd"] is None


def test_list_and_get_module_commands(tmp_path: Path) -> None:
    books = tmp_path / "books"
    books.mkdir(parents=True, exist_ok=True)
    write_book(books / "sample.yaml", name="sample")

    modules = tmp_path / "modules"
    modules.mkdir(parents=True, exist_ok=True)
    (modules / "ops.py").write_text(
        "def run(ctx):\n    '''Run test op.'''\n    return {'message': 'ok'}\n",
        encoding="utf-8",
    )

    env = {"IAC_BOOK_DIRS": str(books), "IAC_MODULE_DIRS": str(modules)}
    r = runner.invoke(app, ["list", "modules", "--output", "table"], env=env)
    assert r.exit_code == 0
    assert "ops" in r.stdout
    assert "# OF FUNCTIONS" in r.stdout

    r = runner.invoke(app, ["get", "module", "ops", "--output", "json"], env=env)
    assert r.exit_code == 0
    payload = json.loads(r.stdout)
    assert payload["module"] == "ops"
    assert any(f["name"] == "run" for f in payload["functions"])
