"""
tests/track_b/test_story_7_01.py

Story 7.01: ConflictDetector — Patch Contradiction Analysis

Black Box Tests (BB1–BB5):
    BB1  Two deltas with same path + different values → has_conflicts=True
    BB2  Two deltas touching different paths → has_conflicts=False, fast_merge returns both patches
    BB3  Empty deltas list → has_conflicts=False, non_conflicting_deltas=[]
    BB4  Single delta → has_conflicts=False
    BB5  Add at /x + Remove at /x → op_contradiction detected

White Box Tests (WB1–WB5):
    WB1  conflicting_paths contains the actual path string
    WB2  fast_merge returns flat list of all patches merged (concatenated)
    WB3  conflict_types list populated correctly
    WB4  non_conflicting_deltas excludes conflicting ones
    WB5  Handles StateDelta with tuple patch (converts to list internally)

Package Test:
    PKG  __init__.py imports work
"""

from __future__ import annotations

import sys
import os
from datetime import datetime

import pytest

# ---------------------------------------------------------------------------
# Path setup
# ---------------------------------------------------------------------------

GENESIS_ROOT = "/mnt/e/genesis-system"
if GENESIS_ROOT not in sys.path:
    sys.path.insert(0, GENESIS_ROOT)

# ---------------------------------------------------------------------------
# Imports under test
# ---------------------------------------------------------------------------

from core.merge.conflict_detector import ConflictDetector, ConflictReport  # noqa: E402
from core.coherence.state_delta import StateDelta  # noqa: E402


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def make_delta(
    agent_id: str,
    patch: list[dict],
    session_id: str = "sess-001",
    version: int = 1,
) -> StateDelta:
    """Create a StateDelta with the given patch (stored as tuple)."""
    return StateDelta(
        agent_id=agent_id,
        session_id=session_id,
        version_at_read=version,
        patch=tuple(patch),
        submitted_at=datetime(2026, 2, 25, 12, 0, 0),
    )


DETECTOR = ConflictDetector()


# ===========================================================================
# BB Tests — Black Box
# ===========================================================================


def test_bb1_path_collision_different_values():
    """BB1: Two deltas write to the same path with different values → conflict."""
    delta_a = make_delta("agent-A", [{"op": "replace", "path": "/status", "value": "active"}])
    delta_b = make_delta("agent-B", [{"op": "replace", "path": "/status", "value": "inactive"}])

    report = DETECTOR.detect([delta_a, delta_b])

    assert report.has_conflicts is True, "Expected a conflict to be detected"
    assert len(report.conflicting_paths) > 0, "Expected at least one conflicting path"


def test_bb2_different_paths_no_conflict():
    """BB2: Two deltas writing to different paths → no conflict; fast_merge combines both."""
    delta_a = make_delta("agent-A", [{"op": "add", "path": "/foo", "value": 1}])
    delta_b = make_delta("agent-B", [{"op": "add", "path": "/bar", "value": 2}])

    report = DETECTOR.detect([delta_a, delta_b])

    assert report.has_conflicts is False
    assert len(report.non_conflicting_deltas) == 2

    merged = DETECTOR.fast_merge(report.non_conflicting_deltas)
    assert len(merged) == 2
    paths = {op["path"] for op in merged}
    assert "/foo" in paths
    assert "/bar" in paths


def test_bb3_empty_list():
    """BB3: Empty deltas → has_conflicts=False, non_conflicting_deltas=[]."""
    report = DETECTOR.detect([])

    assert report.has_conflicts is False
    assert report.non_conflicting_deltas == []
    assert report.conflicting_paths == []


def test_bb4_single_delta():
    """BB4: Single delta → no conflict possible."""
    delta = make_delta("agent-X", [{"op": "add", "path": "/k", "value": 42}])

    report = DETECTOR.detect([delta])

    assert report.has_conflicts is False
    assert report.non_conflicting_deltas == [delta]


def test_bb5_add_vs_remove_op_contradiction():
    """BB5: One delta adds at /x, another removes at /x → op_contradiction."""
    delta_a = make_delta("agent-A", [{"op": "add", "path": "/x", "value": "hello"}])
    delta_b = make_delta("agent-B", [{"op": "remove", "path": "/x"}])

    report = DETECTOR.detect([delta_a, delta_b])

    assert report.has_conflicts is True
    assert "op_contradiction" in report.conflict_types


# ===========================================================================
# WB Tests — White Box
# ===========================================================================


