"""

Phase 3B: Metamemory Module

Computes a domain-level confidence map based on database knowledge.
Identifies gaps and evaluates overall epistemic certainty per domain.
"""
import logging
log = logging.getLogger("pulse.brain.metacognition.metamemory")

import json
import time
import uuid
from datetime import datetime, timedelta, timezone
from pulse.core.paths import get_state_dir
from pulse.core.brain_db import get_conn
from pulse.core.uncertainty import wilson_interval
from pulse.core.temporal import _days_since

METAMEMORY_FILE = get_state_dir() / "memory" / "metamemory_map.json"

def compute_metamemory() -> dict:
    """Computes and saves domain-level confidence map."""
    conn = get_conn()
    tables = ["lessons", "failures", "patterns", "facts"]
    
    domain_stats = {}
    
    with conn:
        for table in tables:
            # Query aggregates per domain
            res = conn.execute(f"""
                SELECT domain, 
                       COUNT(*) as item_count, 
                       AVG(confidence) as avg_conf,
                       SUM(evidence_count) as total_evidence,
                       MAX(timestamp) as latest_ts
                FROM {table}
                WHERE validity = 'valid'
                GROUP BY domain
            """)
            
            for row in res:
                d = row["domain"]
                if not d:
                    continue
                if d not in domain_stats:
                    domain_stats[d] = {
                        "item_count": 0,
                        "sum_conf": 0.0,
                        "total_evidence": 0,
                        "latest_ts": None
                    }
                
                ds = domain_stats[d]
                ds["item_count"] += row["item_count"]
                ds["sum_conf"] += (row["avg_conf"] * row["item_count"])
                ds["total_evidence"] += row["total_evidence"]
                
                # track newest timestamp
                ts = row["latest_ts"]
                if ts:
                    if not ds["latest_ts"] or ts > ds["latest_ts"]:
                        ds["latest_ts"] = ts

    # Finalize calculations
    metamemory_map = {}
    for d, stats in domain_stats.items():
        if stats["item_count"] == 0:
            continue
            
        avg_confidence = stats["sum_conf"] / stats["item_count"]
        lo, _ = wilson_interval(avg_confidence, stats["total_evidence"])
        staleness = _days_since(stats["latest_ts"]) if stats["latest_ts"] else 999
        
        # Coverage score heuristic: more items = better coverage, up to a limit
        coverage_score = min(1.0, stats["item_count"] / 50.0) 
        
        gap_flag = False
        if lo < 0.3 or staleness > 60 or coverage_score < 0.2:
            gap_flag = True

        # Competence level based on Wilson lower bound
        if lo > 0.8:
            competence_level = "expert"
        elif lo > 0.5:
            competence_level = "competent"
        elif lo > 0.2:
            competence_level = "novice"
        else:
            competence_level = "blind_spot"

        metamemory_map[d] = {
            "item_count": stats["item_count"],
            "avg_confidence": round(avg_confidence, 3),
            "wilson_lower": round(lo, 3),
            "coverage_score": round(coverage_score, 3),
            "staleness_days": round(staleness, 1),
            "gap_flag": gap_flag,
            "competence_level": competence_level,
        }
        
    METAMEMORY_FILE.parent.mkdir(parents=True, exist_ok=True)
    from pulse.core.brain_io import _acquire
    with _acquire(METAMEMORY_FILE):
        with open(METAMEMORY_FILE, "w", encoding="utf-8") as f:
            json.dump(metamemory_map, f, indent=2)
        
    return metamemory_map

