"""Unit tests for the level ladder (harness.level) — phase lvl5 semantics.

Pure function — no I/O. Proves the operator-decided rule (plan-phase-lvl5-lint-rung.md,
DECISIONS.md phase lvl5):

    level = max i such that rung_i == "pass" and every rung j < i is "pass" or "skip"

— a real FAIL blocks, an UNVERIFIED rung blocks exactly like a fail, an INTENTIONAL skip is
climbed past. Includes the mission's four worked examples verbatim, and the old N/A cases
(single-published-version recipe, non-backup-capable recipe) now climbing past their former
caps. Run cold with:  cc-ci-run -m pytest tests/unit/test_level.py -q
"""

from __future__ import annotations

import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
from harness import level as L  # noqa: E402


def _rungs(
    install="pass",
    upgrade="pass",
    backup_restore="pass",
    functional="pass",
    lint="pass",
):
    return {
        "install": install,
        "upgrade": upgrade,
        "backup_restore": backup_restore,
        "functional": functional,
        "lint": lint,
    }


# ---- the ladder: five essential rungs, top is L5 (lint) ----


def test_full_clean_climb_is_L5():
    assert L.compute_level(_rungs()) == 5


def test_ladder_is_five_rungs_lint_on_top():
    assert L.RUNGS == ("install", "upgrade", "backup_restore", "functional", "lint")
    assert "lint" in L.RUNG_LABEL[5]


# ---- mission worked examples (operator Q&A 2026-06-11, verbatim) ----


def test_mission_example_fail_blocks():
    # install ✔, upgrade ✘, backup ✔, functional ✔, lint ✔ → level 1 (fail blocks).
    assert L.compute_level(_rungs(upgrade="fail")) == 1


def test_mission_example_intentional_skip_climbs():
    # install ✔, upgrade ✔, backup skip (not capable), functional ✔, lint ✔ → level 5
    # (previously capped at 2 — the confusing part the operator removed).
    assert L.compute_level(_rungs(backup_restore="skip")) == 5


def test_mission_example_unverified_blocks():
    # install ✔, upgrade ✔, backup UNVER (harness error), functional ✔, lint ✔ → level 2
    # (we cannot claim what we didn't check).
    assert L.compute_level(_rungs(backup_restore="unver")) == 2


def test_mission_example_unverified_top_rung_not_earned():
    # all four ✔, lint unver (abra missing) → level 4.
    assert L.compute_level(_rungs(lint="unver")) == 4


# ---- blocking semantics ----


def test_install_fail_is_L0():
    assert L.compute_level(_rungs(install="fail")) == 0


def test_install_unver_is_L0():
    assert L.compute_level(_rungs(install="unver")) == 0


def test_higher_pass_never_rescues_a_fail():
    # everything above a failed rung is dead, however green.
    assert L.compute_level(_rungs(upgrade="fail", backup_restore="pass", functional="pass")) == 1


def test_lint_fail_blocks_at_4():
    assert L.compute_level(_rungs(lint="fail")) == 4


def test_unver_blocks_even_after_a_skip():
    # skip at L2 is climbed past, but the unver at L3 still blocks → level 1.
    assert L.compute_level(_rungs(upgrade="skip", backup_restore="unver")) == 1


# ---- intentional-skip climbing (the de-cap) ----


def test_single_version_recipe_climbs_past_upgrade_skip():
    # old rule: upgrade N/A capped at L1. New rule: skip is climbed past → full climb 5.
    assert L.compute_level(_rungs(upgrade="skip")) == 5


def test_stateless_recipe_climbs_past_backup_skip_to_lint():
    assert L.compute_level(_rungs(upgrade="skip", backup_restore="skip")) == 5


def test_skip_does_not_count_as_pass():
    # ALL skips → nothing passed → level 0 (a skip climbs, but never earns).
    assert (
        L.compute_level(
            _rungs(
                install="skip",
                upgrade="skip",
                backup_restore="skip",
                functional="skip",
                lint="skip",
            )
        )
        == 0
    )


def test_skip_then_pass_earns_the_higher_rung():
    # skip at L4, pass at L5 → level 5 (the skip below doesn't stop the climb).
    assert L.compute_level(_rungs(functional="skip")) == 5


def test_trailing_skip_keeps_last_pass():
    # passes up to L3, skips above → level stays 3 (skips never raise).
    assert L.compute_level(_rungs(functional="skip", lint="skip")) == 3


# ---- input validation ----


def test_invalid_status_raises():
    bad = _rungs()
    bad["functional"] = "na"  # the OLD vocabulary is no longer valid — every N/A is classified
    try:
        L.compute_level(bad)
    except ValueError:
        return
    raise AssertionError("expected ValueError on invalid rung status")


def test_missing_rung_raises():
    bad = _rungs()
    del bad["lint"]
    try:
        L.compute_level(bad)
    except ValueError:
        return
    raise AssertionError("expected ValueError on missing rung")


# ---- helpers: backup_restore_status ----


def test_backup_restore_status_pass():
    assert L.backup_restore_status("pass", "pass", True) == "pass"


def test_backup_restore_status_not_capable_is_intentional_skip():
    assert L.backup_restore_status("skip", "skip", False) == "skip"


def test_backup_restore_status_fail_on_either():
    assert L.backup_restore_status("pass", "fail", True) == "fail"
    assert L.backup_restore_status("fail", "pass", True) == "fail"


def test_backup_restore_partial_is_unverified():
    # backup-capable but restore didn't run cleanly (not pass, not fail) → cannot claim L3,
    # and the non-run is NOT intentional → unver (blocks the level above it).
    assert L.backup_restore_status("pass", "skip", True) == "unver"
    assert L.backup_restore_status(None, None, True) == "unver"


# ---- helpers: tier_to_rung ----


def test_tier_to_rung_mapping_defaults_unverified():
    assert L.tier_to_rung("pass") == "pass"
    assert L.tier_to_rung("fail") == "fail"
    # no intentionality information here — a non-run is unver; derive_rungs upgrades to "skip"
    # only on a declared/structural fact, never the other way.
    assert L.tier_to_rung("skip") == "unver"
    assert L.tier_to_rung(None) == "unver"
