# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""D27: Cognitive Triage — emergency resource redirection."""

import json
import logging
import sys
from datetime import datetime, timezone, timedelta
from pathlib import Path

_log = logging.getLogger("pulse.d27_triage")

try:
    from pulse.core.brain_utils import STATE_DIR
except Exception:
    STATE_DIR = Path("state")

HEALTH_DIR = STATE_DIR / "health"
DAEMON_HEALTH_FILE = HEALTH_DIR / "daemon_health.json"
OVERRIDE_FILE = STATE_DIR / "homeostatic_overrides.json"
METRICS_FILE = HEALTH_DIR / "cognitive_triage.json"

_SEVERITY_THRESHOLD = 0.6
_TRIAGE_DURATION_MIN = 30
_TRIAGE_MAX_HOURS = 2
_EMERGENCY_SALIENCE = 8.0
_SUSPEND_MULTIPLIER = 10

_SUSPENDABLE = {
    "sleep_cycle_scheduler", "epistemic_forager_scheduler",
    "skill_synthesizer_scheduler", "profile_distiller_scheduler",
    "prune_scheduler", "schema_daemon_scheduler",
    "procedure_daemon_scheduler", "intent_daemon_scheduler",
    "synthesize_scheduler", "phylogeny_scheduler",
    "agent_proc_synth_scheduler",
}