def get_metamemory() -> dict:
    """Loads the latest metamemory map."""
    if not METAMEMORY_FILE.exists():
        return {}
    try:
        with open(METAMEMORY_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return {}

# Item 85: Calibration feedback loop (Metacognition)
_CALIBRATION_HISTORY: list = []  # [(predicted_conf, actual_success)]
_CALIBRATION_WINDOW = 50

def _record_calibration(avg_confidence: float, task_succeeded: bool):
    """Record prediction accuracy for calibration. Persists to SQLite for durability."""
    actual = 1.0 if task_succeeded else 0.0
    _CALIBRATION_HISTORY.append((avg_confidence, actual))
    if len(_CALIBRATION_HISTORY) > _CALIBRATION_WINDOW:
        _CALIBRATION_HISTORY.pop(0)
    # Persist to SQLite so history survives process restarts
    try:
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        conn.execute(
            "INSERT INTO predictions (id, content, confidence, domain, timestamp) VALUES (?, ?, ?, ?, ?)",
            (
                f"cal-{uuid.uuid4().hex}",
                f"calibration:{avg_confidence:.3f}:{int(actual)}",
                avg_confidence,
                "calibration",
                datetime.now(timezone.utc).isoformat(),
            ),
        )
        conn.commit()
    except Exception as _exc:
        pass  # QR14: silent — consider log.debug("%s", _exc)

def _get_calibration_factor() -> float:
    """Return calibration adjustment. <1.0 = overconfident, >1.0 = underconfident."""
    # Cold-start: load from SQLite if in-memory history is sparse
    if len(_CALIBRATION_HISTORY) < 10:
        try:
            from pulse.core.brain_db import get_conn
            rows = get_conn().execute(
                "SELECT confidence, content FROM predictions "
                "WHERE domain='calibration' ORDER BY timestamp DESC LIMIT 50"
            ).fetchall()
            for r in rows:
                parts = r["content"].split(":")
                if len(parts) == 3:
                    try:
                        _CALIBRATION_HISTORY.append((float(parts[1]), float(parts[2])))
                    except ValueError:
                        pass
            # Trim to window after bulk-load
            while len(_CALIBRATION_HISTORY) > _CALIBRATION_WINDOW:
                _CALIBRATION_HISTORY.pop(0)
        except Exception as _exc:
            pass  # QR14: silent — consider log.debug("%s", _exc)
    if len(_CALIBRATION_HISTORY) < 10:
        return 0.85  # N55: cold-start assumes 15% overconfidence (Lichtenstein et al. 1982)
    try:
        avg_predicted = sum(p for p, _ in _CALIBRATION_HISTORY) / len(_CALIBRATION_HISTORY)
        avg_actual = sum(a for _, a in _CALIBRATION_HISTORY) / len(_CALIBRATION_HISTORY)
        if avg_predicted > 0:
            return min(1.5, max(0.5, avg_actual / avg_predicted))
    except Exception as _exc:
        pass  # QR14: silent — consider log.debug("%s", _exc)
    return 1.0

# Items 92, 97, 104, 105, 107 — extracted to metamemory_tracking.py
try:
    from pulse.brain.metacognition.metamemory_tracking import (
        _record_source_outcome, _get_source_reliability,
        _compute_domain_decay_stats, _record_agent_domain, get_agent_specialization,
        _compute_brain_health,
        # Item 132: Source credibility weighting (P32)
        apply_source_credibility_to_results,
        # Item 135: Consolidation debt tracking (P32)
        increment_consolidation_debt, reset_consolidation_debt, get_consolidation_debt,
        # Item 138: Cognitive flexibility index (P33)
        record_agent_session_domain, get_cognitive_flexibility_index,
    )

    def _decay_domain_expertise():
        from pulse.brain.metacognition.metamemory_tracking import _decay_domain_expertise as _dde
        _dde(get_metamemory, _save_metamemory)
except Exception:
    _decay_domain_expertise = lambda *a, **kw: None  # fail-open stub

def _save_metamemory(meta: dict):
    """Persist metamemory map to disk."""
    try:
        METAMEMORY_FILE.parent.mkdir(parents=True, exist_ok=True)
        from pulse.core.brain_io import _acquire
        with _acquire(METAMEMORY_FILE):
            with open(METAMEMORY_FILE, "w", encoding="utf-8") as f:
                json.dump(meta, f, indent=2)
    except Exception as _exc:
        pass  # QR14: silent — consider log.debug("%s", _exc)

# Item 111: Spaced retrieval scheduling — extracted to spaced_retrieval.py
try:
    from pulse.brain.plasticity.spaced_retrieval import _schedule_next_retrieval, get_due_domains
except Exception:
    pass  # fail-open

# Item 123: Meta-learning — track knowledge type success rates (Metacognition — P29)
_KNOWLEDGE_TYPE_SUCCESS: dict = {}  # {knowledge_type: {"hits": int, "successes": int}}

def _record_knowledge_type_outcome(knowledge_type: str, success: bool):
    """Track which knowledge types (lesson/failure/pattern/fact) lead to task success."""
    try:
        entry = _KNOWLEDGE_TYPE_SUCCESS.setdefault(
            knowledge_type, {"hits": 0, "successes": 0}
        )
        entry["hits"] += 1
        if success:
            entry["successes"] += 1
    except Exception:
        pass  # fail-open

def get_knowledge_type_priority() -> dict:
    """Return success rate per knowledge type. Used to prioritize search sources."""
    result = {}
    for ktype, stats in _KNOWLEDGE_TYPE_SUCCESS.items():
        hits = stats.get("hits", 0)
        result[ktype] = round(stats.get("successes", 0) / hits, 3) if hits >= 3 else 0.5
    return result

# Item 130: Brain utilization metrics (Monitoring — P30)
def get_brain_utilization() -> dict:
    """Return % of brain items accessed in last 7 days vs total."""
    try:
        from pulse.core.brain_db import get_conn as _gc130
        conn = _gc130()
        cutoff = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
        total = 0
        used = 0
        for tbl in ("lessons", "failures", "patterns", "facts"):
            try:
                t_row = conn.execute(
                    f"SELECT COUNT(*) FROM {tbl} WHERE validity='valid'"
                ).fetchone()
                u_row = conn.execute(
                    f"SELECT COUNT(*) FROM {tbl} "
                    f"WHERE validity='valid' AND last_accessed >= ?",
                    (cutoff,)
                ).fetchone()
                total += int(t_row[0] or 0)
                used += int(u_row[0] or 0)
            except Exception as _exc:
                pass  # QR14: silent — consider log.debug("%s", _exc)
        utilization = round(used / total, 3) if total > 0 else 0.0
        return {"total_items": total, "used_last_7d": used, "utilization_rate": utilization}
    except Exception:
        return {"total_items": 0, "used_last_7d": 0, "utilization_rate": 0.0}

# Item 111: Spaced retrieval scheduling -- delegated to spaced_retrieval.py
# Use: from pulse.brain.plasticity.spaced_retrieval import _schedule_next_retrieval, get_due_domains
record_knowledge_type_outcome = _record_knowledge_type_outcome

# Items 142, 147, 149, 150 — extracted to metamemory_p34.py (file size ceiling)
try:
    from pulse.brain.metacognition.metamemory_p34 import (
        get_decay_rate_24h,        # Item 142
        record_domain_valence,     # Item 147
        get_domain_valence,        # Item 147
        record_agent_collaboration, # Item 149
        get_collaboration_recommendations,  # Item 149
        compute_brain_entropy,     # Item 150
    )
except Exception:
    def get_decay_rate_24h(): return {"decay_count": 0, "total_valid": 0, "decay_rate_pct": 0.0, "warning": False}
    def record_domain_valence(domain, text): pass
    def get_domain_valence(): return {}
    def record_agent_collaboration(a, b, domain, success): pass
    def get_collaboration_recommendations(): return []
    def compute_brain_entropy(): return {"entropy": 0.0, "domain_count": 0}

