"""Tests for the ``orxaq run`` CLI subcommand."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from orxaq.experiences.cli import main

# ── Helpers ─────────────────────────────────────────────────────────────────


def _invoke(argv: list[str], tmp_path: Path) -> tuple[int, str]:
    """Call main() capturing stdout; returns (exit_code, stdout_text)."""
    import io
    from contextlib import redirect_stdout

    buf = io.StringIO()
    with redirect_stdout(buf):
        code = main(argv)
    return code, buf.getvalue()


# ── Basic invocation ─────────────────────────────────────────────────────────


def test_run_exits_zero(tmp_path: Path) -> None:
    code, _ = _invoke(
        ["run", "--institution", "TEST001", "--output", str(tmp_path / "out"), "--loans", "30"],
        tmp_path,
    )
    assert code == 0


def test_run_json_mode_returns_dict(tmp_path: Path) -> None:
    out_dir = str(tmp_path / "out")
    code, stdout = _invoke(
        ["--json", "run", "--institution", "TEST001", "--output", out_dir, "--loans", "30"],
        tmp_path,
    )
    assert code == 0
    parsed = json.loads(stdout)
    assert isinstance(parsed, dict)


def test_run_json_has_institution_id(tmp_path: Path) -> None:
    out_dir = str(tmp_path / "out")
    _, stdout = _invoke(
        ["--json", "run", "--institution", "JSON_BANK", "--output", out_dir, "--loans", "30"],
        tmp_path,
    )
    data = json.loads(stdout)
    assert data["institution_id"] == "JSON_BANK"


def test_run_json_has_period(tmp_path: Path) -> None:
    out_dir = str(tmp_path / "out")
    _, stdout = _invoke(
        [
            "--json",
            "run",
            "--institution",
            "PTEST",
            "--period",
            "2025Q3",
            "--output",
            out_dir,
            "--loans",
            "30",
        ],
        tmp_path,
    )
    data = json.loads(stdout)
    assert data["period"] == "2025Q3"


def test_run_json_has_output_dir(tmp_path: Path) -> None:
    out_dir = str(tmp_path / "out")
    _, stdout = _invoke(
        ["--json", "run", "--institution", "T", "--output", out_dir, "--loans", "30"],
        tmp_path,
    )
    data = json.loads(stdout)
    assert "output_dir" in data


def test_run_json_has_ecl_total(tmp_path: Path) -> None:
    out_dir = str(tmp_path / "out")
    _, stdout = _invoke(
        ["--json", "run", "--institution", "T", "--output", out_dir, "--loans", "30"],
        tmp_path,
    )
    data = json.loads(stdout)
    assert "ecl_total" in data
    assert isinstance(data["ecl_total"], float)


def test_run_json_has_files_list(tmp_path: Path) -> None:
    out_dir = str(tmp_path / "out")
    _, stdout = _invoke(
        ["--json", "run", "--institution", "T", "--output", out_dir, "--loans", "30"],
        tmp_path,
    )
    data = json.loads(stdout)
    assert isinstance(data["files"], list)
    assert len(data["files"]) > 0


def test_run_json_has_duration(tmp_path: Path) -> None:
    out_dir = str(tmp_path / "out")
    _, stdout = _invoke(
        ["--json", "run", "--institution", "T", "--output", out_dir, "--loans", "30"],
        tmp_path,
    )
    data = json.loads(stdout)
    assert "duration_s" in data
    assert data["duration_s"] > 0


def test_run_json_has_audit_entries(tmp_path: Path) -> None:
    out_dir = str(tmp_path / "out")
    _, stdout = _invoke(
        ["--json", "run", "--institution", "T", "--output", out_dir, "--loans", "30"],
        tmp_path,
    )
    data = json.loads(stdout)
    assert "audit_entries" in data
    assert isinstance(data["audit_entries"], int)


# ── File creation ────────────────────────────────────────────────────────────


def test_run_creates_output_directory(tmp_path: Path) -> None:
    out_dir = tmp_path / "new_dir"
    _invoke(
        ["run", "--institution", "T", "--output", str(out_dir), "--loans", "30"],
        tmp_path,
    )
    assert out_dir.is_dir()


def test_run_creates_manifest(tmp_path: Path) -> None:
    out_dir = tmp_path / "out"
    _invoke(
        ["run", "--institution", "T", "--output", str(out_dir), "--loans", "30"],
        tmp_path,
    )
    assert (out_dir / "manifest.json").exists()


# ── Human-readable output ────────────────────────────────────────────────────


def test_run_human_output_mentions_institution(tmp_path: Path) -> None:
    out_dir = str(tmp_path / "out")
    _, stdout = _invoke(
        ["run", "--institution", "READABLE_BANK", "--output", out_dir, "--loans", "30"],
        tmp_path,
    )
    assert "READABLE_BANK" in stdout


def test_run_human_output_mentions_output_dir(tmp_path: Path) -> None:
    out_dir = str(tmp_path / "readable_out")
    _, stdout = _invoke(
        ["run", "--institution", "T", "--output", out_dir, "--loans", "30"],
        tmp_path,
    )
    assert "readable_out" in stdout


# ── Error cases ──────────────────────────────────────────────────────────────


def test_run_no_institution_exits_nonzero(tmp_path: Path) -> None:
    with pytest.raises(SystemExit) as exc_info:
        main(["run", "--output", str(tmp_path / "out")])
    assert exc_info.value.code != 0


# ── Flags ────────────────────────────────────────────────────────────────────


def test_run_with_seed_flag_succeeds(tmp_path: Path) -> None:
    code, _ = _invoke(
        [
            "run",
            "--institution",
            "T",
            "--output",
            str(tmp_path / "out"),
            "--loans",
            "30",
            "--seed",
            "7",
        ],
        tmp_path,
    )
    assert code == 0


def test_run_with_no_fair_lending_flag(tmp_path: Path) -> None:
    code, _ = _invoke(
        [
            "run",
            "--institution",
            "T",
            "--output",
            str(tmp_path / "out"),
            "--loans",
            "30",
            "--no-fair-lending",
        ],
        tmp_path,
    )
    assert code == 0


def test_run_with_custom_loans_count(tmp_path: Path) -> None:
    code, _ = _invoke(
        ["run", "--institution", "T", "--output", str(tmp_path / "out"), "--loans", "20"],
        tmp_path,
    )
    assert code == 0
