#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Stage 2d: Reactivation-gated reconsolidation — merge similar entries."""
import hashlib
import re
from datetime import datetime, timezone

from pulse.core.brain_utils import (
    LESSONS_DIR, PATTERNS_DIR, FAILS_DIR,
    RETRIEVAL_METRICS, load_json_dict, text_similarity, read_jsonl_entries,
)
from pulse.core.temporal import get_half_life, _days_since, reinforce
from pulse.core.uncertainty import bayesian_update
from .stage_io import (
    _MD_TO_JSONL, _get_entries_for_file,
    _load_processed_hashes, _save_processed_hashes, _entry_hash,
)


def stage_reconsolidation(verbose=False):
    """Reactivation-gated reconsolidation — merge similar entries.

    Neuroscience: only recently-retrieved memories become labile (Nader 2000).
    Only processes entries retrieved since last consolidation cycle.
    Uses bounded hash tracking to skip entries already processed in prior cycles.
    """
    retrieval_data = load_json_dict(RETRIEVAL_METRICS)
    retrieved_keys = set(retrieval_data.get("items", {}).keys())

    processed = _load_processed_hashes()
    merged_total = 0
    new_hashes = 0

    for md_file in [
        LESSONS_DIR / "auto_lessons.md",
        PATTERNS_DIR / "auto_patterns.md",
        FAILS_DIR / "auto_fails.md",
    ]:
        raw_entries = _get_entries_for_file(md_file)
        if len(raw_entries) < 2:
            continue

        # Reactivation gate: only consider recently-retrieved entries
        reactivated = [
            e for e in raw_entries
            if hashlib.md5(e["title"][:80].lower().strip().encode("utf-8")).hexdigest()[:12]
            in retrieved_keys
        ]
        if len(reactivated) >= 2:
            raw_entries = reactivated
            if verbose:
                print(f"  [RECONSOLIDATION] Reactivation gate: {len(reactivated)} retrieved")

        entries = []
        for e in raw_entries:
            ehash = _entry_hash(e["title"])
            e.update({"merged": False, "hash": ehash, "seen": ehash in processed})
            entries.append(e)

        for i in range(len(entries)):
            if entries[i]["merged"] or entries[i]["seen"]:
                continue
            for j in range(i + 1, len(entries)):
                if entries[j]["merged"]:
                    continue
                sim = text_similarity(entries[i]["title"][:80], entries[j]["title"][:80])
                if sim >= 0.6:
                    if entries[j]["conf"] > entries[i]["conf"]:
                        i, j = j, i
                    # Dimension E: Bayesian update instead of flat +0.05
                    old_conf = max(entries[i]["conf"], entries[j]["conf"])
                    old_ec = entries[i].get("evidence_count", 1)
                    new_conf, new_ec = bayesian_update(old_conf, old_ec, True)
                    entries[i]["conf"] = new_conf
                    entries[i]["evidence_count"] = new_ec
                    entries[i]["consolidation_count"] = entries[i].get("consolidation_count", 1) + 1
                    # Dimension D: reset decay clock on merge
                    reinforce(entries[i])
                    # MD body update (fallback path)
                    if not entries[i].get("_from_jsonl") and entries[i].get("body"):
                        cc_match = re.search(r"(\*\*Consolidation Count:\*\*\s*)(\d+)", entries[i]["body"])
                        if cc_match:
                            new_cc = int(cc_match.group(2)) + 1
                            entries[i]["body"] = re.sub(
                                r"\*\*Consolidation Count:\*\*\s*\d+",
                                f"**Consolidation Count:** {new_cc}", entries[i]["body"])
                    entries[j]["merged"] = True
                    merged_total += 1
                    # Dimension C: Provenance
                    try:
                        from pulse.brain.integrity.provenance import append_provenance
                        fp_i = hashlib.md5(entries[i]["title"][:80].lower().strip().encode("utf-8")).hexdigest()[:12]
                        fp_j = hashlib.md5(entries[j]["title"][:80].lower().strip().encode("utf-8")).hexdigest()[:12]
                        append_provenance(fp_i, {
                            "stage": "consolidation",
                            "action": "reconsolidation_merge",
                            "merged_with": fp_j,
                        })
                    except Exception:
                        pass

        for e in entries:
            if not e["merged"] and not e["seen"]:
                # P3-5: store timestamp so expiry logic can prune after 14 days
                processed[e["hash"]] = datetime.now(timezone.utc).isoformat()
                new_hashes += 1

        # Dimension B: track which agents' items survived consolidation
        try:
            from pulse.admin.audit.agent_trust import _load_trust, _save_trust
            trust_data = _load_trust()
            agents_dict = trust_data.setdefault("agents", {})
            for e in entries:
                src = e.get("source", "")
                if not src or src == "unknown":
                    continue
                rec = agents_dict.setdefault(src, {})
                if not e["merged"]:
                    rec["survived_consolidation"] = rec.get("survived_consolidation", 0) + 1
                rec["contributions"] = rec.get("contributions", 0) + 1
            _save_trust(trust_data)
        except Exception:
            pass

        # Write back to JSONL (remove merged entries by fingerprint)
        if any(e["merged"] for e in entries):
            jsonl_path = _MD_TO_JSONL.get(md_file)
            if jsonl_path:
                merged_fps = {e.get("fingerprint") for e in entries if e["merged"] and e.get("fingerprint")}
                # Build survivor updates: fingerprint -> {confidence, consolidation_count, evidence_count}
                survivor_updates = {}
                for e in entries:
                    if not e["merged"] and e.get("fingerprint"):
                        survivor_updates[e["fingerprint"]] = {
                            "confidence": e["conf"],
                            "consolidation_count": e.get("consolidation_count", 1),
                            "evidence_count": e.get("evidence_count", 1),
                        }

                # SQLite-first path
                _sqlite_ok = False
                try:
                    from pulse.core.brain_db import get_conn
                    _table_map = {
                        "auto_lessons.jsonl": "lessons",
                        "auto_fails.jsonl": "failures",
                        "auto_patterns.jsonl": "patterns",
                    }
                    table = _table_map.get(jsonl_path.name)
                    if table and merged_fps:
                        conn = get_conn()
                        with conn:
                            fps_list = list(merged_fps)
                            placeholders = ",".join("?" * len(fps_list))
                            conn.execute(
                                f"DELETE FROM knowledge_fts WHERE item_id IN "
                                f"(SELECT id FROM {table} WHERE fingerprint IN ({placeholders}))",
                                fps_list,
                            )
                            conn.execute(
                                f"DELETE FROM {table} WHERE fingerprint IN ({placeholders})",
                                fps_list,
                            )
                            # Update survivors
                            for fp, updates in survivor_updates.items():
                                conn.execute(
                                    f"UPDATE {table} SET confidence=?, consolidation_count=?, "
                                    f"evidence_count=?, updated_at=strftime('%Y-%m-%dT%H:%M:%SZ','now') "
                                    f"WHERE fingerprint=?",
                                    (updates["confidence"], updates["consolidation_count"],
                                     updates["evidence_count"], fp),
                                )
                        _sqlite_ok = True
                except Exception:
                    _sqlite_ok = False

                # JSONL fallback
                if not _sqlite_ok:
                    from pulse.brain.save_writers import rewrite_jsonl
                    all_rows = read_jsonl_entries(jsonl_path)
                    surviving = [r for r in all_rows if r.get("fingerprint") not in merged_fps]
                    surv_by_fp = {r.get("fingerprint"): r for r in surviving}
                    for e in entries:
                        if not e["merged"] and e.get("fingerprint") in surv_by_fp:
                            r = surv_by_fp[e["fingerprint"]]
                            r["confidence"] = e["conf"]
                            r["consolidation_count"] = e.get("consolidation_count", 1)
                            r.setdefault("evidence_count", 1)
                            if e.get("evidence_count"):
                                r["evidence_count"] = e["evidence_count"]
                    rewrite_jsonl(jsonl_path, surviving)

    _save_processed_hashes(processed)
    if verbose:
        print(f"  [RECONSOLIDATION] Merged {merged_total} entries, tracked {new_hashes} new hashes")
    return {"status": "ok", "merged": merged_total, "new_hashes": new_hashes}
