"""
tests/test_architecture_pressure_engine.py

Unit tests for architecture_pressure.py:
  - compute_pressure scoring
  - band classification
  - requires_arch_review flag
  - signal scoring (count-based diminishing + boolean)
  - signals_summary generation
"""
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../services/router"))

import pytest
from architecture_pressure import (
    compute_pressure,
    classify_pressure_band,
    load_pressure_policy,
    _reload_pressure_policy,
    _score_signals,
    _signals_summary,
    _builtin_pressure_defaults,
)


@pytest.fixture(autouse=True)
def reset_policy():
    _reload_pressure_policy()
    yield
    _reload_pressure_policy()


@pytest.fixture
def policy():
    return _builtin_pressure_defaults()


class TestClassifyBand:
    def test_score_0_is_low(self, policy):
        assert classify_pressure_band(0, policy) == "low"

    def test_score_at_low_max_is_low(self, policy):
        assert classify_pressure_band(20, policy) == "low"

    def test_score_21_is_medium(self, policy):
        assert classify_pressure_band(21, policy) == "medium"

    def test_score_at_medium_max_is_medium(self, policy):
        assert classify_pressure_band(45, policy) == "medium"

    def test_score_46_is_high(self, policy):
        assert classify_pressure_band(46, policy) == "high"

    def test_score_at_high_max_is_high(self, policy):
        assert classify_pressure_band(70, policy) == "high"

    def test_score_71_is_critical(self, policy):
        assert classify_pressure_band(71, policy) == "critical"

    def test_score_200_is_critical(self, policy):
        assert classify_pressure_band(200, policy) == "critical"


class TestScoreSignals:
    def test_no_signals_zero_score(self, policy):
        components = {
            "recurrence_high_30d": 0, "recurrence_warn_30d": 0,
            "regressions_30d": 0, "escalations_30d": 0,
            "followups_created_30d": 0, "followups_overdue": 0,
            "drift_failures_30d": 0, "dependency_high_30d": 0,
        }
        assert _score_signals(components, policy) == 0

    def test_recurrence_high_boolean(self, policy):
        components = {
            "recurrence_high_30d": 1, "recurrence_warn_30d": 0,
            "regressions_30d": 0, "escalations_30d": 0,
            "followups_created_30d": 0, "followups_overdue": 0,
            "drift_failures_30d": 0, "dependency_high_30d": 0,
        }
        score = _score_signals(components, policy)
        assert score == 20   # weight = 20

    def test_recurrence_high_multiple_times_still_one_weight(self, policy):
        """recurrence_high_30d is a boolean — count > 1 does not multiply."""
        components = {
            "recurrence_high_30d": 5, "recurrence_warn_30d": 0,
            "regressions_30d": 0, "escalations_30d": 0,
            "followups_created_30d": 0, "followups_overdue": 0,
            "drift_failures_30d": 0, "dependency_high_30d": 0,
        }
        score_5 = _score_signals(components, policy)
        components["recurrence_high_30d"] = 1
        score_1 = _score_signals(components, policy)
        assert score_5 == score_1

    def test_regressions_count_increases_score(self, policy):
        base = {"recurrence_high_30d": 0, "recurrence_warn_30d": 0,
                "regressions_30d": 1, "escalations_30d": 0,
                "followups_created_30d": 0, "followups_overdue": 0,
                "drift_failures_30d": 0, "dependency_high_30d": 0}
        s1 = _score_signals(base, policy)
        base["regressions_30d"] = 3
        s3 = _score_signals(base, policy)
        assert s3 > s1

    def test_followups_overdue_adds_score(self, policy):
        components = {
            "recurrence_high_30d": 0, "recurrence_warn_30d": 0,
            "regressions_30d": 0, "escalations_30d": 0,
            "followups_created_30d": 0, "followups_overdue": 2,
            "drift_failures_30d": 0, "dependency_high_30d": 0,
        }
        score = _score_signals(components, policy)
        # weight=15, first unit = 15, second = 15//2 = 7
        expected = 15 + 7
        assert score == expected

    def test_all_signals_present(self, policy):
        components = {
            "recurrence_high_30d": 1, "recurrence_warn_30d": 1,
            "regressions_30d": 2, "escalations_30d": 2,
            "followups_created_30d": 2, "followups_overdue": 2,
            "drift_failures_30d": 2, "dependency_high_30d": 2,
        }
        score = _score_signals(components, policy)
        assert score > 0


