"""Tests for the Unified Memory Consolidation Bridge — Sprint 0.

Tests: schema creation, PHI gate, deduplication, audit trail, store_with_audit.
"""

import tempfile
from pathlib import Path

import pytest

from familiar.core.consolidation_bridge import (
    ACTION_ADD,
    ACTION_SKIP_DEDUP,
    ACTION_SKIP_ENCRYPTION,
    ConsolidationBridge,
    SOURCE_ANALYTICS,
    SOURCE_CREDENTIALS,
    SOURCE_EMAIL,
    SOURCE_WORKSPACE,
)
from familiar.core.memory_tree import MemoryTree


@pytest.fixture
def bridge(tmp_path):
    """Create a ConsolidationBridge with a temp MemoryTree and PII enabled for testing."""
    tree = MemoryTree(db_path=tmp_path / "test_memory.db")
    b = ConsolidationBridge(tree=tree)
    # Enable PII detection for tests so PHI scrubbing gate works
    b._is_pii_enabled = lambda: True
    return b


# ── Schema ──


def test_schema_created(bridge):
    """consolidation_log table exists after init."""
    conn = bridge._tree._connect()
    try:
        tables = [
            r[0]
            for r in conn.execute(
                "SELECT name FROM sqlite_master WHERE type='table'"
            ).fetchall()
        ]
        assert "consolidation_log" in tables
    finally:
        conn.close()


def test_schema_has_indexes(bridge):
    """consolidation_log has the expected indexes."""
    conn = bridge._tree._connect()
    try:
        indexes = [
            r[0]
            for r in conn.execute(
                "SELECT name FROM sqlite_master WHERE type='index'"
            ).fetchall()
        ]
        assert "idx_consol_source" in indexes
        assert "idx_consol_timestamp" in indexes
    finally:
        conn.close()


# ── PHI Gate ──


def test_phi_gate_clean_text(bridge):
    """Clean text passes through unchanged."""
    text = "LLM Usage: 42 calls, 10000 tokens"
    scrubbed, found = bridge.phi_gate(text)
    assert not found
    assert scrubbed == text


def test_phi_gate_scrubs_email(bridge):
    """Email addresses are redacted."""
    text = "Contact user@example.com for details"
    scrubbed, found = bridge.phi_gate(text)
    assert found
    assert "[EMAIL]" in scrubbed
    assert "user@example.com" not in scrubbed


def test_phi_gate_scrubs_phone(bridge):
    """Phone numbers are redacted."""
    text = "Call 555-123-4567 for help"
    scrubbed, found = bridge.phi_gate(text)
    assert found
    assert "[PHONE]" in scrubbed
    assert "555-123-4567" not in scrubbed


def test_phi_gate_scrubs_ssn(bridge):
    """SSN patterns are redacted."""
    text = "SSN is 123-45-6789"
    scrubbed, found = bridge.phi_gate(text)
    assert found
    assert "[SSN]" in scrubbed
    assert "123-45-6789" not in scrubbed


def test_phi_gate_scrubs_card(bridge):
    """Credit card patterns are redacted."""
    text = "Card 4111 1111 1111 1111"
    scrubbed, found = bridge.phi_gate(text)
    assert found
    assert "[CARD]" in scrubbed


def test_phi_gate_disabled_passes_through(tmp_path):
    """When PII toggle is OFF, PHI gate passes text unchanged."""
    tree = MemoryTree(db_path=tmp_path / "test_memory_nophi.db")
    b = ConsolidationBridge(tree=tree)
    b._is_pii_enabled = lambda: False  # PII OFF
    text = "Patient SSN 123-45-6789, email john@hospital.org"
    scrubbed, found = b.phi_gate(text)
    assert not found
    assert scrubbed == text  # unchanged — user chose to disable


def test_phi_gate_empty_text(bridge):
    """Empty string passes through."""
    scrubbed, found = bridge.phi_gate("")
    assert not found
    assert scrubbed == ""


