"""Chaos: Non-Determinism (Subtle & Deadly).

Proving: "Same data + same spec → identical results, regardless of input order."
If any test fails → stop everything.
Requires native extension with reconcile_local.
"""
from __future__ import annotations

import random

import pytest

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

pytestmark = requires_reconcile


# ---------------------------------------------------------------------------
# Determinism tests
# ---------------------------------------------------------------------------

BASE_ROWS = [
    {"id": "1", "email": "alice@example.com", "first_name": "Alice",
     "last_name": "Smith", "phone": "555-0001"},
    {"id": "2", "email": "bob@example.com", "first_name": "Bob",
     "last_name": "Jones", "phone": "555-0002"},
    {"id": "3", "email": "alice@example.com", "first_name": "Alice",
     "last_name": "Smith", "phone": "555-0003"},
    {"id": "4", "email": "carol@example.com", "first_name": "Carol",
     "last_name": "Brown", "phone": "555-0004"},
    {"id": "5", "email": "dave@example.com", "first_name": "Dave",
     "last_name": "Miller", "phone": "555-0005"},
]


class TestDeterminism:
    def test_shuffled_rows_same_clusters(self, tmp_path):
        """Shuffle CSV rows 5 times → same cluster_count every time."""
        from kanoniv.reconcile import reconcile

        spec = make_spec(SINGLE_SOURCE_SPEC)
        counts = []
        for i in range(5):
            shuffled = BASE_ROWS.copy()
            random.Random(i).shuffle(shuffled)
            src = make_source(tmp_path, f"main_{i}", shuffled)
            result = reconcile([src], spec)
            counts.append(result.cluster_count)
        assert len(set(counts)) == 1, f"Cluster counts varied: {counts}"

    def test_shuffled_rows_same_merge_rate(self, tmp_path):
        """Shuffle → same merge_rate."""
        from kanoniv.reconcile import reconcile

        spec = make_spec(SINGLE_SOURCE_SPEC)
        rates = []
        for i in range(5):
            shuffled = BASE_ROWS.copy()
            random.Random(i + 100).shuffle(shuffled)
            src = make_source(tmp_path, f"main_mr_{i}", shuffled)
            result = reconcile([src], spec)
            rates.append(round(result.merge_rate, 10))
        assert len(set(rates)) == 1, f"Merge rates varied: {rates}"

    def test_shuffled_rows_same_golden_records(self, tmp_path):
        """Shuffle → same golden records (order-independent, identity-stable comparison).

        When multiple records merge into one cluster, the survivorship strategy
        picks a representative.  Which *source row* is chosen can depend on
        input order, so we compare golden records by their identity-stable
        fields (email, first_name, last_name) rather than row-level fields
        (id, phone) that vary per representative.
        """
        from kanoniv.reconcile import reconcile

        # Fields that are stable across merged records in the same cluster
        identity_fields = {"email", "first_name", "last_name"}

        spec = make_spec(SINGLE_SOURCE_SPEC)
        golden_sets = []
        for i in range(5):
            shuffled = BASE_ROWS.copy()
            random.Random(i + 200).shuffle(shuffled)
            src = make_source(tmp_path, f"main_gr_{i}", shuffled)
            result = reconcile([src], spec)
            normalized = frozenset(
                tuple(sorted((k, v) for k, v in gr.items() if k in identity_fields))
                for gr in result.golden_records
            )
            golden_sets.append(normalized)
        assert len(set(golden_sets)) == 1, "Golden records varied across shuffles"

    def test_repeated_runs_identical(self, tmp_path):
        """Run reconcile 3 times, same input → identical results."""
        from kanoniv.reconcile import reconcile

        spec = make_spec(SINGLE_SOURCE_SPEC)
        src = make_source(tmp_path, "main", BASE_ROWS)
        results = [reconcile([src], spec) for _ in range(3)]
        counts = [r.cluster_count for r in results]
        rates = [round(r.merge_rate, 10) for r in results]
        assert len(set(counts)) == 1, f"Cluster counts varied: {counts}"
        assert len(set(rates)) == 1, f"Merge rates varied: {rates}"

    def test_source_order_invariant(self, tmp_path):
        """[crm, orders] vs [orders, crm] → same clusters."""
        from kanoniv.reconcile import reconcile

        spec = make_spec(CUSTOMER_SPEC)
        crm_rows = [
            {"id": "1", "email": "alice@example.com", "first_name": "Alice",
             "last_name": "Smith", "phone": "555-0001"},
            {"id": "2", "email": "bob@example.com", "first_name": "Bob",
             "last_name": "Jones", "phone": "555-0002"},
        ]
        ecom_rows = [
            {"id": "3", "email": "alice@example.com", "first_name": "Alice",
             "last_name": "Smith", "phone": "555-0003"},
        ]
        crm = make_source(tmp_path, "crm", crm_rows)
        ecom = make_source(tmp_path, "ecommerce", ecom_rows)

        result_ab = reconcile([crm, ecom], spec)
        result_ba = reconcile([ecom, crm], spec)

        assert result_ab.cluster_count == result_ba.cluster_count
        assert round(result_ab.merge_rate, 10) == round(result_ba.merge_rate, 10)
