#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""Consolidation Late Stages Runner: stages 11-21 + log assembly.

Extracted from consolidation_daemon.py. Called by run_all_stages().
"""
import time

from pulse.core.paths import get_state_dir
from pulse.core.brain_utils import HIPPOCAMPUS_DIR, save_json, now_iso as _now_iso

CONSOLIDATION_LOG = get_state_dir() / "consolidation_log.json"

_STAGE_PROGRESS_FILE = HIPPOCAMPUS_DIR / 'Evidence' / 'Metrics' / 'consolidation_stages.json'


def _save_stage_progress(stage_results: dict):
    """Item 99: Persist per-stage timing data to state/ for monitoring dashboards."""
    try:
        _STAGE_PROGRESS_FILE.parent.mkdir(parents=True, exist_ok=True)
        save_json(_STAGE_PROGRESS_FILE, {'timestamp': _now_iso(), 'stages': stage_results})
    except Exception:
        pass  # fail-open


def run_late_stages(verbose: bool = False, early: dict = None) -> dict:
    """Run consolidation stages 11-21 and assemble the full log. Requires early stage results."""
    if early is None:
        early = {}
    start = early.get('start', time.time())
    dedup_result = early.get('dedup_result', {})
    mining_result = early.get('mining_result', {})
    sequences = early.get('sequences', [])
    cross_patterns = early.get('cross_patterns', [])
    proc_count = early.get('proc_count', 0)
    episodic_grad_result = early.get('episodic_grad_result', {})
    ltd_result = early.get('ltd_result', {})
    recon_result = early.get('recon_result', {})
    transfer_result = early.get('transfer_result', {})
    cross_region_result = early.get('cross_region_result', {})
    schema_result = early.get('schema_result', {})
    seq_schema_result = early.get('seq_schema_result', {})
    micro_pattern_result = early.get('micro_pattern_result', {})
    meta_schema_result = early.get('meta_schema_result', {})
    graph_result = early.get('graph_result', {})
    abstraction_result = early.get('abstraction_result', {})
    promote_result = early.get('promote_result', {})
    meta_result = early.get('meta_result', {})
    prune_result = early.get('prune_result', {})
    health_result = early.get('health_result', {})
    truth_result = early.get('truth_result', {})
    schema_audit_result = early.get('schema_audit_result', {})
    decision_result = early.get('decision_result', {})
    index_result = early.get('index_result', {})
    metamemory_result = early.get('metamemory_result', {})
    homeostasis_result = early.get('homeostasis_result', {})
    recency_result = early.get('recency_result', {})


    # Stage 11: Causal Graph Reasoning (Phase 4B-4D — confounders + analogy scan)
    print("[CONSOLIDATION] Stage 11: Causal Graph Reasoning")
    try:
        from pulse.graph.causal_model import detect_confounders
        confounded = detect_confounders()
        causal_result = {"confounders_found": len(confounded), "pairs": [list(p) for p in confounded[:10]]}

        # Run analogy scan on high-degree nodes (find structural isomorphisms)
        from pulse.core.brain_db import get_conn
        conn = get_conn()
        top_nodes = conn.execute(
            "SELECT id, domain FROM graph_nodes ORDER BY degree DESC LIMIT 20"
        ).fetchall()
        analogy_matches = 0
        if len(top_nodes) >= 2:
            from pulse.graph.analogy import build_analogy
            seen = set()
            for i, n1 in enumerate(top_nodes):
                for n2 in top_nodes[i+1:]:
                    if n1["domain"] == n2["domain"]:
                        continue  # Only cross-domain
                    pair_key = tuple(sorted([n1["id"], n2["id"]]))
                    if pair_key in seen:
                        continue
                    seen.add(pair_key)
                    res = build_analogy(n1["id"], n2["id"])
                    if res.get("match"):
                        analogy_matches += 1
                    if analogy_matches >= 5 or len(seen) >= 30:
                        break
                if analogy_matches >= 5 or len(seen) >= 30:
                    break
        causal_result["analogy_matches"] = analogy_matches
        if verbose:
            print(f"  [CAUSAL] {len(confounded)} confounders, {analogy_matches} analogies")
    except Exception as e:
        causal_result = {"status": "error", "error": str(e)}

    # Stage 12: Goal Generation (Phase 5A — self-directed investigation)
    print("[CONSOLIDATION] Stage 12: Goal Generation")
    try:
        from pulse.daemons.synthesis.planning.goal_engine import formulate_goals
        goal_result = formulate_goals(verbose=verbose)
    except Exception as e:
        goal_result = {"status": "error", "error": str(e)}

    # Stage 13: Prospective Memory Check (Phase 5B — deferred intentions)
    print("[CONSOLIDATION] Stage 13: Prospective Memory")
    try:
        from pulse.daemons.synthesis.planning.prospective_memory import check_intentions
        intention_result = check_intentions(verbose=verbose)
    except Exception as e:
        intention_result = {"status": "error", "error": str(e)}

    # Stage 14: Sentinel Health Checks (Phase 5C)
    print("[CONSOLIDATION] Stage 14: Sentinel Checks")
    try:
        from pulse.admin.audit.sentinels import run_sentinels
        sentinel_result = run_sentinels(verbose=verbose)
    except Exception as e:
        sentinel_result = {"status": "error", "error": str(e)}

    # Stage 15: Autonomy Promotion Check (Phase 5D)
    print("[CONSOLIDATION] Stage 15: Autonomy Check")
    try:
        from pulse.core.autonomy_state import check_promotion
        promoted = check_promotion()
        autonomy_result = {"promoted": promoted}
    except Exception as e:
        autonomy_result = {"status": "error", "error": str(e)}

    # Stage 16: Pattern Evolution (Phase 6E — auto-generate regex)
    # Sacred Boundary #7: approval gate on self-generated patterns
    print("[CONSOLIDATION] Stage 16: Pattern Evolution")
    try:
        from pulse.brain.monitoring.pattern_evolution import evolve_patterns
        evolution_result = evolve_patterns(verbose=verbose)
        new_patterns = evolution_result.get("new_patterns", [])
        if isinstance(new_patterns, list) and len(new_patterns) > 0:
            from pulse.daemons.consolidation.approval_gate import check_approval
            approved_ev, blocked_ev = check_approval("pattern_evolution", new_patterns)
            if blocked_ev and verbose:
                print(f"  [APPROVAL] {len(blocked_ev)} evolved patterns pending human review")
    except Exception as e:
        evolution_result = {"status": "error", "error": str(e)}

    # Stage 17: Associative Replay (Phase 6A — dreaming, low-activity only)
    print("[CONSOLIDATION] Stage 17: Associative Replay")
    try:
        from pulse.daemons.synthesis.learning.associative_replay import run_replay
        replay_result = run_replay(verbose=verbose)
    except Exception as e:
        replay_result = {"status": "error", "error": str(e)}

    # Stage 18: Deep Consolidation (promote permanent, archive dead — unified from deep_consolidation.py)
    print("[CONSOLIDATION] Stage 18: Deep Consolidation")
    try:
        from pulse.daemons.consolidation.consolidation_stages import stage_deep_consolidation
        deep_result = stage_deep_consolidation(verbose=verbose)
    except Exception as e:
        deep_result = {"status": "error", "error": str(e)}

    # Stage 19: Session-Based Replay (hippocampal replay of actual retrieval sequences)
    print("[CONSOLIDATION] Stage 19: Session Replay")
    try:
        from pulse.daemons.consolidation.session_replay import run_session_replay
        session_replay_result = run_session_replay(verbose=verbose)
    except Exception as e:
        session_replay_result = {"status": "error", "error": str(e)}

    # Stage 20: Conflict Monitor (Botvinick 2001 — ACC contradiction detection)
    print("[CONSOLIDATION] Stage 20: Conflict Monitor")
    try:
        from pulse.daemons.synthesis.core.conflict_monitor import scan_for_conflicts
        conflict_result = scan_for_conflicts(verbose=verbose)
    except Exception as e:
        conflict_result = {"status": "error", "error": str(e)}

    # Stage 21: Spaced Retrieval (Dunlosky 2013 — testing effect + spacing effect)
    print("[CONSOLIDATION] Stage 21: Spaced Retrieval")
    try:
        from pulse.daemons.consolidation.retrieval_scheduler import schedule_reviews
        spaced_result = schedule_reviews(verbose=verbose)
    except Exception as e:
        spaced_result = {"status": "error", "error": str(e)}


    # Stage 22: Neocortical Semantic Extraction (Phase C Item 15)
    print("[CONSOLIDATION] Stage 22: Neocortical Semantic Extraction")
    try:
        from pulse.daemons.consolidation.stages.stage_neocortical_extraction import stage_neocortical_extraction
        neo_result = stage_neocortical_extraction(verbose=verbose)
    except Exception as e:
        neo_result = {"status": "error", "error": str(e)}

    elapsed = round(time.time() - start, 2)

    # Item 99: Save per-stage progress for monitoring
    _stage_results_99 = {}
    for _sname99, _sdata99 in [
        ("stage_1_dedup", dedup_result),
        ("stage_2_mining", {"sequences_found": len(sequences), "cross_patterns_found": len(cross_patterns)}),
        ("stage_4_graph", graph_result),
        ("stage_5_metacognition", meta_result),
        ("stage_6_prune", prune_result),
    ]:
        _stage_results_99[_sname99] = {"status": "ok" if isinstance(_sdata99, dict) and "error" not in _sdata99 else "error",
                                        "result_keys": list(_sdata99.keys()) if isinstance(_sdata99, dict) else []}
    _save_stage_progress(_stage_results_99)

    # Write consolidation log
    log = {
        "timestamp": _now_iso(),
        "elapsed_seconds": elapsed,
        "stage_1_dedup": dedup_result,
        "stage_2_mining": {
            "sequences_found": len(sequences),
            "cross_patterns_found": len(cross_patterns),
        },
        "stage_2b_procedures": {"registered": proc_count},
        "stage_2c_ltd_decay": ltd_result,
        "stage_2d_reconsolidation": recon_result,
        "stage_2e_transfer": transfer_result,
        "stage_2f_cross_region": cross_region_result,
        "stage_3_schemas": schema_result,
        "stage_3b_sequence_schemas": seq_schema_result,
        "stage_3c_micro_patterns": micro_pattern_result,
        "stage_3d_meta_schemas": meta_schema_result,
        "stage_4_graph": graph_result,
        "stage_4b_promote_patterns": promote_result,
        "stage_4c_abstraction": abstraction_result,
        "stage_5_metacognition": meta_result,
        "stage_6_hierarchical_prune": prune_result,
        "stage_7_brain_health": health_result,
        "stage_8_truth_verification": truth_result,
        "stage_9_decision_consolidation": decision_result,
        "stage_6b_search_index": index_result,
        "stage_10_metamemory": metamemory_result,
        "stage_10b_homeostasis": homeostasis_result,
        "stage_11_causal_graph": causal_result,
        "stage_12_goals": goal_result,
        "stage_13_prospective": intention_result,
        "stage_14_sentinels": sentinel_result,
        "stage_15_autonomy": autonomy_result,
        "stage_16_pattern_evolution": evolution_result,
        "stage_17_replay": replay_result,
        "stage_18_deep_consolidation": deep_result,
        "stage_19_session_replay": session_replay_result,
        "stage_20_conflict_monitor": conflict_result,
        "stage_21_spaced_retrieval": spaced_result,
    }

    CONSOLIDATION_LOG.parent.mkdir(parents=True, exist_ok=True)
    save_json(CONSOLIDATION_LOG, log)

    print(f"\n[CONSOLIDATION] Complete in {elapsed}s")
    print(f"  Dedup merged: {dedup_result.get('merged', 0)}")
    print(f"  Patterns found: {mining_result.get('total_found', 0)}")
    print(f"  Schemas: {schema_result.get('new_schemas', 0)} new, "
          f"{schema_result.get('total_schemas', 0)} total")

    # Item 135: Reset consolidation debt after run
    try:
        from pulse.brain.metacognition.metamemory_tracking import reset_consolidation_debt
        reset_consolidation_debt()
    except Exception: pass

    # Item 175: Brain self-assessment (post-consolidation snapshot)
    try:
        from pulse.brain.monitoring.brain_self_assessment import get_brain_health_report
        health = get_brain_health_report()
        import json
        from pulse.core.paths import get_state_dir
        (get_state_dir() / "brain_health.json").write_text(
            json.dumps(health, indent=2), encoding="utf-8"
        )
    except Exception:
        pass

    # Item 164: Domain expertise decay — handled by consolidation_daemon.py
    # (removed duplicate call here)

    # Item 156: Record consolidation completeness (which stages ran)
    try:
        from pulse.brain.monitoring.consolidation_completeness import record_consolidation_run
        record_consolidation_run(list(_stage_results_99.keys()))
    except Exception:
        pass

    return log

