import os
import sys
import yaml

PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TABLES_DIR = os.path.join(PROJECT_ROOT, "docs", "tables")


def load_yaml(filename):
    with open(os.path.join(TABLES_DIR, filename)) as handle:
        return yaml.safe_load(handle)


def find_transition(data, from_state, event, to_state=None):
    for t in data["transitions"]:
        if t.get("from") == from_state and t.get("event") == event:
            if to_state is None or t.get("to") == to_state:
                return t
    return None


def find_row(data, condition_text):
    for row in data["rows"]:
        if row.get("Condition") == condition_text:
            return row
    return None


class TestNodeLifecycleP1Fixes:
    def test_conflict_recorded_from_ready_exists(self):
        data = load_yaml("node-lifecycle.yaml")
        t = find_transition(data, "READY", "CONFLICT_RECORDED", "CONFLICTED")
        assert t is not None, "CONFLICT_RECORDED from READY to CONFLICTED must exist"
        assert "guard" in t, "Transition must have a guard"

    def test_validation_invalidated_from_validating_exists(self):
        data = load_yaml("node-lifecycle.yaml")
        t = find_transition(data, "VALIDATING", "VALIDATION_INVALIDATED", "BLOCKED")
        assert t is not None, "VALIDATION_INVALIDATED from VALIDATING to BLOCKED must exist"
        assert "guard" in t, "Transition must have a guard"

    def test_policy_completeness_blocking_conflict_covers_ready(self):
        data = load_yaml("policy-completeness.yaml")
        row = find_row(data, "Blocking conflict found")
        assert row is not None, "Blocking conflict found condition must exist"
        assert "READY" in row["Applicable source states"], \
            "Blocking conflict found must apply from READY"
        assert "CONFLICT_RECORDED" in row["Required event path"], \
            "Blocking conflict found requires CONFLICT_RECORDED"

    def test_policy_completeness_input_invalidated_covers_validating(self):
        data = load_yaml("policy-completeness.yaml")
        row = find_row(data, "Input invalidated")
        assert row is not None, "Input invalidated condition must exist"
        assert "VALIDATING" in row["Applicable source states"], \
            "Input invalidated must apply from VALIDATING"
        assert "VALIDATION_INVALIDATED" in row["Required event path"], \
            "Input invalidated requires VALIDATION_INVALIDATED or READINESS_REVOKED"


class TestResultFreshnessP2Fixes:
    def test_result_revalidated_exists(self):
        data = load_yaml("result-freshness.yaml")
        t = find_transition(data, "REVALIDATION_REQUIRED", "RESULT_REVALIDATED", "STALE")
        assert t is not None, "RESULT_REVALIDATED from REVALIDATION_REQUIRED to STALE must exist"
        assert "guard" in t, "Transition must have a guard"
        assert "new acceptance record" in t["guard"].lower(), \
            "Guard must mention new acceptance record"

    def test_result_marked_stale_from_revalidation_required_widened(self):
        data = load_yaml("result-freshness.yaml")
        t = find_transition(data, "REVALIDATION_REQUIRED", "RESULT_MARKED_STALE", "STALE")
        assert t is not None, "RESULT_MARKED_STALE from REVALIDATION_REQUIRED must exist"
        guard = t["guard"].lower()
        assert "bound input changed" in guard, \
            "Guard must cover input changes, not just revalidation outcomes"

    def test_result_marked_invalid_from_revalidation_required_widened(self):
        data = load_yaml("result-freshness.yaml")
        t = find_transition(data, "REVALIDATION_REQUIRED", "RESULT_MARKED_INVALID", "INVALID")
        assert t is not None, "RESULT_MARKED_INVALID from REVALIDATION_REQUIRED must exist"
        guard = t["guard"].lower()
        assert "bound input changed" in guard, \
            "Guard must cover input changes, not just revalidation failure"

    def test_result_revalidated_in_event_catalogue(self):
        cat_path = os.path.join(PROJECT_ROOT, "docs", "event-catalogue.md")
        with open(cat_path) as handle:
            content = handle.read()
        assert "RESULT_REVALIDATED" in content, \
            "RESULT_REVALIDATED must be in event catalogue"
        assert "Freshness state machine" in content, \
            "RESULT_REVALIDATED must affect freshness state machine"

    def test_revalidation_success_separated_from_failure(self):
        data = load_yaml("result-freshness.yaml")
        success = find_transition(data, "REVALIDATION_REQUIRED", "RESULT_REVALIDATED", "STALE")
        failure = find_transition(data, "REVALIDATION_REQUIRED", "RESULT_MARKED_STALE", "STALE")
        assert success is not None, "RESULT_REVALIDATED (success) must exist"
        assert failure is not None, "RESULT_MARKED_STALE (failure) must exist"
        assert "revalidation succeeded" in success["guard"].lower(), \
            "Success guard must mention revalidation succeeded"
        assert "revalidation failed" in failure["guard"].lower(), \
            "Failure guard must mention revalidation failed"


class TestGeneratedMarkdownConsistency:
    def test_control_plane_has_conflict_recorded_from_ready(self):
        cp_path = os.path.join(PROJECT_ROOT, "docs", "control-plane.md")
        with open(cp_path) as handle:
            content = handle.read()
        assert "| `READY` | `CONFLICT_RECORDED`" in content, \
            "control-plane.md must have READY | CONFLICT_RECORDED transition"

    def test_control_plane_has_validation_invalidated_from_validating(self):
        cp_path = os.path.join(PROJECT_ROOT, "docs", "control-plane.md")
        with open(cp_path) as handle:
            content = handle.read()
        assert "| `VALIDATING` | `VALIDATION_INVALIDATED`" in content, \
            "control-plane.md must have VALIDATING | VALIDATION_INVALIDATED transition"

    def test_control_plane_has_result_revalidated(self):
        cp_path = os.path.join(PROJECT_ROOT, "docs", "control-plane.md")
        with open(cp_path) as handle:
            content = handle.read()
        assert "| `REVALIDATION_REQUIRED` | `RESULT_REVALIDATED`" in content, \
            "control-plane.md must have REVALIDATION_REQUIRED | RESULT_REVALIDATED transition"

    def test_control_plane_stale_guard_widened(self):
        cp_path = os.path.join(PROJECT_ROOT, "docs", "control-plane.md")
        with open(cp_path) as handle:
            content = handle.read()
        assert "bound input changed making the result stale" in content, \
            "control-plane.md must have widened STALE guard"

    def test_control_plane_invalid_guard_widened(self):
        cp_path = os.path.join(PROJECT_ROOT, "docs", "control-plane.md")
        with open(cp_path) as handle:
            content = handle.read()
        assert "bound input changed making the result invalid" in content, \
            "control-plane.md must have widened INVALID guard"
