#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""Stages 2c/5: LTD decay and metacognitive quality audit."""
import hashlib
import logging
import re
from datetime import datetime, timezone, timedelta

from pulse.core.brain_utils import (
    LESSONS_DIR, PATTERNS_DIR, FAILS_DIR, EVIDENCE_METRICS_DIR,
    HIPPOCAMPUS_DIR, load_json, load_json_dict, save_json,
    now_iso as _now_iso, read_jsonl_entries,
)
from pulse.core.temporal import get_half_life, _days_since, temporal_confidence
from .stage_io import _MD_TO_JSONL, _get_entries_for_file

_ACTIONABLE = re.compile(
    r"(?:use|fix|add|remove|change|avoid|always|never|check|run|set|switch|"
    r"replace|install|update|create|delete|move|ensure|implement|deploy)", re.IGNORECASE
)


def stage_ltd_decay(verbose=False):
    """Apply temporal decay to SQLite knowledge tables (Dimension D -- Long-Term Depression).

    Updates decay_score column directly: power-law decay based on age and half-life,
    with retrieval bonus for accessed items.  Formula: (1 + age/HL)^-0.5
    """
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
    except Exception:
        return {"status": "skipped", "reason": "no db connection", "decayed": 0}
    decayed = 0
    _tables = ["lessons", "failures", "patterns", "facts", "intents", "evidence"]
    now = _now_iso()
    for table in _tables:
        try:
            rows = conn.execute(
                f"SELECT id, timestamp, type, access_count FROM {table} "
                "WHERE decay_score >= 0.5 AND timestamp != '' LIMIT 5000"
            ).fetchall()
        except Exception:
            continue
        updates = []
        for row in rows:
            ts_str = row["timestamp"] or ""
            if not ts_str:
                continue
            age = _days_since(ts_str)
            ktype = row["type"] or "lesson"
            hl = get_half_life(ktype)
            if age < 1.0:
                continue
            # Power-law decay: (1 + age/HL)^-0.5
            raw_decay = (1.0 + age / max(hl, 1.0)) ** -0.5
            # Retrieval bonus: accessed items decay 50% slower
            access = row["access_count"] or 0
            if access > 0:
                raw_decay = min(1.0, raw_decay + 0.1 * min(access, 5))
            new_score = round(max(0.05, min(1.0, raw_decay)), 4)
            if new_score < 0.99:
                updates.append((new_score, now, row["id"]))
        if updates:
            with conn:
                conn.executemany(
                    f"UPDATE {table} SET decay_score = ?, updated_at = ? WHERE id = ?",
                    updates,
                )
            decayed += len(updates)
            if verbose:
                print(f"  [LTD] {table}: {len(updates)} entries decayed")
    if verbose:
        print(f"  [LTD] Total: {decayed} entries decayed across {len(_tables)} tables")
    return {"status": "ok", "decayed": decayed}


def stage_metacognitive_audit(verbose=False):
    """Stage 5: Brain quality audit — flag vague/unused/contradicted items, then auto-fix."""
    report = {"vague": 0, "no_verb": 0, "unused_14d": 0, "contradicted": 0,
              "total_audited": 0, "timestamp": _now_iso()}
    retrieval_file = EVIDENCE_METRICS_DIR / "retrieval_metrics.json"
    retrieval_data = load_json_dict(retrieval_file)
    retrieved_keys = set(retrieval_data.get("items", {}).keys())
    cutoff_14d = datetime.now(timezone.utc) - timedelta(days=14)
    contradiction_file = HIPPOCAMPUS_DIR.parent / "state" / "contradiction_log.json"
    contradiction_data = load_json_dict(contradiction_file)
    repeat_counts = contradiction_data.get("repeat_counts", {})

    for md_file in [
        LESSONS_DIR / "auto_lessons.md",
        PATTERNS_DIR / "auto_patterns.md",
        FAILS_DIR / "auto_fails.md",
    ]:
        entries = _get_entries_for_file(md_file)
        for e in entries:
            title = e["title"]
            if not title:
                continue
            report["total_audited"] += 1
            if len(title) < 30:
                report["vague"] += 1
            if not _ACTIONABLE.search(title):
                report["no_verb"] += 1
            key = hashlib.md5(title[:80].lower().strip().encode("utf-8")).hexdigest()[:12]
            item_date_str = e.get("date", "")
            if item_date_str and key not in retrieved_keys:
                try:
                    item_date = datetime.fromisoformat(item_date_str[:10] + "T00:00:00+00:00")
                    if item_date < cutoff_14d:
                        report["unused_14d"] += 1
                except (ValueError, TypeError):
                    logging.debug("Suppressed exception in stage_metacognitive_audit", exc_info=True)
            if repeat_counts.get(title[:60].lower(), 0) >= 3:
                report["contradicted"] += 1

    report_file = EVIDENCE_METRICS_DIR / "metacognition_report.json"
    report_file.parent.mkdir(parents=True, exist_ok=True)
    fixes = _apply_metacognitive_fixes(retrieved_keys, repeat_counts, cutoff_14d, verbose)
    report["fixes_applied"] = fixes
    save_json(report_file, report)

    if verbose:
        print(f"  [METACOGNITION] Audited {report['total_audited']} items: "
              f"{report['vague']} vague, {report['no_verb']} no-verb, "
              f"{report['unused_14d']} unused, {report['contradicted']} contradicted")
    return report


