#!/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
ROOT_DIR = SCRIPT_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

# --- Script Paths ---
DEDUP_SCRIPT = SCRIPT_DIR / "synthesis" / "pulse_dedup.py"
CONSOLIDATION_SCRIPT = SCRIPT_DIR / "consolidation" / "consolidation_daemon.py"
SYNTHESIZE_SCRIPT = SCRIPT_DIR / "synthesis" / "synthesize_plans.py"
PRUNE_SCRIPT = ROOT_DIR / "pulse" / "admin" / "brain_ops" / "pulse_prune.py"
AUTO_CAPTURE_SCRIPT = ROOT_DIR / "pulse" / "brain" / "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 = SCRIPT_DIR / "neural" / "prediction_daemon.py"
BRAIN_HEALER_DAEMON = SCRIPT_DIR / "synthesis" / "brain_healer.py"
PROFILE_DISTILLER_DAEMON = SCRIPT_DIR / "synthesis" / "profile_distiller.py"
CONSTRAINT_ENGINE_DAEMON = SCRIPT_DIR / "synthesis" / "constraint_engine.py"
GATE_FEEDBACK_DAEMON = SCRIPT_DIR / "neural" / "gate_feedback.py"
PATHWEAVER_DAEMON = SCRIPT_DIR / "synthesis" / "pathweaver.py"
CAUSAL_RECOVERY_DAEMON = SCRIPT_DIR / "synthesis" / "causal_claim_recovery.py"
SWARM_REFLEX_DAEMON = SCRIPT_DIR / "synthesis" / "swarm_reflex.py"
EPISTEMIC_FORAGER_DAEMON = SCRIPT_DIR / "synthesis" / "epistemic_forager.py"
SKILL_SYNTHESIZER_DAEMON = SCRIPT_DIR / "synthesis" / "skill_dependency_synthesizer.py"
GHOST_RESONANCE_DAEMON = SCRIPT_DIR / "synthesis" / "ghost_resonance.py"
HEBBIAN_BALANCER_DAEMON = SCRIPT_DIR / "synthesis" / "hebbian_balancer.py"
HOMEOSTATIC_INTERVAL = 60                 # D13 Homeostatic Scheduler: 60 seconds
HOMEOSTATIC_DAEMON = SCRIPT_DIR / "synthesis" / "homeostatic_scheduler.py"
HEALER_INTERVAL = 12 * 3600              # D14 Procedural Sleep Healer: 12 hours
HEALER_DAEMON = SCRIPT_DIR / "synthesis" / "procedural_sleep_healer.py"
PHYLOGENY_INTERVAL = 12 * 3600           # D15 Phylogenetic Skill Compiler: 12 hours
PHYLOGENY_DAEMON = SCRIPT_DIR / "synthesis" / "skill_phylogeny.py"
AGENT_PROC_SYNTH_INTERVAL = 24 * 3600   # D16 Agent Procedure Synthesizer: 24 hours
AGENT_PROC_SYNTH_DAEMON = SCRIPT_DIR / "synthesis" / "agent_procedure_synthesizer.py"
SCHEMA_DAEMON_SCRIPT = SCRIPT_DIR / "synthesis" / "schema_daemon.py"
PROCEDURE_DAEMON_SCRIPT = SCRIPT_DIR / "synthesis" / "procedure_daemon.py"
INTENT_DAEMON_SCRIPT = SCRIPT_DIR / "bridge" / "intent_daemon.py"
SLEEP_CYCLE_SCRIPT = SCRIPT_DIR / "synthesis" / "sleep_cycle.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, printing output lines with [PULSE] prefix."""
    if not script.exists():
        return
    cmd = [sys.executable, str(script)]
    if extra_args:
        cmd.extend(extra_args)
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
        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:
        print(f"[PULSE] {label} timed out after {timeout}s.")
    except Exception as e:
        print(f"[PULSE] {label} error: {e}")

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=300)
    _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)
