"""Verify consolidation dedup stage merges similar entries correctly.

Tests cover:
- _get_text: correct text extraction from evidence items
- _merge_items: confidence/description/tags merged correctly
- dedup_json_file: near-duplicates merged, non-duplicates kept, dry-run safe
- Edge cases: short descriptions skipped, single-item files untouched
"""
import json
import pytest
from pathlib import Path
from pulse.daemons.synthesis.core.pulse_dedup import (
    _get_text,
    _merge_items,
    dedup_json_file,
    MERGE_THRESHOLD,
    EXACT_THRESHOLD,
    MIN_DESCRIPTION_LEN,
)


# ---------------------------------------------------------------------------
# _get_text
# ---------------------------------------------------------------------------

def test_get_text_prefers_description():
    item = {"description": "use filelock for atomic writes", "title": "ignored"}
    assert "filelock" in _get_text(item)
    assert "atomic writes" in _get_text(item)


def test_get_text_falls_back_to_title():
    item = {"title": "always query brain first"}
    assert "always query brain first" == _get_text(item)


def test_get_text_combines_multiple_fields():
    item = {"description": "brain query", "summary": "mandatory step"}
    text = _get_text(item)
    assert "brain query" in text
    assert "mandatory step" in text


def test_get_text_empty_item_returns_empty():
    assert _get_text({}) == ""


# ---------------------------------------------------------------------------
# _merge_items
# ---------------------------------------------------------------------------

def test_merge_items_bumps_consolidation_count():
    keeper = {"description": "short desc", "consolidation_count": 1}
    absorbed = {"description": "also short desc here"}
    result = _merge_items(keeper, absorbed)
    assert result["consolidation_count"] == 2


def test_merge_items_keeps_longer_description():
    keeper = {"description": "short"}
    absorbed = {"description": "a much longer and richer description of the same concept"}
    result = _merge_items(keeper, absorbed)
    assert result["description"] == absorbed["description"]


def test_merge_items_keeps_shorter_if_keeper_richer():
    long_desc = "brain first is mandatory before every implementation step always"
    keeper = {"description": long_desc}
    absorbed = {"description": "brain first"}
    result = _merge_items(keeper, absorbed)
    assert result["description"] == long_desc


def test_merge_items_takes_higher_confidence():
    keeper = {"description": "some text", "confidence": 0.5}
    absorbed = {"description": "some text too", "confidence": 0.9}
    result = _merge_items(keeper, absorbed)
    assert result["confidence"] == 0.9


def test_merge_items_keeps_own_high_confidence():
    keeper = {"description": "some text", "confidence": 0.95}
    absorbed = {"description": "some text here", "confidence": 0.3}
    result = _merge_items(keeper, absorbed)
    assert result["confidence"] == 0.95


def test_merge_items_unions_tags():
    keeper = {"description": "a thing", "tags": ["brain", "memory"]}
    absorbed = {"description": "a thing too", "tags": ["memory", "pipeline"]}
    result = _merge_items(keeper, absorbed)
    assert set(result["tags"]) == {"brain", "memory", "pipeline"}


def test_merge_items_sets_last_consolidated_timestamp():
    keeper = {"description": "some text"}
    absorbed = {"description": "some other text"}
    result = _merge_items(keeper, absorbed)
    assert "_last_consolidated" in result


def test_merge_items_initializes_consolidation_count_from_zero():
    keeper = {"description": "item without count"}
    absorbed = {"description": "similar item without count"}
    result = _merge_items(keeper, absorbed)
    assert result["consolidation_count"] == 2  # 1 (default) + 1


# ---------------------------------------------------------------------------
# dedup_json_file — core merge logic
# ---------------------------------------------------------------------------

def _write_json(path: Path, data) -> Path:
    path.write_text(json.dumps(data), encoding="utf-8")
    return path


def test_dedup_merges_near_duplicate_entries(tmp_path):
    """Two items with high word overlap (>= 0.75) must be merged into one."""
    items = [
        {"description": "always use filelock for atomic brain writes to prevent corruption"},
        {"description": "always use filelock for atomic brain writes to avoid corruption"},
    ]
    f = _write_json(tmp_path / "evidence.json", items)
    stats = dedup_json_file(f)
    assert stats["merged"] == 1
    remaining = json.loads(f.read_text())
    assert len(remaining) == 1


def test_dedup_keeps_distinct_entries(tmp_path):
    """Two entirely different items must not be merged."""
    items = [
        {"description": "always use filelock for atomic brain writes and never skip"},
        {"description": "python subprocess encoding must specify utf-8 on windows platform"},
    ]
    f = _write_json(tmp_path / "evidence.json", items)
    stats = dedup_json_file(f)
    assert stats["merged"] == 0
    remaining = json.loads(f.read_text())
    assert len(remaining) == 2


def test_dedup_absorbs_exact_duplicates(tmp_path):
    """Exact duplicate strings (sim ~1.0 >= EXACT_THRESHOLD) must be absorbed."""
    text = "brain first is mandatory before every code implementation always run query"
    items = [{"description": text}, {"description": text}]
    f = _write_json(tmp_path / "evidence.json", items)
    stats = dedup_json_file(f)
    assert stats["merged"] == 1
    remaining = json.loads(f.read_text())
    assert len(remaining) == 1


