"""
Phase 3B: Metamemory Module

Computes a domain-level confidence map based on database knowledge.
Identifies gaps and evaluates overall epistemic certainty per domain.
"""
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:
        pass


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:
            pass
    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:
        pass
    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:
        pass


# 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:
                pass
        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}