class TestSignalsSummary:
    def test_empty_components_no_summary(self, policy):
        comp = {k: 0 for k in [
            "recurrence_high_30d", "recurrence_warn_30d", "regressions_30d",
            "escalations_30d", "followups_created_30d", "followups_overdue",
            "drift_failures_30d", "dependency_high_30d",
        ]}
        assert _signals_summary(comp, policy) == []

    def test_recurrence_high_summary(self, policy):
        comp = {"recurrence_high_30d": 1, "recurrence_warn_30d": 0,
                "regressions_30d": 0, "escalations_30d": 0,
                "followups_created_30d": 0, "followups_overdue": 0,
                "drift_failures_30d": 0, "dependency_high_30d": 0}
        summaries = _signals_summary(comp, policy)
        assert any("High-recurrence" in s for s in summaries)

    def test_followups_overdue_in_summary(self, policy):
        comp = {"recurrence_high_30d": 0, "recurrence_warn_30d": 0,
                "regressions_30d": 0, "escalations_30d": 0,
                "followups_created_30d": 0, "followups_overdue": 3,
                "drift_failures_30d": 0, "dependency_high_30d": 0}
        summaries = _signals_summary(comp, policy)
        assert any("Overdue follow-ups" in s for s in summaries)


class TestComputePressure:
    def test_no_signals_returns_low(self, policy):
        report = compute_pressure(
            "gateway", "prod",
            components={
                "recurrence_high_30d": 0, "recurrence_warn_30d": 0,
                "regressions_30d": 0, "escalations_30d": 0,
                "followups_created_30d": 0, "followups_overdue": 0,
                "drift_failures_30d": 0, "dependency_high_30d": 0,
            },
            policy=policy,
        )
        assert report["score"] == 0
        assert report["band"] == "low"
        assert report["requires_arch_review"] is False

    def test_high_pressure_sets_requires_arch_review(self, policy):
        report = compute_pressure(
            "gateway", "prod",
            components={
                "recurrence_high_30d": 1,
                "regressions_30d": 3,
                "escalations_30d": 3,
                "followups_overdue": 2,
                "recurrence_warn_30d": 1,
                "followups_created_30d": 2,
                "drift_failures_30d": 2,
                "dependency_high_30d": 2,
            },
            policy=policy,
        )
        assert report["requires_arch_review"] is True
        assert report["band"] in ("high", "critical")

    def test_report_includes_service_and_env(self, policy):
        report = compute_pressure(
            "router", "staging",
            components={k: 0 for k in [
                "recurrence_high_30d", "recurrence_warn_30d", "regressions_30d",
                "escalations_30d", "followups_created_30d", "followups_overdue",
                "drift_failures_30d", "dependency_high_30d",
            ]},
            policy=policy,
        )
        assert report["service"] == "router"
        assert report["env"] == "staging"

    def test_report_has_computed_at(self, policy):
        report = compute_pressure("svc", "prod", components={}, policy=policy)
        assert "computed_at" in report

    def test_missing_components_default_to_zero(self, policy):
        """If components dict is incomplete, missing keys default to 0."""
        report = compute_pressure(
            "svc", "prod",
            components={"recurrence_high_30d": 1},
            policy=policy,
        )
        assert report["score"] >= 0   # no KeyError

    def test_score_is_non_negative(self, policy):
        report = compute_pressure("svc", "prod", components={}, policy=policy)
        assert report["score"] >= 0

    def test_signals_summary_populated_when_signals_active(self, policy):
        report = compute_pressure(
            "svc", "prod",
            components={
                "recurrence_high_30d": 1, "regressions_30d": 2,
                "escalations_30d": 0, "recurrence_warn_30d": 0,
                "followups_created_30d": 0, "followups_overdue": 0,
                "drift_failures_30d": 0, "dependency_high_30d": 0,
            },
            policy=policy,
        )
        assert len(report["signals_summary"]) > 0

    def test_band_critical_above_70(self, policy):
        report = compute_pressure(
            "svc", "prod",
            components={
                "recurrence_high_30d": 1,
                "regressions_30d": 5,
                "escalations_30d": 5,
                "followups_overdue": 5,
                "recurrence_warn_30d": 1,
                "followups_created_30d": 5,
                "drift_failures_30d": 5,
                "dependency_high_30d": 5,
            },
            policy=policy,
        )
        assert report["band"] == "critical"
        assert report["requires_arch_review"] is True

    def test_no_stores_with_none_components_returns_report(self, policy):
        """When no stores, fallback to zeros — should not raise."""
        report = compute_pressure("svc", "prod", policy=policy)
        assert "score" in report
        assert "band" in report