def _load_daemon_health() -> dict:
    if not DAEMON_HEALTH_FILE.exists():
        return {}
    try:
        return json.loads(DAEMON_HEALTH_FILE.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return {}


def _detect_failures() -> list[dict]:
    failures = []
    health = _load_daemon_health()
    now = datetime.now(timezone.utc)
    stale_cutoff = now - timedelta(minutes=10)
    for name, info in health.items():
        if not isinstance(info, dict):
            continue
        if info.get("status") == "error":
            failures.append({"type": "daemon_error", "daemon": name, "severity": 0.7})
        ts_str = info.get("timestamp", "")
        if ts_str:
            try:
                ts = datetime.fromisoformat(ts_str)
                if ts < stale_cutoff:
                    failures.append({"type": "daemon_stale", "daemon": name,
                                     "severity": 0.5, "last_seen": ts_str})
            except (ValueError, TypeError):
                pass
        metrics = info.get("metrics", {})
        if isinstance(metrics, dict) and metrics.get("exit_code", 0) != 0:
            failures.append({"type": "daemon_exit_error", "daemon": name, "severity": 0.6})
    contention_file = HEALTH_DIR / "contention_tracker.json"
    if contention_file.exists():
        try:
            ct = json.loads(contention_file.read_text(encoding="utf-8"))
            for fname, data in ct.items():
                if data.get("max_wait", 0) > 30:
                    failures.append({"type": "extreme_contention", "file": fname,
                                     "severity": 0.8, "max_wait": data["max_wait"]})
        except (json.JSONDecodeError, OSError):
            pass
    return failures


def _compute_max_severity(failures: list[dict]) -> float:
    return max((f.get("severity", 0) for f in failures), default=0.0)


def _write_triage_overrides(suspend: bool):
    import os
    try:
        existing = {}
        if OVERRIDE_FILE.exists():
            try:
                existing = json.loads(OVERRIDE_FILE.read_text(encoding="utf-8"))
            except (json.JSONDecodeError, OSError):
                pass
        overrides = existing.get("overrides", {})
        if suspend:
            for sched in _SUSPENDABLE:
                overrides[sched] = 3600 * _SUSPEND_MULTIPLIER
        else:
            for sched in _SUSPENDABLE:
                overrides.pop(sched, None)
        data = {
            "overrides": overrides,
            "stress": existing.get("stress", 0.5),
            "zone": existing.get("zone", "elevated"),
            "updated": datetime.now(timezone.utc).isoformat(),
            "_triage_active": suspend,
        }
        OVERRIDE_FILE.parent.mkdir(parents=True, exist_ok=True)
        tmp = OVERRIDE_FILE.with_suffix(".tmp")
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
        os.replace(str(tmp), str(OVERRIDE_FILE))
    except OSError as e:
        _log.warning("Failed to write triage overrides: %s", e)


def _inject_emergency_context(failures: list[dict]) -> int:
    try:
        from pulse.brain.brain_search import brain_search
        from pulse.brain.memory.hot_cache import write_hot
    except ImportError:
        return 0
    terms = set()
    for f in failures[:5]:
        terms.add(f.get("daemon", f.get("file", "system")))
        terms.add(f.get("type", ""))
    query = " ".join(terms) + " recovery fix prevent"
    results = brain_search(query, max_per_source=3)
    items = []
    for source_items in results.get("results", {}).values():
        if isinstance(source_items, list):
            items.extend(source_items)
    items.sort(key=lambda x: x.get("relevance", 0), reverse=True)
    injected = 0
    now = datetime.now(timezone.utc).isoformat()
    for item in items[:5]:
        entry = {
            "content": item.get("text", "")[:300],
            "title": f"[TRIAGE] {item.get('title', '')[:60]}",
            "type": "triage", "salience": _EMERGENCY_SALIENCE,
            "confidence": item.get("confidence", 0.7),
            "source": "cognitive_triage", "domain": item.get("domain", "system"),
            "timestamp": now,
        }
        if write_hot(entry):
            injected += 1
    return injected


def _load_state() -> dict:
    if not METRICS_FILE.exists():
        return {"runs": 0, "triage_active": False, "total_events": 0}
    try:
        return json.loads(METRICS_FILE.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return {"runs": 0, "triage_active": False, "total_events": 0}


def _save_state(state: dict):
    import os
    METRICS_FILE.parent.mkdir(parents=True, exist_ok=True)
    tmp = METRICS_FILE.with_suffix(".tmp")
    try:
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump(state, f, indent=2, ensure_ascii=False)
        os.replace(str(tmp), str(METRICS_FILE))
    except OSError:
        if tmp.exists():
            tmp.unlink()


def run_once(verbose: bool = False) -> dict:
    """Run one triage cycle: detect → assess → respond."""
    state = _load_state()
    failures = _detect_failures()
    severity = _compute_max_severity(failures)
    just_cleared = False
    if state.get("triage_active"):
        started = state.get("triage_started", "")
        if started:
            try:
                ts = datetime.fromisoformat(started)
                elapsed = (datetime.now(timezone.utc) - ts).total_seconds()
                if elapsed > _TRIAGE_MAX_HOURS * 3600:
                    if verbose: _log.info("Triage expired (>%dh) — clearing", _TRIAGE_MAX_HOURS)
                    state["triage_active"] = False
                    _write_triage_overrides(suspend=False)
                    just_cleared = True
                elif elapsed > _TRIAGE_DURATION_MIN * 60:
                    if verbose: _log.info("Triage soft timeout (>%d min) — auto-clearing", _TRIAGE_DURATION_MIN)
                    state["triage_active"] = False
                    _write_triage_overrides(suspend=False)
                    just_cleared = True
            except (ValueError, TypeError):
                pass
    now = datetime.now(timezone.utc).isoformat()
    injected = 0
    if severity >= _SEVERITY_THRESHOLD and not state.get("triage_active") and not just_cleared:
        if verbose: _log.info("TRIAGE ACTIVATED — severity=%.2f, %d failure(s)", severity, len(failures))
        _write_triage_overrides(suspend=True)
        injected = _inject_emergency_context(failures)
        state.update(triage_active=True, triage_started=now, current_severity=severity,
                     current_failures=[f.get("type", "") for f in failures[:5]],
                     total_events=state.get("total_events", 0) + 1)
        if verbose: _log.info("  Suspended %d low-priority daemons, injected %d items", len(_SUSPENDABLE), injected)
    elif severity < _SEVERITY_THRESHOLD * 0.5 and state.get("triage_active"):
        if verbose: _log.info("Triage cleared — severity=%.2f (below threshold)", severity)
        _write_triage_overrides(suspend=False)
        state["triage_active"] = False
        state["triage_started"] = None
    elif state.get("triage_active"):
        _write_triage_overrides(suspend=True)
        injected = _inject_emergency_context(failures) if failures else 0
    state["runs"] = state.get("runs", 0) + 1
    state["last_run"] = now
    state["last_severity"] = severity
    state["emergency_injections"] = state.get("emergency_injections", 0) + injected
    _save_state(state)
    return {"failures": len(failures), "severity": severity,
            "triage_active": state.get("triage_active", False),
            "injected": injected, "status": "ok"}


def cmd_triage(args: list):
    """CLI entry point for `pulse triage`."""
    logging.basicConfig(level=logging.INFO, format="%(message)s")
    if args and args[0] == "status":
        s = _load_state()
        active = "ACTIVE" if s.get("triage_active") else "inactive"
        print(f"[D27] Cognitive Triage — {active}, Runs: {s.get('runs', 0)}, Events: {s.get('total_events', 0)}")
        print(f"  Last severity: {s.get('last_severity', 0):.2f}, Emergency injections: {s.get('emergency_injections', 0)}")
        if s.get("triage_active"):
            print(f"  Triage started: {s.get('triage_started', '?')}")
            print(f"  Failures: {s.get('current_failures', [])}")
        print(f"  Last run: {s.get('last_run', 'never')}")
        return
    print(json.dumps(run_once(verbose=True), indent=2))

if __name__ == "__main__": cmd_triage(sys.argv[1:])
