#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Save Knowledge Ops: Refinement, correction, and broadcast operations.

Extracted from pulse_save.py per Law 7 (files <= 300 lines).

Operations:
  - refine_knowledge(): Phase 5.9 reconsolidation (fuzzy-match + replace)
  - flag_incorrect_knowledge(): Fix 9 (dispute + correct)
  - broadcast_critical_failure(): Fix 10 (alert other agents)
"""
import re


from pulse.core.brain_utils import (
    atomic_update_json, text_similarity, HIPPOCAMPUS_DIR,
    LESSONS_JSONL, FAILS_JSONL, PATTERNS_JSONL, read_jsonl_entries,
)
from .save_writers import _now_iso, rewrite_jsonl

CORRECTIONS_FILE = HIPPOCAMPUS_DIR / "Decisions" / "corrections.json"
BROADCAST_FILE = HIPPOCAMPUS_DIR / "Evidence" / "Sessions" / "broadcast_alerts.json"
REFINEMENT_LOG = HIPPOCAMPUS_DIR / "Evidence" / "Metrics" / "refinement_log.json"

# JSONL files to search for refinement/flagging
_JSONL_FILES = {
    "lessons": LESSONS_JSONL,
    "patterns": PATTERNS_JSONL,
    "failures": FAILS_JSONL,
}


def broadcast_critical_failure(failure_text: str, agent_name: str) -> bool:
    """Broadcast high-salience failures so other agents see them at boot (Fix 10).

    Writes to broadcast_alerts.json (keeps last 10, auto-prunes).
    Only broadcasts if the failure text contains critical keywords.
    """
    critical_keywords = re.compile(
        r"(?:critical|data loss|security|corruption|regression|crash|breaking|"
        r"destroyed|deleted|wiped|lost|catastrophic)", re.IGNORECASE
    )
    if not critical_keywords.search(failure_text):
        return False

    BROADCAST_FILE.parent.mkdir(parents=True, exist_ok=True)

    def _updater(data):
        if not isinstance(data, dict):
            data = {"alerts": []}
        if "alerts" not in data:
            data["alerts"] = []
        data["alerts"].append({
            "failure": failure_text[:300],
            "agent": agent_name,
            "timestamp": _now_iso(),
            "read_by": [],
        })
        data["alerts"] = data["alerts"][-10:]
        data["last_updated"] = _now_iso()
        return data

    atomic_update_json(BROADCAST_FILE, _updater, default={"alerts": []})
    return True


def refine_knowledge(original_snippet: str, updated_text: str, reason: str,
                     agent_name: str = "unknown") -> dict:
    """Refine an existing lesson/pattern/failure with improved knowledge (Phase 5.9).

    Searches JSONL brain files for the original text using fuzzy matching (60% similarity).
    If found, updates the entry content and appends revision metadata.
    """
    result = {"refined": False, "file": "", "original": "", "revision_count": 0}

    if not original_snippet or not updated_text:
        return result

    if len(updated_text.strip()) < 25 or len(updated_text.split()) < 5:
        result["error"] = "Updated text too short (min 25 chars, 5 words)"
        return result

    snippet_lower = original_snippet.lower()

    for source_name, jsonl_path in _JSONL_FILES.items():
        entries = read_jsonl_entries(jsonl_path)
        if not entries:
            continue

        modified = False
        for entry in entries:
            entry_text = (entry.get("title") or "") + " " + (entry.get("content") or "")
            sim = text_similarity(snippet_lower, entry_text.lower())
            substring_match = snippet_lower in entry_text.lower()

            if sim >= 0.6 or substring_match:
                result["original"] = (entry.get("title") or entry.get("content", ""))[:100]

                # Check for same-day same-agent refinement
                revisions = entry.get("revisions", [])
                now_date = _now_iso()[:10]
                if any(r.get("date", "")[:10] == now_date and r.get("agent") == agent_name
                       for r in revisions):
                    result["error"] = "Already refined today by this agent"
                    return result

                # Apply refinement
                entry["title"] = updated_text[:80]
                entry["content"] = updated_text
                entry.setdefault("revisions", []).append({
                    "date": _now_iso(),
                    "agent": agent_name,
                    "previous": result["original"][:60],
                    "reason": reason[:100],
                })
                result["revision_count"] = len(entry.get("revisions", []))
                result["refined"] = True
                result["file"] = jsonl_path.name
                modified = True
                break

        if modified:
            # SQLite-first: update the refined entry by fingerprint
            try:
                from pulse.brain.save_writers.jsonl_store import _jsonl_to_table
                from pulse.core.brain_db import get_conn
                table = _jsonl_to_table(jsonl_path)
                if table:
                    # Find the entry that was modified (the one with the match)
                    refined_entry = next(
                        (e for e in entries if e.get("content") == updated_text), None
                    )
                    if refined_entry and refined_entry.get("fingerprint"):
                        conn = get_conn()
                        with conn:
                            conn.execute(
                                f"UPDATE {table} SET title = ?, content = ?, "
                                f"updated_at = strftime('%Y-%m-%dT%H:%M:%SZ','now') "
                                f"WHERE fingerprint = ?",
                                (refined_entry.get("title", "")[:80],
                                 updated_text,
                                 refined_entry["fingerprint"]),
                            )
            except Exception:
                pass  # SQLite unavailable — JSONL rewrite below is the fallback
            rewrite_jsonl(jsonl_path, entries)

            # Log refinement
            REFINEMENT_LOG.parent.mkdir(parents=True, exist_ok=True)

            def _updater(data):
                if not isinstance(data, dict):
                    data = {"refinements": [], "total": 0}
                if "refinements" not in data:
                    data["refinements"] = []
                data["refinements"].append({
                    "original": result["original"][:100],
                    "updated": updated_text[:100],
                    "reason": reason[:100],
                    "agent": agent_name,
                    "file": jsonl_path.name,
                    "revision_number": result["revision_count"],
                    "timestamp": _now_iso(),
                })
                data["refinements"] = data["refinements"][-200:]
                data["total"] = data.get("total", 0) + 1
                return data

            atomic_update_json(REFINEMENT_LOG, _updater, default={"refinements": [], "total": 0})
            break

    return result


def flag_incorrect_knowledge(query: str, correction: str, agent_name: str = "unknown") -> dict:
    """Flag a lesson/failure as incorrect and log the correction (Fix 9).

    Searches JSONL brain files for matching text, marks as DISPUTED,
    and logs to corrections.json.
    """
    result = {"flagged": False, "file": "", "correction_logged": False}
    query_lower = query.lower()

    for source_name, jsonl_path in _JSONL_FILES.items():
        entries = read_jsonl_entries(jsonl_path)
        if not entries:
            continue

        modified = False
        for entry in entries:
            entry_title = (entry.get("title") or entry.get("content", "")).lower()
            if query_lower in entry_title[:200]:
                if "[DISPUTED]" not in (entry.get("title") or ""):
                    entry["title"] = (entry.get("title", "") + " [DISPUTED]").strip()
                    entry.setdefault("disputes", []).append({
                        "agent": agent_name,
                        "correction": correction,
                        "timestamp": _now_iso(),
                    })
                    modified = True
                    result["flagged"] = True
                    result["file"] = jsonl_path.name
                break

        if modified:
            # SQLite-first: mark entry as DISPUTED by fingerprint
            try:
                from pulse.brain.save_writers.jsonl_store import _jsonl_to_table
                from pulse.core.brain_db import get_conn
                table = _jsonl_to_table(jsonl_path)
                if table:
                    flagged_entry = next(
                        (e for e in entries if "[DISPUTED]" in (e.get("title") or "")), None
                    )
                    if flagged_entry and flagged_entry.get("fingerprint"):
                        conn = get_conn()
                        with conn:
                            conn.execute(
                                f"UPDATE {table} SET title = ?, "
                                f"updated_at = strftime('%Y-%m-%dT%H:%M:%SZ','now') "
                                f"WHERE fingerprint = ?",
                                (flagged_entry["title"],
                                 flagged_entry["fingerprint"]),
                            )
            except Exception:
                pass  # SQLite unavailable — JSONL rewrite below is the fallback
            rewrite_jsonl(jsonl_path, entries)
            break

    CORRECTIONS_FILE.parent.mkdir(parents=True, exist_ok=True)

    def _updater(data):
        if not isinstance(data, dict):
            data = {"corrections": []}
        if "corrections" not in data:
            data["corrections"] = []
        data["corrections"].append({
            "query": query,
            "correction": correction,
            "flagged_by": agent_name,
            "timestamp": _now_iso(),
            "source_file": result.get("file", ""),
        })
        data["corrections"] = data["corrections"][-100:]
        return data

    atomic_update_json(CORRECTIONS_FILE, _updater, default={"corrections": []})
    result["correction_logged"] = True

    return result