# ── Deduplication ──


def test_dedup_no_match(bridge):
    """Returns None for new content."""
    result = bridge.dedup_check("New Title", "New body content")
    assert result is None


def test_dedup_detects_duplicate(bridge):
    """Returns memory_id for duplicate content."""
    entry = bridge._tree.store(title="Test Title", body="Test body")
    result = bridge.dedup_check("Test Title", "Test body")
    assert result == entry.memory_id


# ── Audit Trail ──


def test_audit_log_written(bridge):
    """log_consolidation writes to the consolidation_log table."""
    bridge.log_consolidation(
        source=SOURCE_ANALYTICS,
        action=ACTION_ADD,
        memory_id="test-123",
        source_id="analytics-456",
        details="Test consolidation entry",
        phi_detected=False,
    )
    log = bridge.get_log(limit=10)
    assert len(log) == 1
    assert log[0]["source"] == SOURCE_ANALYTICS
    assert log[0]["action"] == ACTION_ADD
    assert log[0]["memory_id"] == "test-123"


def test_audit_log_phi_flag(bridge):
    """PHI detection flag is recorded in the audit log."""
    bridge.log_consolidation(
        source=SOURCE_EMAIL,
        action=ACTION_ADD,
        details="Entry with PHI",
        phi_detected=True,
    )
    log = bridge.get_log()
    assert log[0]["phi_detected"] == 1


def test_audit_log_filter_by_source(bridge):
    """Can filter log entries by source."""
    bridge.log_consolidation(source=SOURCE_ANALYTICS, action=ACTION_ADD)
    bridge.log_consolidation(source=SOURCE_EMAIL, action=ACTION_ADD)
    analytics_log = bridge.get_log(source=SOURCE_ANALYTICS)
    assert len(analytics_log) == 1
    assert analytics_log[0]["source"] == SOURCE_ANALYTICS


# ── Store with Audit ──


def test_store_with_audit_creates_memory(bridge):
    """store_with_audit creates a memory and logs the operation."""
    mid = bridge.store_with_audit(
        source=SOURCE_ANALYTICS,
        title="LLM Usage: 2026-03-22",
        body="qwen2.5: 38 calls, 5399 tokens in, $0.00",
        tier="long",
        tags=["analytics", "llm_usage"],
        importance=0.3,
        source_id="daily_2026-03-22",
    )
    assert mid is not None

    # Verify memory exists
    entry = bridge._tree.get(mid)
    assert entry is not None
    assert "LLM Usage" in entry.title
    assert SOURCE_ANALYTICS in entry.tags

    # Verify audit log
    log = bridge.get_log()
    assert len(log) == 1
    assert log[0]["action"] == ACTION_ADD
    assert log[0]["memory_id"] == mid


def test_store_with_audit_dedup(bridge):
    """Duplicate content is skipped with audit."""
    mid1 = bridge.store_with_audit(
        source=SOURCE_ANALYTICS,
        title="Test Entry",
        body="Same content",
    )
    mid2 = bridge.store_with_audit(
        source=SOURCE_ANALYTICS,
        title="Test Entry",
        body="Same content",
    )
    assert mid1 is not None
    assert mid2 is None  # deduplicated

    log = bridge.get_log()
    assert len(log) == 2
    assert log[0]["action"] == ACTION_SKIP_DEDUP  # most recent first


def test_store_with_audit_phi_scrubbing(bridge):
    """PHI is scrubbed before storage and flagged in audit."""
    mid = bridge.store_with_audit(
        source=SOURCE_EMAIL,
        title="Sender: user@example.com",
        body="Contact 555-123-4567",
    )
    assert mid is not None

    entry = bridge._tree.get(mid)
    assert "user@example.com" not in entry.title
    assert "[EMAIL]" in entry.title
    assert "555-123-4567" not in entry.body
    assert "[PHONE]" in entry.body

    log = bridge.get_log()
    assert log[0]["phi_detected"] == 1


