"""Layer 4: Observability & Summary Tests.

Proving: "Results explain what happened — summaries, telemetry, diffs."
Mix of pure plan/diff tests and result inspection.

NOTE: Installed DiffResult has: rules_added, rules_removed, rules_modified,
thresholds_changed, summary. No has_changes, blocking_changed, etc.
"""
from __future__ import annotations

import pytest

from tests.contract.conftest import (
    CUSTOMER_SPEC,
    SINGLE_SOURCE_SPEC,
    make_source,
    make_spec,
    requires_native,
    requires_reconcile,
)

from kanoniv.plan import plan
from kanoniv.diff import diff


# ---------------------------------------------------------------------------
# Plan observability
# ---------------------------------------------------------------------------

@requires_native
class TestPlanObservability:
    def test_plan_summary_describes_rules(self):
        """Summary mentions rule names or entity info."""
        spec = make_spec(CUSTOMER_SPEC)
        s = plan(spec).summary()
        assert len(s) > 0


# ---------------------------------------------------------------------------
# Diff observability
# ---------------------------------------------------------------------------

@requires_native
class TestDiffObservability:
    def test_diff_summary_non_empty_on_change(self):
        """diff(a, b).summary is non-empty when rules change."""
        spec_a = make_spec(CUSTOMER_SPEC)
        extra_rule = """  - name: name_exact
    type: exact
    field: first_name
    weight: 0.5
"""
        modified = CUSTOMER_SPEC.replace("decision:", extra_rule + "decision:")
        spec_b = make_spec(modified)
        result = diff(spec_a, spec_b)
        assert len(result.summary) > 0

    def test_diff_detects_rule_addition(self):
        """Adding a rule → rules_added contains it."""
        spec_a = make_spec(CUSTOMER_SPEC)
        extra_rule = """  - name: name_exact
    type: exact
    field: first_name
    weight: 0.5
"""
        modified = CUSTOMER_SPEC.replace("decision:", extra_rule + "decision:")
        spec_b = make_spec(modified)
        result = diff(spec_a, spec_b)
        assert "name_exact" in result.rules_added

    def test_diff_detects_rule_removal(self):
        """Removing a rule → rules_removed contains it."""
        spec_a = make_spec(CUSTOMER_SPEC)
        modified = CUSTOMER_SPEC.replace(
            """  - name: phone_exact
    type: exact
    field: phone
    weight: 0.8
""",
            "",
        )
        spec_b = make_spec(modified)
        result = diff(spec_a, spec_b)
        assert "phone_exact" in result.rules_removed

    def test_diff_detects_threshold_change(self):
        """Changing thresholds → thresholds_changed is True."""
        spec_a = make_spec(CUSTOMER_SPEC)
        modified = CUSTOMER_SPEC.replace("match: 0.5", "match: 0.9")
        spec_b = make_spec(modified)
        result = diff(spec_a, spec_b)
        assert result.thresholds_changed

    def test_diff_detects_rule_modification(self):
        """Changing a rule weight → rules_modified is non-empty."""
        spec_a = make_spec(CUSTOMER_SPEC)
        modified = CUSTOMER_SPEC.replace("weight: 0.8", "weight: 0.6")
        spec_b = make_spec(modified)
        result = diff(spec_a, spec_b)
        assert len(result.rules_modified) > 0

    def test_diff_no_change_summary_empty(self):
        """Identical specs → no rules added/removed/modified, no threshold change."""
        spec_a = make_spec(CUSTOMER_SPEC)
        spec_b = make_spec(CUSTOMER_SPEC)
        result = diff(spec_a, spec_b)
        assert result.rules_added == []
        assert result.rules_removed == []
        assert result.rules_modified == []
        assert not result.thresholds_changed


# ---------------------------------------------------------------------------
# Telemetry (requires reconcile)
# ---------------------------------------------------------------------------

@requires_reconcile
class TestTelemetry:
    def test_telemetry_has_pairs_evaluated(self, tmp_path):
        """telemetry['pairs_evaluated'] exists and > 0."""
        from kanoniv.reconcile import reconcile

        rows = [
            {"id": "1", "email": "shared@example.com", "first_name": "A",
             "last_name": "Smith", "phone": "555-0001"},
            {"id": "2", "email": "shared@example.com", "first_name": "B",
             "last_name": "Jones", "phone": "555-0002"},
        ]
        src = make_source(tmp_path, "main", rows)
        spec = make_spec(SINGLE_SOURCE_SPEC)
        result = reconcile([src], spec)
        assert "pairs_evaluated" in result.telemetry
        assert result.telemetry["pairs_evaluated"] > 0

    def test_telemetry_has_rule_telemetry(self, tmp_path):
        """telemetry['rule_telemetry'] exists."""
        from kanoniv.reconcile import reconcile

        rows = [
            {"id": "1", "email": "a@example.com", "first_name": "A",
             "last_name": "Smith", "phone": "555-0001"},
            {"id": "2", "email": "b@example.com", "first_name": "B",
             "last_name": "Jones", "phone": "555-0002"},
        ]
        src = make_source(tmp_path, "main", rows)
        spec = make_spec(SINGLE_SOURCE_SPEC)
        result = reconcile([src], spec)
        assert "rule_telemetry" in result.telemetry


# ---------------------------------------------------------------------------
# Result shape (requires reconcile)
# ---------------------------------------------------------------------------

@requires_reconcile
class TestResultShape:
    def test_result_to_pandas_works(self, tmp_path):
        """.to_pandas() returns valid DataFrame."""
        from kanoniv.reconcile import reconcile

        rows = [
            {"id": "1", "email": "a@example.com", "first_name": "A",
             "last_name": "Smith", "phone": "555-0001"},
            {"id": "2", "email": "b@example.com", "first_name": "B",
             "last_name": "Jones", "phone": "555-0002"},
        ]
        src = make_source(tmp_path, "main", rows)
        spec = make_spec(SINGLE_SOURCE_SPEC)
        result = reconcile([src], spec)
        import pandas as pd

        df = result.to_pandas()
        assert isinstance(df, pd.DataFrame)

    def test_result_to_pandas_row_count(self, tmp_path):
        """DataFrame has cluster_count rows."""
        from kanoniv.reconcile import reconcile

        rows = [
            {"id": "1", "email": "a@example.com", "first_name": "A",
             "last_name": "Smith", "phone": "555-0001"},
            {"id": "2", "email": "b@example.com", "first_name": "B",
             "last_name": "Jones", "phone": "555-0002"},
        ]
        src = make_source(tmp_path, "main", rows)
        spec = make_spec(SINGLE_SOURCE_SPEC)
        result = reconcile([src], spec)
        df = result.to_pandas()
        assert len(df) == result.cluster_count