def test_dedup_skips_short_descriptions(tmp_path):
    """Items whose text is shorter than MIN_DESCRIPTION_LEN chars must not be compared."""
    items = [
        {"description": "x"},  # too short
        {"description": "x"},  # too short
        {"description": "a longer entry that is definitely over the minimum length threshold"},
    ]
    f = _write_json(tmp_path / "evidence.json", items)
    stats = dedup_json_file(f)
    # Short items are skipped — only the one long item survives comparison unmerged
    assert stats["merged"] == 0


def test_dedup_single_item_file_unchanged(tmp_path):
    """A file with one item must be returned untouched."""
    items = [{"description": "only entry in this brain file nothing to compare against"}]
    f = _write_json(tmp_path / "evidence.json", items)
    stats = dedup_json_file(f)
    assert stats["merged"] == 0
    assert stats["original"] == 1


def test_dedup_dry_run_does_not_write(tmp_path):
    """dry_run=True must not modify the file even when merges are found."""
    items = [
        {"description": "always use filelock for atomic brain writes to prevent corruption"},
        {"description": "always use filelock for atomic brain writes to avoid corruption"},
    ]
    f = _write_json(tmp_path / "evidence.json", items)
    original_content = f.read_text()
    stats = dedup_json_file(f, dry_run=True)
    assert stats["merged"] == 1  # merge detected
    assert f.read_text() == original_content  # file untouched


def test_dedup_multiple_items_greedy_merge(tmp_path):
    """With 3 near-duplicate items, greedy algorithm absorbs 2 into the first."""
    base = "always run brain query before every implementation step in pulse system"
    items = [
        {"description": base},
        {"description": "always run brain query before every implementation step in pulse"},
        {"description": "always run brain query before every implementation in pulse system"},
    ]
    f = _write_json(tmp_path / "evidence.json", items)
    stats = dedup_json_file(f)
    assert stats["merged"] >= 1
    remaining = json.loads(f.read_text())
    assert len(remaining) < 3


def test_dedup_merged_item_has_higher_confidence(tmp_path):
    """After merge, the surviving item takes the max confidence from both."""
    items = [
        {"description": "always use filelock for atomic brain writes to prevent data loss", "confidence": 0.5},
        {"description": "always use filelock for atomic brain writes to avoid data loss", "confidence": 0.9},
    ]
    f = _write_json(tmp_path / "evidence.json", items)
    dedup_json_file(f)
    remaining = json.loads(f.read_text())
    assert remaining[0]["confidence"] == 0.9


def test_dedup_merged_item_bumps_consolidation_count(tmp_path):
    """The surviving merged item must have consolidation_count incremented."""
    items = [
        {"description": "always use filelock for atomic brain writes to prevent data loss"},
        {"description": "always use filelock for atomic brain writes to avoid data loss"},
    ]
    f = _write_json(tmp_path / "evidence.json", items)
    dedup_json_file(f)
    remaining = json.loads(f.read_text())
    assert remaining[0].get("consolidation_count", 0) >= 2


def test_dedup_non_list_json_returns_zero_merged(tmp_path):
    """A JSON file that isn't a list must return merged=0 without crashing."""
    f = tmp_path / "evidence.json"
    f.write_text(json.dumps({"key": "not a list"}), encoding="utf-8")
    stats = dedup_json_file(f)
    assert stats["merged"] == 0


def test_dedup_merge_threshold_boundary(tmp_path):
    """Items at exactly the MERGE_THRESHOLD similarity must be merged."""
    # Craft two strings with exactly MERGE_THRESHOLD Jaccard overlap
    # With threshold=0.75: need |A∩B|/|A∪B| >= 0.75
    # 6 shared words out of 8 union = 0.75 exactly
    a_words = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta"]
    b_words = ["alpha", "beta", "gamma", "delta", "epsilon", "zeta", "iota", "kappa"]
    # Shared: alpha beta gamma delta epsilon zeta (6), Union: 10 → 0.6, not quite 0.75
    # Let's use: 3 shared / 4 union = 0.75
    a_words = ["word1", "word2", "word3", "word4"]
    b_words = ["word1", "word2", "word3", "word5"]
    # Jaccard = 3/5 = 0.6 — below threshold, won't merge. Let's try 6/8=0.75
    a_words = ["w1", "w2", "w3", "w4", "w5", "w6", "w7", "w8"]
    b_words = ["w1", "w2", "w3", "w4", "w5", "w6", "w9", "w10"]
    # shared=6, union=10, jaccard=0.6 — still not right

    # Use threshold check directly: pairs with sim >= MERGE_THRESHOLD are merged
    from pulse.core.brain_analysis import text_similarity
    text_a = "always query brain before implementing any code change in pulse system now"
    text_b = "always query brain before implementing any code change in pulse system here"
    sim = text_similarity(text_a, text_b)
    items = [{"description": text_a}, {"description": text_b}]
    f = _write_json(tmp_path / "evidence.json", items)
    stats = dedup_json_file(f)
    if sim >= MERGE_THRESHOLD:
        assert stats["merged"] == 1
    else:
        assert stats["merged"] == 0
