"""Tests for conflict resolution."""
import pytest

from greymatter_core.sync.conflict import (
    ConflictStrategy,
    flush_audit_log,
    resolve_conflict,
    _resolve_lww,
    _merge_pattern,
    _merge_observation,
)


class TestLWW:
    def test_higher_version_wins(self):
        local = {"id": "p-1", "title": "Local", "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "p-1", "title": "Remote", "_sync_version": 2, "_node_id": "n2"}
        result = _resolve_lww(local, remote)
        assert result["title"] == "Remote"

    def test_lower_version_loses(self):
        local = {"id": "p-1", "title": "Local", "_sync_version": 5, "_node_id": "n1"}
        remote = {"id": "p-1", "title": "Remote", "_sync_version": 3, "_node_id": "n2"}
        result = _resolve_lww(local, remote)
        assert result["title"] == "Local"

    def test_tiebreaker_uses_node_id(self):
        local = {"id": "p-1", "title": "Local", "_sync_version": 1, "_node_id": "aaa"}
        remote = {"id": "p-1", "title": "Remote", "_sync_version": 1, "_node_id": "zzz"}
        result = _resolve_lww(local, remote)
        assert result["title"] == "Remote"  # "zzz" > "aaa"

    def test_tiebreaker_other_direction(self):
        local = {"id": "p-1", "title": "Local", "_sync_version": 1, "_node_id": "zzz"}
        remote = {"id": "p-1", "title": "Remote", "_sync_version": 1, "_node_id": "aaa"}
        result = _resolve_lww(local, remote)
        assert result["title"] == "Local"  # "zzz" > "aaa" so local wins


class TestLWWWithHLC:
    """Test LWW resolution using HLC timestamps."""

    def test_hlc_wins_over_sync_version(self):
        """When HLC timestamps are present, they take priority."""
        # Local has higher _sync_version, but remote has later HLC
        local = {"id": "p-1", "title": "Local", "_sync_version": 5, "_node_id": "n1",
                 "_hlc_timestamp": "1000.000001.n1"}
        remote = {"id": "p-1", "title": "Remote", "_sync_version": 2, "_node_id": "n2",
                  "_hlc_timestamp": "1001.000000.n2"}
        result = _resolve_lww(local, remote)
        assert result["title"] == "Remote"  # HLC 1001 > 1000, ignoring sync_version

    def test_hlc_earlier_loses(self):
        local = {"id": "p-1", "title": "Local", "_sync_version": 1, "_node_id": "n1",
                 "_hlc_timestamp": "1005.000003.n1"}
        remote = {"id": "p-1", "title": "Remote", "_sync_version": 10, "_node_id": "n2",
                  "_hlc_timestamp": "1003.000000.n2"}
        result = _resolve_lww(local, remote)
        assert result["title"] == "Local"  # HLC 1005 > 1003

    def test_hlc_same_wall_counter_breaks_tie(self):
        local = {"id": "p-1", "title": "Local", "_sync_version": 1, "_node_id": "n1",
                 "_hlc_timestamp": "1000.000002.n1"}
        remote = {"id": "p-1", "title": "Remote", "_sync_version": 1, "_node_id": "n2",
                  "_hlc_timestamp": "1000.000005.n2"}
        result = _resolve_lww(local, remote)
        assert result["title"] == "Remote"  # counter 5 > 2

    def test_falls_back_to_sync_version_without_hlc(self):
        """Without HLC timestamps, behavior is unchanged."""
        local = {"id": "p-1", "title": "Local", "_sync_version": 5, "_node_id": "n1"}
        remote = {"id": "p-1", "title": "Remote", "_sync_version": 3, "_node_id": "n2"}
        result = _resolve_lww(local, remote)
        assert result["title"] == "Local"  # sync_version 5 > 3

    def test_falls_back_when_only_one_has_hlc(self):
        """If only one side has HLC, fall back to sync_version."""
        local = {"id": "p-1", "title": "Local", "_sync_version": 1, "_node_id": "n1",
                 "_hlc_timestamp": "1000.000001.n1"}
        remote = {"id": "p-1", "title": "Remote", "_sync_version": 5, "_node_id": "n2"}
        result = _resolve_lww(local, remote)
        assert result["title"] == "Remote"  # Falls back to sync_version 5 > 1


class TestMergePattern:
    def test_takes_max_confirmations(self):
        local = {"id": "p-1", "confirmations": 5, "applications": 2,
                 "confidence": "medium", "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "p-1", "confirmations": 3, "applications": 8,
                  "confidence": "low", "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_pattern(local, remote)
        assert result["confirmations"] == 5   # max(5, 3)
        assert result["applications"] == 8    # max(2, 8)

    def test_takes_higher_confidence(self):
        local = {"id": "p-1", "confirmations": 0, "applications": 0,
                 "confidence": "high", "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "p-1", "confirmations": 0, "applications": 0,
                  "confidence": "medium", "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_pattern(local, remote)
        assert result["confidence"] == "high"

    def test_verified_beats_high(self):
        local = {"id": "p-1", "confirmations": 0, "applications": 0,
                 "confidence": "verified", "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "p-1", "confirmations": 0, "applications": 0,
                  "confidence": "high", "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_pattern(local, remote)
        assert result["confidence"] == "verified"


class TestMergeObservation:
    def test_takes_max_confirmations(self):
        local = {"id": "obs-1", "confirmations": 3, "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "obs-1", "confirmations": 7, "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        assert result["confirmations"] == 7

    def test_higher_confidence_wins(self):
        """Confirmation (higher confidence) should win over decay (lower)."""
        local = {"id": "obs-1", "confidence": 0.8, "confirmations": 0,
                 "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "obs-1", "confidence": 0.3, "confirmations": 0,
                  "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        assert result["confidence"] == 0.8  # higher wins even though remote has newer version

    def test_pruned_true_wins(self):
        """Pruning is irreversible — pruned=True always wins."""
        local = {"id": "obs-1", "pruned": 1, "confidence": 0.5, "confirmations": 0,
                 "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "obs-1", "pruned": 0, "confidence": 0.5, "confirmations": 0,
                  "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        assert result["pruned"] == 1

    def test_pruned_true_wins_reverse(self):
        """Remote prune should also propagate."""
        local = {"id": "obs-1", "pruned": 0, "confidence": 0.5, "confirmations": 0,
                 "_sync_version": 2, "_node_id": "n1"}
        remote = {"id": "obs-1", "pruned": 1, "confidence": 0.5, "confirmations": 0,
                  "_sync_version": 1, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        assert result["pruned"] == 1

    def test_knowledge_id_prefer_non_null(self):
        """If one side linked to knowledge, preserve that link."""
        local = {"id": "obs-1", "knowledge_id": "k-42", "confidence": 0.5,
                 "confirmations": 0, "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "obs-1", "knowledge_id": None, "confidence": 0.5,
                  "confirmations": 0, "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        assert result["knowledge_id"] == "k-42"

    def test_pattern_id_prefer_non_null(self):
        """If one side linked to pattern, preserve that link."""
        local = {"id": "obs-1", "pattern_id": None, "confidence": 0.5,
                 "confirmations": 0, "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "obs-1", "pattern_id": "p-7", "confidence": 0.5,
                  "confirmations": 0, "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        assert result["pattern_id"] == "p-7"

    def test_stability_days_takes_higher(self):
        """Higher stability = more confirmed = should win."""
        local = {"id": "obs-1", "stability_days": 15.0, "confidence": 0.5,
                 "confirmations": 0, "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "obs-1", "stability_days": 3.0, "confidence": 0.5,
                  "confirmations": 0, "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        assert result["stability_days"] == 15.0

    def test_last_confirmed_at_takes_later(self):
        """More recent confirmation timestamp wins."""
        local = {"id": "obs-1", "last_confirmed_at": "2026-03-05T10:00:00",
                 "confidence": 0.5, "confirmations": 0,
                 "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "obs-1", "last_confirmed_at": "2026-03-07T15:30:00",
                  "confidence": 0.5, "confirmations": 0,
                  "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        assert result["last_confirmed_at"] == "2026-03-07T15:30:00"

    def test_concurrent_prune_and_confirm(self):
        """Node A prunes while Node B confirms — prune wins but keeps high confidence."""
        local = {"id": "obs-1", "pruned": 1, "confidence": 0.2,
                 "confirmations": 3, "stability_days": 5.0,
                 "_sync_version": 5, "_node_id": "n1"}
        remote = {"id": "obs-1", "pruned": 0, "confidence": 0.9,
                  "confirmations": 8, "stability_days": 12.0,
                  "_sync_version": 5, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        assert result["pruned"] == 1          # prune wins (irreversible)
        assert result["confidence"] == 0.9    # but keep higher confidence
        assert result["confirmations"] == 8   # and higher confirmations
        assert result["stability_days"] == 12.0

    def test_merge_returns_field_list(self):
        """Merged fields should be reported when merge overrides LWW."""
        # Remote wins LWW (version 2 > 1), but local has higher confidence
        local = {"id": "obs-1", "confidence": 0.9, "confirmations": 2,
                 "pruned": 0, "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "obs-1", "confidence": 0.3, "confirmations": 5,
                  "pruned": 0, "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        # confidence was merged (local's 0.9 overrode LWW winner's 0.3)
        assert "confidence" in merged
        # confirmations was NOT merged — LWW winner already had the max (5)
        assert "confirmations" not in merged

    def test_merge_reports_confirmations_when_overridden(self):
        """confirmations should be in merged list when it overrides LWW."""
        # Remote wins LWW (version 2), but local has higher confirmations
        local = {"id": "obs-1", "confidence": 0.3, "confirmations": 10,
                 "pruned": 0, "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "obs-1", "confidence": 0.8, "confirmations": 2,
                  "pruned": 0, "_sync_version": 2, "_node_id": "n2"}
        result, merged = _merge_observation(local, remote)
        assert result["confirmations"] == 10
        assert "confirmations" in merged


class TestResolveConflict:
    def test_both_none_returns_none(self):
        assert resolve_conflict("patterns", None, None) is None

    def test_local_none_returns_remote(self):
        remote = {"id": "p-1", "title": "Remote"}
        result = resolve_conflict("patterns", None, remote)
        assert result["title"] == "Remote"

    def test_remote_none_keeps_local_for_lww(self):
        local = {"id": "p-1", "title": "Local"}
        result = resolve_conflict("work_items", local, None)
        assert result["title"] == "Local"

    def test_remote_none_deletes_for_tombstone(self):
        local = {"id": "p-1", "title": "Local"}
        result = resolve_conflict("patterns", local, None, strategy=ConflictStrategy.TOMBSTONE)
        assert result is None

    def test_default_strategy_for_patterns_is_merge(self):
        local = {"id": "p-1", "confirmations": 10, "applications": 0,
                 "confidence": "high", "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "p-1", "confirmations": 3, "applications": 5,
                  "confidence": "medium", "_sync_version": 2, "_node_id": "n2"}
        result = resolve_conflict("patterns", local, remote)
        assert result["confirmations"] == 10  # merge: max
        assert result["applications"] == 5   # merge: max

    def test_default_strategy_for_work_items_is_lww(self):
        local = {"id": "wi-1", "title": "Local", "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "wi-1", "title": "Remote", "_sync_version": 2, "_node_id": "n2"}
        result = resolve_conflict("work_items", local, remote)
        assert result["title"] == "Remote"

    def test_explicit_strategy_override(self):
        local = {"id": "p-1", "title": "Local", "_sync_version": 5, "_node_id": "n1"}
        remote = {"id": "p-1", "title": "Remote", "_sync_version": 3, "_node_id": "n2"}
        # Force LWW even though patterns default to MERGE
        result = resolve_conflict("patterns", local, remote, strategy=ConflictStrategy.LWW)
        assert result["title"] == "Local"  # version 5 > 3

    def test_null_confirmations_treated_as_zero(self):
        local = {"id": "p-1", "confirmations": None, "applications": None,
                 "confidence": "low", "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "p-1", "confirmations": 3, "applications": 2,
                  "confidence": "medium", "_sync_version": 2, "_node_id": "n2"}
        result = resolve_conflict("patterns", local, remote)
        assert result["confirmations"] == 3


class TestAuditLog:
    def setup_method(self):
        """Clear audit log before each test."""
        flush_audit_log()

    def test_conflict_generates_audit_entry(self):
        local = {"id": "p-1", "title": "L", "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "p-1", "title": "R", "_sync_version": 2, "_node_id": "n2"}
        resolve_conflict("work_items", local, remote)
        entries = flush_audit_log()
        assert len(entries) == 1
        assert entries[0]["table"] == "work_items"
        assert entries[0]["row_id"] == "p-1"
        assert entries[0]["strategy"] == "lww"

    def test_no_audit_for_one_sided(self):
        """No audit entry when only one side exists (not a real conflict)."""
        resolve_conflict("work_items", None, {"id": "p-1", "title": "R"})
        entries = flush_audit_log()
        assert len(entries) == 0

    def test_flush_clears_log(self):
        local = {"id": "p-1", "title": "L", "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "p-1", "title": "R", "_sync_version": 2, "_node_id": "n2"}
        resolve_conflict("work_items", local, remote)
        first = flush_audit_log()
        assert len(first) == 1
        second = flush_audit_log()
        assert len(second) == 0

    def test_merge_records_merged_fields(self):
        """Audit should record which fields were merged (overrode LWW)."""
        # Remote wins LWW (version 2), but local has higher confidence AND confirmations
        local = {"id": "obs-1", "confidence": 0.9, "confirmations": 10,
                 "pruned": 0, "_sync_version": 1, "_node_id": "n1"}
        remote = {"id": "obs-1", "confidence": 0.3, "confirmations": 2,
                  "pruned": 0, "_sync_version": 2, "_node_id": "n2"}
        resolve_conflict("observations", local, remote)
        entries = flush_audit_log()
        assert len(entries) == 1
        # Both confidence and confirmations overrode the LWW winner
        assert "confidence" in entries[0]["merged_fields"]
        assert "confirmations" in entries[0]["merged_fields"]
