"""Chaos: Plan-Level (Semantic Drift).

Proving: "Tiny real changes → hash changes. Cosmetic rearrangement → hash stable."
All tests are pure spec/plan/diff — no engine calls.

NOTE: The current plan hasher is NOT order-invariant for rules/sources.
Reordering rules or sources DOES change the hash. Tests document this.
"""
from __future__ import annotations

import pytest
import yaml

from tests.contract.conftest import CUSTOMER_SPEC, make_spec, requires_native

pytestmark = requires_native

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


# ---------------------------------------------------------------------------
# Cosmetic invariance
# ---------------------------------------------------------------------------


class TestCosmeticInvariance:
    def test_whitespace_change_same_hash(self):
        """Extra whitespace/newlines → same plan hash."""
        spec_a = make_spec(CUSTOMER_SPEC)
        padded = CUSTOMER_SPEC.replace("\nentity:", "\n\n\nentity:")
        spec_b = make_spec(padded)
        assert plan(spec_a).plan_hash == plan(spec_b).plan_hash

    def test_reorder_rules_changes_hash(self):
        """Swapping rule order → currently changes hash (order-sensitive hashing)."""
        spec_a = make_spec(CUSTOMER_SPEC)
        parsed = yaml.safe_load(CUSTOMER_SPEC)
        parsed["rules"] = list(reversed(parsed["rules"]))
        reordered = yaml.dump(parsed, default_flow_style=False)
        spec_b = make_spec(reordered)
        # Document: rule order IS significant in the current implementation
        assert plan(spec_a).plan_hash != plan(spec_b).plan_hash

    def test_reorder_sources_changes_hash(self):
        """Swapping source order → currently changes hash (order-sensitive hashing)."""
        spec_a = make_spec(CUSTOMER_SPEC)
        parsed = yaml.safe_load(CUSTOMER_SPEC)
        parsed["sources"] = list(reversed(parsed["sources"]))
        reordered = yaml.dump(parsed, default_flow_style=False)
        spec_b = make_spec(reordered)
        # Document: source order IS significant in the current implementation
        assert plan(spec_a).plan_hash != plan(spec_b).plan_hash


# ---------------------------------------------------------------------------
# Semantic sensitivity
# ---------------------------------------------------------------------------


class TestSemanticSensitivity:
    def test_tiny_threshold_change_different_hash(self):
        """0.70 → 0.69 → hash changes."""
        spec_a = make_spec(CUSTOMER_SPEC)
        modified = CUSTOMER_SPEC.replace("match: 0.5", "match: 0.69")
        spec_b = make_spec(modified)
        assert plan(spec_a).plan_hash != plan(spec_b).plan_hash

    def test_tiny_weight_change_different_hash(self):
        """0.8 → 0.79 → hash changes."""
        spec_a = make_spec(CUSTOMER_SPEC)
        modified = CUSTOMER_SPEC.replace("weight: 0.8", "weight: 0.79")
        spec_b = make_spec(modified)
        assert plan(spec_a).plan_hash != plan(spec_b).plan_hash

    def test_diff_explains_threshold_shift(self):
        """Tiny threshold change → diff detects threshold change."""
        spec_a = make_spec(CUSTOMER_SPEC)
        modified = CUSTOMER_SPEC.replace("match: 0.5", "match: 0.69")
        spec_b = make_spec(modified)
        result = diff(spec_a, spec_b)
        assert result.thresholds_changed

    def test_diff_explains_weight_shift(self):
        """Tiny weight change → diff detects rule modification."""
        spec_a = make_spec(CUSTOMER_SPEC)
        modified = CUSTOMER_SPEC.replace("weight: 0.8", "weight: 0.79")
        spec_b = make_spec(modified)
        result = diff(spec_a, spec_b)
        assert len(result.rules_modified) > 0