def _apply_metacognitive_fixes(retrieved_keys, repeat_counts, cutoff_14d, verbose=False):
    """Reduce confidence on vague/unused/contradicted items. Archive below 0.2."""
    fixes = {"confidence_reduced": 0, "archived": 0}
    for md_file in [
        LESSONS_DIR / "auto_lessons.md",
        PATTERNS_DIR / "auto_patterns.md",
        FAILS_DIR / "auto_fails.md",
    ]:
        if not md_file.exists():
            continue
        jsonl_entries = _get_entries_for_file(md_file)
        penalty_by_title_key = {}
        if jsonl_entries and jsonl_entries[0].get("_from_jsonl"):
            for e in jsonl_entries:
                title = e["title"]
                if not title:
                    continue
                conf = e.get("conf", 0.5)
                try:
                    effective_conf = temporal_confidence(
                        conf,
                        knowledge_type=e.get("type", "lesson"),
                        timestamp=e.get("date", ""),
                        retrieval_count=0,
                    )
                except Exception:
                    effective_conf = conf
                penalty = 0.0
                if len(title) < 30:
                    penalty += 0.1
                key = hashlib.md5(title[:80].lower().strip().encode("utf-8")).hexdigest()[:12]
                item_date_str = e.get("date", "")
                if item_date_str and key not in retrieved_keys:
                    try:
                        if datetime.fromisoformat(item_date_str[:10] + "T00:00:00+00:00") < cutoff_14d:
                            penalty += 0.15
                    except (ValueError, TypeError):
                        logging.debug("Suppressed exception in _apply_metacognitive_fixes", exc_info=True)
                if repeat_counts.get(title[:60].lower(), 0) >= 3:
                    penalty += 0.2
                max_penalty = max(0.0, effective_conf - 0.1)
                penalty = min(penalty, max_penalty)
                title_key = hashlib.md5(title[:80].lower().encode("utf-8")).hexdigest()
                penalty_by_title_key[title_key] = (penalty, effective_conf)

        if penalty_by_title_key:
            jsonl_path = _MD_TO_JSONL.get(md_file)
            if jsonl_path:
                # P47 FIX: Read confidence from SQLite (not stale JSONL) to avoid clobbering backfill
                _table_map_read = {"auto_lessons.jsonl": "lessons", "auto_fails.jsonl": "failures", "auto_patterns.jsonl": "patterns"}
                _tbl_r = _table_map_read.get(jsonl_path.name)
                all_rows = []
                if _tbl_r:
                    try:
                        from pulse.core.brain_db import get_conn as _gc_r
                        _rows_db = _gc_r().execute(f"SELECT title, content, confidence, fingerprint FROM {_tbl_r}").fetchall()
                        all_rows = [{"title": r[0] or "", "content": r[1] or "", "confidence": r[2] or 0.5, "fingerprint": r[3]} for r in _rows_db]
                    except Exception:
                        all_rows = read_jsonl_entries(jsonl_path)  # fallback to JSONL
                else:
                    all_rows = read_jsonl_entries(jsonl_path)
                updates = []  # (fingerprint, new_conf) for rows to update
                deletes = []  # fingerprints for rows to delete (conf < 0.2)
                for r in all_rows:
                    title = (r.get("title") or r.get("content", ""))[:200]
                    title_key = hashlib.md5(title[:80].lower().encode("utf-8")).hexdigest()
                    if title_key in penalty_by_title_key:
                        penalty, _ = penalty_by_title_key[title_key]
                        if penalty > 0:
                            new_conf = max(0.0, round(r.get("confidence", 0.5) - penalty, 2))
                            fp = r.get("fingerprint")
                            fixes["confidence_reduced"] += 1
                            if new_conf < 0.2:
                                fixes["archived"] += 1
                                if fp:
                                    deletes.append(fp)
                            elif fp:
                                updates.append((fp, new_conf))

                # 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 (updates or deletes):
                        conn = get_conn()
                        with conn:
                            for fp, new_conf in updates:
                                conn.execute(
                                    f"UPDATE {table} SET confidence=?, "
                                    f"updated_at=strftime('%Y-%m-%dT%H:%M:%SZ','now') "
                                    f"WHERE fingerprint=?",
                                    (new_conf, fp),
                                )
                            if deletes:
                                placeholders = ",".join("?" * len(deletes))
                                conn.execute(
                                    f"DELETE FROM knowledge_fts WHERE item_id IN "
                                    f"(SELECT id FROM {table} WHERE fingerprint IN ({placeholders}))",
                                    deletes,
                                )
                                conn.execute(
                                    f"DELETE FROM {table} WHERE fingerprint IN ({placeholders})",
                                    deletes,
                                )
                        _sqlite_ok = True
                except Exception:
                    _sqlite_ok = False

                # JSONL fallback
                if not _sqlite_ok:
                    from pulse.brain.save_writers import rewrite_jsonl
                    surviving = []
                    delete_set = set(deletes)
                    update_map = {fp: conf for fp, conf in updates}
                    for r in all_rows:
                        fp = r.get("fingerprint")
                        if fp in delete_set:
                            continue
                        if fp in update_map:
                            r["confidence"] = update_map[fp]
                        surviving.append(r)
                    rewrite_jsonl(jsonl_path, surviving)
    if verbose:
        print(f"  [METACOGNITION] Fixes: {fixes['confidence_reduced']} reduced, {fixes['archived']} archived")
    return fixes
