#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Orchestrator Schedulers: Periodic batch jobs for PULSE brain maintenance.

Extracted from pulse.py per Law 7 (files <= 300 lines).
"""
import json
import subprocess
import sys
import threading
import time
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
DAEMONS_DIR = SCRIPT_DIR.parent
ROOT_DIR = DAEMONS_DIR.parent.parent

# --- Scheduler Intervals ---
DEDUP_INTERVAL = 6 * 3600          # Consolidation: 6 hours
SYNTHESIZE_INTERVAL = 12 * 3600    # Plans synthesis: 12 hours
PRUNE_INTERVAL = 24 * 3600         # Pruning: 24 hours
AUTO_CAPTURE_INTERVAL = 1 * 3600   # Auto-capture: 1 hour
ANTI_PATTERN_INTERVAL = 6 * 3600   # Anti-pattern build: 6 hours
PRIVATE_SYNTHESIS_INTERVAL = 24 * 3600  # Private synthesis: 24 hours
PREDICTION_INTERVAL = 5 * 60       # Prediction: 5 minutes
BRAIN_HEALER_INTERVAL = 4 * 3600   # Brain Healer: 4 hours
PROFILE_DISTILLER_INTERVAL = 12 * 3600 # Profile Distiller: 12 hours
CONSTRAINT_ENGINE_INTERVAL = 6 * 3600  # D4 Constraint Engine: 6 hours
GATE_FEEDBACK_DECAY_INTERVAL = 4 * 3600  # D5 Gate Feedback decay: 4 hours
PATHWEAVER_INTERVAL = 12 * 3600           # D6 Pathweaver: 12 hours
CAUSAL_RECOVERY_INTERVAL = 30 * 60        # D7 CausalClaim Recovery: 30 minutes
SWARM_REFLEX_INTERVAL = 5 * 60            # D8 Swarm Reflex: 5 minutes
EPISTEMIC_FORAGER_INTERVAL = 24 * 3600    # D9 Epistemic Forager: 24 hours
SKILL_SYNTHESIZER_INTERVAL = 24 * 3600   # D10 Skill Dependency Synthesizer: 24 hours
GHOST_RESONANCE_INTERVAL = 1 * 3600     # D11 Ghost Resonance: 1 hour
HEBBIAN_BALANCER_INTERVAL = 15 * 60     # D12 Hebbian Balancer: 15 minutes
SCHEMA_DAEMON_INTERVAL = 12 * 3600        # Schema extraction: 12 hours
PROCEDURE_DAEMON_INTERVAL = 12 * 3600     # Procedure extraction: 12 hours
INTENT_DAEMON_INTERVAL = 12 * 3600        # Intent synthesis: 12 hours
SLEEP_CYCLE_INTERVAL = 24 * 3600          # RAG sleep cycle: 24 hours
GC_DAEMON_INTERVAL = 6 * 3600             # GC daemon: 6 hours
MEMORY_DISPATCHER_INTERVAL = 1 * 3600    # Memory Dispatcher: 1 hour

# --- Script Paths ---
DEDUP_SCRIPT = DAEMONS_DIR / "synthesis" / "core" / "pulse_dedup.py"
CONSOLIDATION_SCRIPT = DAEMONS_DIR / "consolidation" / "consolidation_daemon.py"
SYNTHESIZE_SCRIPT = DAEMONS_DIR / "synthesis" / "planning" / "synthesize_plans.py"
PRUNE_SCRIPT = ROOT_DIR / "pulse" / "admin" / "brain_ops" / "pulse_prune.py"
AUTO_CAPTURE_SCRIPT = ROOT_DIR / "pulse" / "brain" / "capture" / "auto_capture.py"
ANTI_PATTERN_SCRIPT = ROOT_DIR / "pulse" / "admin" / "patterns" / "anti_pattern_builder.py"
PRIVATE_SYNTHESIS_SCRIPT = ROOT_DIR / "pulse" / "admin" / "brain_ops" / "private_synthesis.py"
PREDICTION_DAEMON = DAEMONS_DIR / "neural" / "prediction_daemon.py"
BRAIN_HEALER_DAEMON = DAEMONS_DIR / "synthesis" / "core" / "brain_healer.py"
PROFILE_DISTILLER_DAEMON = DAEMONS_DIR / "synthesis" / "core" / "profile_distiller.py"
CONSTRAINT_ENGINE_DAEMON = DAEMONS_DIR / "synthesis" / "planning" / "constraint_engine.py"
GATE_FEEDBACK_DAEMON = DAEMONS_DIR / "neural" / "gate_feedback.py"
PATHWEAVER_DAEMON = DAEMONS_DIR / "synthesis" / "pathweaver" / "pathweaver.py"
CAUSAL_RECOVERY_DAEMON = DAEMONS_DIR / "synthesis" / "core" / "causal_claim_recovery.py"
SWARM_REFLEX_DAEMON = DAEMONS_DIR / "synthesis" / "swarm" / "swarm_reflex.py"
EPISTEMIC_FORAGER_DAEMON = DAEMONS_DIR / "synthesis" / "core" / "epistemic_forager.py"
SKILL_SYNTHESIZER_DAEMON = DAEMONS_DIR / "synthesis" / "skill" / "skill_dependency_synthesizer.py"
GHOST_RESONANCE_DAEMON = DAEMONS_DIR / "synthesis" / "resonance" / "ghost_resonance.py"
HEBBIAN_BALANCER_DAEMON = DAEMONS_DIR / "synthesis" / "learning" / "hebbian_balancer.py"
HOMEOSTATIC_INTERVAL = 60                 # D13 Homeostatic Scheduler: 60 seconds
HOMEOSTATIC_DAEMON = DAEMONS_DIR / "synthesis" / "health" / "homeostatic_scheduler.py"
PIPELINE_HEALTH_INTERVAL = 60             # Phase 6 Pipeline Health: 60 seconds
PIPELINE_HEALTH_SCRIPT = DAEMONS_DIR / "runtime" / "daemon_heartbeat.py"
HEALER_INTERVAL = 12 * 3600              # D14 Procedural Sleep Healer: 12 hours
HEALER_DAEMON = DAEMONS_DIR / "synthesis" / "sleep" / "procedural_sleep_healer.py"
PHYLOGENY_INTERVAL = 12 * 3600           # D15 Phylogenetic Skill Compiler: 12 hours
PHYLOGENY_DAEMON = DAEMONS_DIR / "synthesis" / "skill" / "skill_phylogeny.py"
AGENT_PROC_SYNTH_INTERVAL = 24 * 3600   # D16 Agent Procedure Synthesizer: 24 hours
AGENT_PROC_SYNTH_DAEMON = DAEMONS_DIR / "synthesis" / "core" / "agent_procedure_synthesizer.py"
CHOREOGRAPHER_INTERVAL = 10 * 60        # D17 Swarm Choreographer: 10 minutes
CHOREOGRAPHER_DAEMON = DAEMONS_DIR / "synthesis" / "swarm" / "swarm_choreographer.py"
COGNITIVE_HUD_INTERVAL = 60                 # D18 Cognitive HUD: 60 seconds
COGNITIVE_HUD_DAEMON = DAEMONS_DIR / "neural" / "cognitive_hud.py"
ECHOREFINE_INTERVAL = 6 * 3600          # D19 EchoRefine Synthesis: 6 hours
ECHOREFINE_DAEMON = DAEMONS_DIR / "synthesis" / "resonance" / "echo_refine.py"
IMMUNITY_INTERVAL = 5 * 60              # D20 Swarm Immunity: 5 minutes
IMMUNITY_DAEMON = DAEMONS_DIR / "synthesis" / "swarm" / "swarm_immunity.py"
NEUROPLASTICITY_INTERVAL = 30 * 60      # D21 Neuroplasticity: 30 minutes
NEUROPLASTICITY_DAEMON = DAEMONS_DIR / "synthesis" / "learning" / "neuroplasticity.py"
RESONANCE_INTERVAL = 30 * 60                 # D22 Prediction Resonance: 30 minutes
RESONANCE_DAEMON = DAEMONS_DIR / "synthesis" / "resonance" / "prediction_resonance.py"
CONTENTION_CORTEX_INTERVAL = 15 * 60         # D23 Contention Cortex: 15 minutes
CONTENTION_CORTEX_DAEMON = DAEMONS_DIR / "synthesis" / "health" / "contention_cortex.py"
SKILL_RESONANCE_INTERVAL = 15 * 60           # D24 Skill Resonance: 15 minutes
SKILL_RESONANCE_DAEMON = DAEMONS_DIR / "synthesis" / "skill" / "skill_resonance.py"
INTENT_TOPOLOGY_INTERVAL = 30 * 60           # D25 Intent Topology: 30 minutes
INTENT_TOPOLOGY_DAEMON = DAEMONS_DIR / "neural" / "intent_topology.py"
JIT_INOCULATION_INTERVAL = 2 * 60            # D26 JIT Inoculation: 2 minutes
JIT_INOCULATION_DAEMON = DAEMONS_DIR / "synthesis" / "health" / "jit_inoculation.py"
COGNITIVE_TRIAGE_INTERVAL = 60               # D27 Cognitive Triage: 60 seconds
COGNITIVE_TRIAGE_DAEMON = DAEMONS_DIR / "synthesis" / "health" / "cognitive_triage.py"
SCHEMA_DAEMON_SCRIPT = DAEMONS_DIR / "synthesis" / "core" / "schema_daemon.py"
PROCEDURE_DAEMON_SCRIPT = DAEMONS_DIR / "synthesis" / "core" / "procedure_daemon.py"
INTENT_DAEMON_SCRIPT = DAEMONS_DIR / "bridge" / "intent_daemon.py"
SLEEP_CYCLE_SCRIPT = DAEMONS_DIR / "synthesis" / "sleep" / "sleep_cycle.py"
GC_DAEMON_SCRIPT = DAEMONS_DIR / "runtime" / "gc_daemon.py"
MEMORY_DISPATCHER_SCRIPT = DAEMONS_DIR / "bridge" / "memory_dispatcher.py"

OVERRIDE_FILE = ROOT_DIR / "state" / "homeostatic_overrides.json"
_OVERRIDE_CACHE: dict = {}
_OVERRIDE_MTIME: float = 0.0


def _get_effective_interval(scheduler_name: str, default: int) -> int:
    """Read interval override from D13 homeostatic state. Cached by mtime."""
    global _OVERRIDE_CACHE, _OVERRIDE_MTIME
    try:
        if OVERRIDE_FILE.exists():
            mt = OVERRIDE_FILE.stat().st_mtime
            if mt != _OVERRIDE_MTIME:
                _OVERRIDE_CACHE = json.loads(
                    OVERRIDE_FILE.read_text(encoding="utf-8")
                ).get("overrides", {})
                _OVERRIDE_MTIME = mt
        return _OVERRIDE_CACHE.get(scheduler_name, default)
    except (json.JSONDecodeError, OSError):
        return default


def _run_script(script: Path, label: str, timeout: int = 120, extra_args: list = None):
    """Run a script as a subprocess, record heartbeat, print output with [PULSE] prefix."""
    if not script.exists():
        return
    cmd = [sys.executable, str(script)]
    if extra_args:
        cmd.extend(extra_args)
    exit_code = -1
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout)
        exit_code = result.returncode
        if result.stdout:
            for line in result.stdout.strip().split("\n"):
                print(f"[PULSE] {line}")
        if result.stderr:
            for line in result.stderr.strip().split("\n"):
                print(f"[PULSE] {line}")
    except subprocess.TimeoutExpired as _te:
        print(f"[PULSE] {label} timed out after {timeout}s.")
        exit_code = -2
        try:
            _te.process.kill()
            _te.process.communicate()
        except Exception:
            pass
    except Exception as e:
        print(f"[PULSE] {label} error: {e}")
    # Record scheduler heartbeat (Phase 6)
    try:
        from pulse.daemons.runtime.daemon_heartbeat import send_heartbeat
        hb_name = label.lower().replace(" ", "_").replace("-", "_")
        send_heartbeat(hb_name, status="alive" if exit_code == 0 else "error",
                       metrics={"exit_code": exit_code})
    except Exception:
        pass

def _scheduler_loop(script: Path, label: str, interval: int, running_check, name: str, initial_delay: int = 0, timeout: int = 120, extra_args: list = None, run_at_boot: bool = True):
    """Generic scheduler: run once at boot (after optional delay), then repeat.
    Sleeps in 30s chunks, re-reading D13 homeostatic overrides each chunk."""
    def _loop():
        if initial_delay:
            time.sleep(initial_delay)
        if run_at_boot:
            _run_script(script, label, timeout, extra_args)
        while running_check():
            effective = _get_effective_interval(name, interval)
            elapsed = 0
            while elapsed < effective and running_check():
                chunk = min(30, effective - elapsed)
                time.sleep(chunk)
                elapsed += chunk
                effective = _get_effective_interval(name, interval)
            if running_check():
                _run_script(script, label, timeout, extra_args)
    t = threading.Thread(target=_loop, daemon=True, name=name)
    t.start()

def start_all_schedulers(running_check):
    """Start all periodic batch job schedulers."""
    script = CONSOLIDATION_SCRIPT if CONSOLIDATION_SCRIPT.exists() else DEDUP_SCRIPT
    _scheduler_loop(script, "Consolidation", DEDUP_INTERVAL, running_check, "consolidation_scheduler", timeout=600)
    _scheduler_loop(SYNTHESIZE_SCRIPT, "Synthesize", SYNTHESIZE_INTERVAL, running_check, "synthesize_scheduler")
    _scheduler_loop(PRUNE_SCRIPT, "Prune", PRUNE_INTERVAL, running_check, "prune_scheduler")
    _scheduler_loop(AUTO_CAPTURE_SCRIPT, "Auto-capture", AUTO_CAPTURE_INTERVAL, running_check, "auto_capture_scheduler", extra_args=["--batch"])
    _scheduler_loop(ANTI_PATTERN_SCRIPT, "Anti-pattern builder", ANTI_PATTERN_INTERVAL, running_check, "anti_pattern_scheduler", initial_delay=30)
    _scheduler_loop(PRIVATE_SYNTHESIS_SCRIPT, "Private synthesis", PRIVATE_SYNTHESIS_INTERVAL, running_check, "private_synthesis_scheduler")
    _scheduler_loop(PREDICTION_DAEMON, "Prediction", PREDICTION_INTERVAL, running_check, "prediction_scheduler", initial_delay=60, timeout=60, extra_args=["--once"])
    _scheduler_loop(BRAIN_HEALER_DAEMON, "Brain Healer", BRAIN_HEALER_INTERVAL, running_check, "brain_healer_scheduler", initial_delay=120, timeout=300)
    _scheduler_loop(PROFILE_DISTILLER_DAEMON, "Profile Distiller", PROFILE_DISTILLER_INTERVAL, running_check, "profile_distiller_scheduler", initial_delay=180, timeout=300, run_at_boot=False)
    _scheduler_loop(CONSTRAINT_ENGINE_DAEMON, "Constraint Engine", CONSTRAINT_ENGINE_INTERVAL, running_check, "constraint_engine_scheduler", initial_delay=240, timeout=300, extra_args=["--once"], run_at_boot=False)
    _scheduler_loop(GATE_FEEDBACK_DAEMON, "Gate Feedback Decay", GATE_FEEDBACK_DECAY_INTERVAL, running_check, "gate_feedback_scheduler", initial_delay=60, timeout=60, extra_args=["--decay"], run_at_boot=True)
    _scheduler_loop(PATHWEAVER_DAEMON, "Pathweaver", PATHWEAVER_INTERVAL, running_check, "pathweaver_scheduler", initial_delay=300, timeout=300, extra_args=["--once"], run_at_boot=True)
    _scheduler_loop(CAUSAL_RECOVERY_DAEMON, "Causal Recovery", CAUSAL_RECOVERY_INTERVAL, running_check, "causal_recovery_scheduler", initial_delay=60, timeout=120, extra_args=["--once"], run_at_boot=False)
    _scheduler_loop(SWARM_REFLEX_DAEMON, "Swarm Reflex", SWARM_REFLEX_INTERVAL, running_check, "swarm_reflex_scheduler", initial_delay=90, timeout=60, extra_args=["--once"], run_at_boot=True)
    _scheduler_loop(SCHEMA_DAEMON_SCRIPT, "Schema Daemon", SCHEMA_DAEMON_INTERVAL, running_check, "schema_daemon_scheduler", initial_delay=360, timeout=300, extra_args=["--once", "--verbose"], run_at_boot=False)
    _scheduler_loop(PROCEDURE_DAEMON_SCRIPT, "Procedure Daemon", PROCEDURE_DAEMON_INTERVAL, running_check, "procedure_daemon_scheduler", initial_delay=420, timeout=300, extra_args=["--once", "--verbose"], run_at_boot=False)
    _scheduler_loop(INTENT_DAEMON_SCRIPT, "Intent Daemon", INTENT_DAEMON_INTERVAL, running_check, "intent_daemon_scheduler", initial_delay=480, timeout=300, extra_args=["--once", "--verbose"], run_at_boot=False)
    _scheduler_loop(SLEEP_CYCLE_SCRIPT, "Sleep Cycle", SLEEP_CYCLE_INTERVAL, running_check, "sleep_cycle_scheduler", initial_delay=600, timeout=600, extra_args=["--verbose"], run_at_boot=False)
    _scheduler_loop(EPISTEMIC_FORAGER_DAEMON, "Epistemic Forager", EPISTEMIC_FORAGER_INTERVAL, running_check, "epistemic_forager_scheduler", initial_delay=660, timeout=300, extra_args=["--once"], run_at_boot=False)
    _scheduler_loop(SKILL_SYNTHESIZER_DAEMON, "Skill Synthesizer", SKILL_SYNTHESIZER_INTERVAL, running_check, "skill_synthesizer_scheduler", initial_delay=720, timeout=300, extra_args=["--once"], run_at_boot=False)
    _scheduler_loop(GHOST_RESONANCE_DAEMON, "Ghost Resonance", GHOST_RESONANCE_INTERVAL, running_check, "ghost_resonance_scheduler", initial_delay=180, timeout=120, run_at_boot=False)
    _scheduler_loop(HEBBIAN_BALANCER_DAEMON, "Hebbian Balancer", HEBBIAN_BALANCER_INTERVAL, running_check, "hebbian_balancer_scheduler", initial_delay=120, timeout=60, run_at_boot=False)
    _scheduler_loop(HOMEOSTATIC_DAEMON, "Homeostatic Scheduler", HOMEOSTATIC_INTERVAL, running_check, "homeostatic_scheduler", initial_delay=180, timeout=30, extra_args=["--once"], run_at_boot=False)
    _scheduler_loop(HEALER_DAEMON, "Procedural Sleep Healer", HEALER_INTERVAL, running_check, "healer_scheduler", initial_delay=540, timeout=120, extra_args=["--once"], run_at_boot=False)
    _scheduler_loop(PHYLOGENY_DAEMON, "Phylogenetic Compiler", PHYLOGENY_INTERVAL, running_check, "phylogeny_scheduler", initial_delay=780, timeout=300, extra_args=["--once", "-v"], run_at_boot=False)
    _scheduler_loop(AGENT_PROC_SYNTH_DAEMON, "Agent Procedure Synth", AGENT_PROC_SYNTH_INTERVAL, running_check, "agent_proc_synth_scheduler", initial_delay=840, timeout=300, extra_args=["--once", "-v"], run_at_boot=False)
    _scheduler_loop(PIPELINE_HEALTH_SCRIPT, "Pipeline Health", PIPELINE_HEALTH_INTERVAL, running_check, "pipeline_health_scheduler", initial_delay=30, timeout=30, extra_args=["--once", "--verbose"], run_at_boot=True)
    _scheduler_loop(CHOREOGRAPHER_DAEMON, "Swarm Choreographer", CHOREOGRAPHER_INTERVAL, running_check, "choreographer_scheduler", initial_delay=300, timeout=60, run_at_boot=False)
    _scheduler_loop(COGNITIVE_HUD_DAEMON, "Cognitive HUD", COGNITIVE_HUD_INTERVAL, running_check, "cognitive_hud_scheduler", initial_delay=120, timeout=30, extra_args=["--once"], run_at_boot=False)
    _scheduler_loop(ECHOREFINE_DAEMON, "EchoRefine Synthesis", ECHOREFINE_INTERVAL, running_check, "echorefine_scheduler", initial_delay=900, timeout=120, run_at_boot=False)
    _scheduler_loop(IMMUNITY_DAEMON, "Swarm Immunity", IMMUNITY_INTERVAL, running_check, "immunity_scheduler", initial_delay=120, timeout=60, run_at_boot=True)
    _scheduler_loop(NEUROPLASTICITY_DAEMON, "Neuroplasticity", NEUROPLASTICITY_INTERVAL, running_check, "neuroplasticity_scheduler", initial_delay=600, timeout=120, run_at_boot=False)
    _scheduler_loop(RESONANCE_DAEMON, "Prediction Resonance", RESONANCE_INTERVAL, running_check, "resonance_scheduler", initial_delay=660, timeout=120, run_at_boot=False)
    _scheduler_loop(CONTENTION_CORTEX_DAEMON, "Contention Cortex", CONTENTION_CORTEX_INTERVAL, running_check, "contention_cortex_scheduler", initial_delay=360, timeout=120, run_at_boot=False)
    _scheduler_loop(SKILL_RESONANCE_DAEMON, "Skill Resonance", SKILL_RESONANCE_INTERVAL, running_check, "skill_resonance_scheduler", initial_delay=420, timeout=60, run_at_boot=False)
    _scheduler_loop(INTENT_TOPOLOGY_DAEMON, "Intent Topology", INTENT_TOPOLOGY_INTERVAL, running_check, "intent_topology_scheduler", initial_delay=480, timeout=120, run_at_boot=False)
    _scheduler_loop(JIT_INOCULATION_DAEMON, "JIT Inoculation", JIT_INOCULATION_INTERVAL, running_check, "jit_inoculation_scheduler", initial_delay=60, timeout=30, run_at_boot=True)
    _scheduler_loop(COGNITIVE_TRIAGE_DAEMON, "Cognitive Triage", COGNITIVE_TRIAGE_INTERVAL, running_check, "cognitive_triage_scheduler", initial_delay=30, timeout=30, run_at_boot=True)
    _scheduler_loop(GC_DAEMON_SCRIPT, "GC Daemon", GC_DAEMON_INTERVAL, running_check, "gc_daemon_scheduler", initial_delay=300, timeout=120, run_at_boot=False)
    _scheduler_loop(MEMORY_DISPATCHER_SCRIPT, "Memory Dispatcher", MEMORY_DISPATCHER_INTERVAL, running_check, "memory_dispatcher_scheduler", initial_delay=300, timeout=120, run_at_boot=False)

    # In-process reconsolidation sweep — cleans expired labile items every 60s
    # Lightweight (no subprocess needed), just reads/writes a small JSON file
    def _reconsolidation_sweep_loop():
        while running_check():
            try:
                from pulse.brain.plasticity.reconsolidation import _sweep_expired
                _sweep_expired()
            except Exception:
                pass
            time.sleep(60)
    t = threading.Thread(target=_reconsolidation_sweep_loop, daemon=True, name="reconsolidation_sweep")
    t.start()
