"""
E2E: Critical Path Coverage Gate
==================================
This test enforces that specific, production-critical code paths are
ALWAYS exercised during the E2E test suite.

PROBLEM: Standard coverage tools measure overall percentage, but a bot
with 80% coverage can still crash if the missing 20% contains the
startup path, serialization, or session persistence.

SOLUTION: This test defines a registry of "Critical Functions" that
MUST be covered. If any critical function has 0% coverage, the build fails.

HOW TO USE:
    1. Run: pytest tests/e2e --cov=GramAddict.core --cov-report=json:coverage_e2e.json
    2. Then: pytest tests/e2e/test_system_coverage_gate.py
    The test reads coverage_e2e.json and validates critical paths.

    Or run everything together (recommended):
    pytest tests/e2e --cov=GramAddict.core --cov-report=json:coverage_e2e.json

HOW TO ADD NEW CRITICAL PATHS:
    When a production bug is caused by untested code, add the function
    to CRITICAL_PATHS with a description. This prevents the same class
    of bug from ever recurring.
"""

import json
import os

import pytest


# ═══════════════════════════════════════════════════════════════════════
# CRITICAL PATHS REGISTRY
# ═══════════════════════════════════════════════════════════════════════
# Each entry: (file_path_suffix, line_ranges_that_must_be_covered, description)
#
# These are functions where 0% coverage means a PRODUCTION CRASH.
# Add entries here as post-mortem actions after every production bug.
# ═══════════════════════════════════════════════════════════════════════

CRITICAL_PATHS = {
    # ── Serialization & Persistence ──
    "session_state.py": {
        "min_coverage_pct": 50,
        "reason": "SessionStateEncoder crashed mid-write, corrupting sessions.json (2026-05-02 incident)",
    },
    "persistent_list.py": {
        "min_coverage_pct": 20,
        "reason": "persist() was silently skipped in tests, hiding a fatal serialization bug",
    },
    # ── Core Lifecycle ──
    "dopamine_engine.py": {
        "min_coverage_pct": 40,
        "reason": "Session timeout logic must be verified or bot runs forever",
    },
    "config.py": {
        "min_coverage_pct": 50,
        "reason": "Config parsing failure = bot cannot start",
    },
    # ── Perception Pipeline ──
    "perception/screen_identity.py": {
        "min_coverage_pct": 60,
        "reason": "Screen misidentification causes navigation drift and crash loops",
    },
    "perception/spatial_parser.py": {
        "min_coverage_pct": 70,
        "reason": "Spatial parsing is the foundation of all UI interaction",
    },
    "perception/intent_resolver.py": {
        "min_coverage_pct": 50,
        "reason": "Intent resolution drives every autonomous click decision",
    },
    # ── Navigation ──
    "q_nav_graph.py": {
        "min_coverage_pct": 25,
        "reason": "Navigation failures cause the bot to get stuck in infinite loops",
    },
    # ── Device Interface ──
    "device_facade.py": {
        "min_coverage_pct": 40,
        "reason": "Device interface is the single point of contact with the real device",
    },
}


class TestCriticalPathCoverage:
    """
    Reads the coverage JSON report and validates that all critical paths
    meet their minimum coverage thresholds.
    """

    def _load_coverage_data(self):
        """Load coverage JSON if it exists."""
        coverage_file = os.path.join(os.path.dirname(__file__), "..", "..", "coverage_e2e.json")
        coverage_file = os.path.abspath(coverage_file)

        if not os.path.exists(coverage_file):
            pytest.skip(
                "coverage_e2e.json not found. Run with: "
                "pytest tests/e2e --cov=GramAddict.core --cov-report=json:coverage_e2e.json"
            )

        with open(coverage_file, "r") as f:
            return json.load(f)

    def test_all_critical_paths_meet_minimum_coverage(self):
        """
        GATE: Every critical production path must meet its minimum coverage.
        If a critical path has 0% coverage, it means the E2E tests are lying
        about system health.
        """
        data = self._load_coverage_data()
        files = data.get("files", {})

        violations = []

        for path_suffix, requirements in CRITICAL_PATHS.items():
            min_pct = requirements["min_coverage_pct"]
            reason = requirements["reason"]

            # Find the matching file in coverage data
            matching_files = [
                (fpath, fdata) for fpath, fdata in files.items()
                if fpath.endswith(path_suffix)
            ]

            if not matching_files:
                violations.append(
                    f"  ❌ {path_suffix}: NOT FOUND in coverage data! "
                    f"This critical module is completely invisible to tests."
                )
                continue

            fpath, fdata = matching_files[0]
            summary = fdata.get("summary", {})
            actual_pct = summary.get("percent_covered", 0)

            if actual_pct < min_pct:
                violations.append(
                    f"  ❌ {path_suffix}: {actual_pct:.1f}% < {min_pct}% minimum\n"
                    f"     Reason: {reason}"
                )

        assert not violations, (
            "\n🚨 CRITICAL PATH COVERAGE VIOLATIONS 🚨\n"
            "The following production-critical code paths are insufficiently tested.\n"
            "These are not arbitrary thresholds — each one guards against a known\n"
            "production failure class:\n\n"
            + "\n".join(violations)
            + "\n\nFix: Add E2E tests that exercise these code paths."
        )

    def test_coverage_report_freshness(self):
        """
        Ensures the coverage report is from the current test run,
        not stale data from a previous session.
        """
        coverage_file = os.path.join(os.path.dirname(__file__), "..", "..", "coverage_e2e.json")
        coverage_file = os.path.abspath(coverage_file)

        if not os.path.exists(coverage_file):
            pytest.skip("No coverage report to validate freshness.")

        import time

        age_seconds = time.time() - os.path.getmtime(coverage_file)
        max_age_seconds = 1800  # 30 minutes (full E2E suite takes ~17min)

        assert age_seconds < max_age_seconds, (
            f"Coverage report is {age_seconds:.0f}s old (max: {max_age_seconds}s). "
            f"Re-run with --cov to generate fresh data."
        )

    def test_overall_e2e_coverage_floor(self):
        """
        GATE: Overall E2E coverage must not drop below the floor.
        This prevents silent regressions where new code is added without tests.
        """
        data = self._load_coverage_data()
        totals = data.get("totals", {})
        overall_pct = totals.get("percent_covered", 0)

        min_floor = 35  # Current baseline, ratchet up over time

        assert overall_pct >= min_floor, (
            f"OVERALL E2E COVERAGE DROPPED: {overall_pct:.1f}% < {min_floor}% floor!\n"
            f"New code was added without corresponding E2E tests."
        )
