from __future__ import annotations

from pathlib import Path

import pytest

from iac.cli.formatting import build_table, ensure_output_format
from iac.runbook.deps import resolve_install_command
from iac.runbook.discovery import (
    book_id_for_path,
    list_books,
    parse_book_dirs,
    resolve_book,
    split_book_step,
)
from iac.runbook.expr import WhenContext, WhenEvaluator, default_ctx
from iac.runbook.loader import load_book, merge_vars, parse_vars_env, parse_vars_kv
from iac.runbook.model import parse_book
from iac.runbook.sensitive import (
    REDACTION,
    SecretRedactor,
    redact_vars_map,
    sensitive_values,
)
from iac.runbook.template import TemplateEngine


def test_formatting_helpers() -> None:
    assert ensure_output_format("json") == "json"
    with pytest.raises(Exception):
        ensure_output_format("nope")
    t = build_table(["A", "B"], [[None, True], ["x", False]])
    s = t.get_string()
    assert "true" in s and "false" in s and "x" in s


def test_discovery_and_resolution(tmp_path: Path) -> None:
    d1 = tmp_path / "books"
    d2 = tmp_path / "other"
    d1.mkdir()
    d2.mkdir()
    (d1 / "a.yaml").write_text(
        "version: 1\nsteps: [{name: s, shell: 'echo hi'}]\n", encoding="utf-8"
    )
    (d2 / "b").mkdir()
    (d2 / "b" / "book.yaml").write_text(
        "version: 1\nsteps: [{name: s, shell: 'echo hi'}]\n", encoding="utf-8"
    )

    dirs = parse_book_dirs(f"{d1}:{d2}:{d1}")
    books = list_books(dirs)
    assert len(books) >= 2
    assert book_id_for_path(d2 / "b" / "book.yaml") == "b"
    assert resolve_book("a", [d1]).name == "a.yaml"
    assert resolve_book("b", [d2]).name == "book.yaml"
    assert split_book_step("a@x") == ("a", "x")
    with pytest.raises(ValueError):
        split_book_step("broken")


def test_loader_vars_and_parse_helpers(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    assert parse_vars_env("a=1,b=2 c=3") == ["a=1", "b=2", "c=3"]
    assert parse_vars_kv(["a=1"]) == {"a": "1"}
    with pytest.raises(ValueError):
        parse_vars_kv(["bad"])

    monkeypatch.setenv("IAC_VAR_FOO", "envfoo")
    merged = merge_vars({"foo": "book", "x": "1"}, vars_env="x=2", vars_cli=["foo=cli"])
    assert merged["foo"] == "cli"
    assert merged["x"] == "2"

    p = tmp_path / "book.yaml"
    p.write_text(
        "version: 1\nname: b\nvars: {a: b}\nsteps: [{name: s, shell: 'echo hi'}]\n",
        encoding="utf-8",
    )
    b = load_book(p)
    assert b.meta.name == "b"


def test_model_validation_and_sensitive() -> None:
    data = {
        "version": 1,
        "name": "x",
        "vars": {"secret": "abc"},
        "sensitive": ["secret"],
        "steps": [
            {
                "name": "s",
                "shell": "echo {{ secret }}",
                "retries": 1,
                "retry_delay": 0.1,
            }
        ],
    }
    b = parse_book(data)
    vals = sensitive_values(b, vars_map={"secret": "abc"})
    red = SecretRedactor.from_values(vals)
    assert red.redact_text("abc") == REDACTION
    assert redact_vars_map(b, {"secret": "abc"})["secret"] == REDACTION

    with pytest.raises(ValueError):
        parse_book(
            {
                "version": 1,
                "env": {"X": "1"},
                "steps": [{"name": "s", "shell": "echo x"}],
            }
        )
    with pytest.raises(ValueError):
        parse_book(
            {"version": 1, "steps": [{"name": "s", "shell": "echo x", "retries": -1}]}
        )
    with pytest.raises(ValueError):
        parse_book(
            {
                "version": 1,
                "steps": [{"name": "s", "shell": "echo x", "retry_delay": -1}],
            }
        )
    with pytest.raises(ValueError):
        parse_book(
            {"version": 1, "steps": [{"name": "s", "shell": "echo x", "timeout": 0}]}
        )


def test_template_engine_and_when_eval() -> None:
    t = TemplateEngine.default()
    rendered = t.render_str(
        "{{ name | upper }}-${HOME}-{{ missing | default('x') }}-{{ name | replace('A','b') }}",
        vars_map={"name": "abc"},
        env_map={"HOME": "/tmp"},
    )
    assert rendered == "ABC-/tmp-x-abc"
    assert t.render_obj(
        ["{{ name }}", {"x": "${HOME}"}], vars_map={"name": "n"}, env_map={"HOME": "h"}
    ) == ["n", {"x": "h"}]

    wc = WhenContext(vars={"x": 1}, env={"CI": "1"}, ctx={"v": "abc"})
    ev = WhenEvaluator(max_len=64)
    assert ev.eval("vars['x'] == 1", wc) is True
    assert ev.eval("contains(ctx['v'], 'a')", wc) is True
    assert ev.eval("", wc) is True
    with pytest.raises(ValueError):
        ev.eval("x" * 200, wc)

    ctx = default_ctx()
    assert "os" in ctx and "platform" in ctx and "ci" in ctx


def test_resolve_install_command() -> None:
    target, cmd, tried = resolve_install_command({"darwin": "brew i"})
    assert isinstance(tried, list)
    # host dependent, but contract should hold
    if target:
        assert cmd