def test_store_with_audit_encryption_required(bridge):
    """Refuses to store PHI-sensitive data without encryption."""
    # Bridge's tree doesn't have encryption enabled
    mid = bridge.store_with_audit(
        source=SOURCE_EMAIL,
        title="Sensitive Data",
        body="PHI content",
        require_encryption=True,
    )
    assert mid is None  # refused

    log = bridge.get_log()
    assert log[0]["action"] == ACTION_SKIP_ENCRYPTION


# ── Update with Audit ──


def test_update_with_audit(bridge):
    """update_with_audit creates new entry (supersedes old) and logs."""
    mid = bridge.store_with_audit(
        source=SOURCE_ANALYTICS,
        title="Monthly Spend",
        body="Total: $5.00",
    )
    success = bridge.update_with_audit(
        source=SOURCE_ANALYTICS,
        memory_id=mid,
        title="Monthly Spend",
        body="Total: $7.21",
    )
    assert success

    # Old entry should be invalidated
    old_entry = bridge._tree.get(mid)
    assert old_entry.status == "invalidated"

    # New entry should exist with updated body (find via search)
    results = bridge._tree.search("Monthly Spend", limit=5)
    active = [r for r in results if r.status == "active"]
    assert len(active) >= 1
    assert "$7.21" in active[0].body

    log = bridge.get_log()
    assert any(l["action"] == "update" for l in log)


# ── Edge Creation ──


def test_add_edge(bridge):
    """add_edge creates a relationship between memories."""
    mid1 = bridge.store_with_audit(source=SOURCE_ANALYTICS, title="Day 1", body="Usage day 1")
    mid2 = bridge.store_with_audit(source=SOURCE_ANALYTICS, title="Day 2", body="Usage day 2")
    bridge.add_edge(mid2, mid1, relation_type="derived_from")

    edges = bridge._tree.get_edges(mid2)
    assert len(edges) == 1
    assert edges[0]["relation_type"] == "derived_from"
    assert edges[0]["to_id"] == mid1


# ── Find Existing ──


def test_find_existing(bridge):
    """find_existing locates a consolidated memory by source and title prefix."""
    bridge.store_with_audit(
        source=SOURCE_ANALYTICS,
        title="Monthly LLM Spend: $5.35 (March 2026)",
        body="Details...",
        tags=["analytics", "spend"],
    )
    found = bridge.find_existing(SOURCE_ANALYTICS, "Monthly LLM Spend")
    assert found is not None


def test_find_existing_not_found(bridge):
    """find_existing returns None when no match."""
    found = bridge.find_existing(SOURCE_ANALYTICS, "Nonexistent")
    assert found is None


# ── PHI Stats ──


def test_phi_stats(bridge):
    """get_phi_stats returns correct counts."""
    bridge.store_with_audit(source=SOURCE_EMAIL, title="Clean title", body="Clean body")
    bridge.store_with_audit(source=SOURCE_EMAIL, title="PHI: user@test.com", body="OK")
    stats = bridge.get_phi_stats()
    assert stats["total_operations"] == 2
    assert stats["phi_detections"] == 1
    assert SOURCE_EMAIL in stats["by_source"]


# ── Status ──


def test_status(bridge):
    """get_status returns current bridge state."""
    status = bridge.get_status()
    assert status.last_run is None
    assert isinstance(status.results, dict)
    assert isinstance(status.phi_scrubbing_available, bool)


# ── Consolidate All (stubs) ──


def test_consolidate_all_runs(bridge):
    """consolidate_all executes without error and returns results for all sources."""
    results = bridge.consolidate_all()
    assert SOURCE_ANALYTICS in results
    assert SOURCE_CREDENTIALS in results
    assert SOURCE_EMAIL in results
    assert SOURCE_WORKSPACE in results
    # Analytics may consolidate real data if analytics.db exists; others are stubs
    assert results[SOURCE_ANALYTICS].errors == 0
    assert results[SOURCE_CREDENTIALS].errors == 0