def test_wb1_conflicting_paths_contains_path_string():
    """WB1: conflicting_paths contains the exact path string that conflicted."""
    delta_a = make_delta("agent-A", [{"op": "replace", "path": "/config/mode", "value": "fast"}])
    delta_b = make_delta("agent-B", [{"op": "replace", "path": "/config/mode", "value": "slow"}])

    report = DETECTOR.detect([delta_a, delta_b])

    assert "/config/mode" in report.conflicting_paths


def test_wb2_fast_merge_concatenates_all_patches():
    """WB2: fast_merge returns a flat list of all ops from all non-conflicting deltas."""
    ops_a = [{"op": "add", "path": "/a", "value": 1}, {"op": "add", "path": "/b", "value": 2}]
    ops_b = [{"op": "add", "path": "/c", "value": 3}]
    delta_a = make_delta("agent-A", ops_a)
    delta_b = make_delta("agent-B", ops_b)

    # Both touch different paths — no conflict
    report = DETECTOR.detect([delta_a, delta_b])
    assert report.has_conflicts is False

    merged = DETECTOR.fast_merge(report.non_conflicting_deltas)
    assert len(merged) == 3  # 2 from A + 1 from B
    paths = [op["path"] for op in merged]
    assert "/a" in paths
    assert "/b" in paths
    assert "/c" in paths


def test_wb3_conflict_types_populated_correctly():
    """WB3: conflict_types list records the correct conflict category."""
    # Semantic contradiction via replace ops with different values
    delta_a = make_delta("agent-A", [{"op": "replace", "path": "/val", "value": 10}])
    delta_b = make_delta("agent-B", [{"op": "replace", "path": "/val", "value": 20}])

    report = DETECTOR.detect([delta_a, delta_b])

    assert report.has_conflicts is True
    # Both semantic_contradiction and path_collision fire for two differing replace ops
    assert any(ct in report.conflict_types for ct in ("semantic_contradiction", "path_collision"))


def test_wb4_non_conflicting_deltas_excludes_conflicting():
    """WB4: When 1-of-3 deltas conflicts with another, the clean third is in non_conflicting."""
    delta_conflict_a = make_delta("agent-A", [{"op": "replace", "path": "/shared", "value": "X"}])
    delta_conflict_b = make_delta("agent-B", [{"op": "replace", "path": "/shared", "value": "Y"}])
    delta_clean = make_delta("agent-C", [{"op": "add", "path": "/unique", "value": 99}])

    report = DETECTOR.detect([delta_conflict_a, delta_conflict_b, delta_clean])

    assert report.has_conflicts is True
    # The clean delta must appear in non_conflicting_deltas
    assert delta_clean in report.non_conflicting_deltas
    # Conflicting deltas must NOT appear
    assert delta_conflict_a not in report.non_conflicting_deltas
    assert delta_conflict_b not in report.non_conflicting_deltas


def test_wb5_handles_tuple_patch():
    """WB5: StateDelta stores patch as a tuple; ConflictDetector converts it internally."""
    # StateDelta always stores patch as tuple — verify detector handles this without error
    delta_a = make_delta("agent-A", [{"op": "add", "path": "/t", "value": "alpha"}])
    delta_b = make_delta("agent-B", [{"op": "add", "path": "/t", "value": "beta"}])

    # Confirm patches ARE tuples (the StateDelta frozen dataclass contract)
    assert isinstance(delta_a.patch, tuple)
    assert isinstance(delta_b.patch, tuple)

    # Should still detect the conflict correctly
    report = DETECTOR.detect([delta_a, delta_b])
    assert report.has_conflicts is True


# ===========================================================================
# Package import test
# ===========================================================================


def test_pkg_init_imports():
    """PKG: core.merge __init__.py exports ConflictDetector and ConflictReport."""
    from core.merge import ConflictDetector as CD, ConflictReport as CR  # noqa: F401
    assert CD is ConflictDetector
    assert CR is ConflictReport


# ===========================================================================
# Additional edge-case coverage
# ===========================================================================


def test_same_value_no_conflict():
    """Two deltas writing the same value to the same path should NOT conflict."""
    delta_a = make_delta("agent-A", [{"op": "replace", "path": "/status", "value": "ok"}])
    delta_b = make_delta("agent-B", [{"op": "replace", "path": "/status", "value": "ok"}])

    report = DETECTOR.detect([delta_a, delta_b])

    # Identical values — not a logical contradiction
    assert report.has_conflicts is False


def test_fast_merge_empty_input():
    """fast_merge with empty list returns empty list."""
    merged = DETECTOR.fast_merge([])
    assert merged == []
