import json
import threading
from pathlib import Path
from datetime import datetime, timezone

from pulse.core.paths import get_state_dir
from pulse.core import event_bus
from pulse.core.brain_db import DB_PATH
from pulse.core.brain_utils import HIPPOCAMPUS_DIR

INTEROCEPTION_DIR = get_state_dir() / "interoception"
SIGNALS_FILE = INTEROCEPTION_DIR / "signals.json"
TASK_BOARD_FILE = get_state_dir() / "task_board.json"

_lock = threading.RLock()
_signals = {}

def _init_signals():
    return {
        "memory_pressure": {"current_value": 0.0, "baseline": 50.0, "threshold": 500.0, "trend": []},
        "search_latency": {"current_value": 0.0, "baseline": 0.05, "threshold": 1.0, "trend": []},
        "extraction_yield": {"current_value": 1.0, "baseline": 0.8, "threshold": 0.3, "trend": []},
        "consolidation_health": {"current_value": 0.0, "baseline": 0.0, "threshold": 1000.0, "trend": []},
        "agent_load": {"current_value": 0.0, "baseline": 2.0, "threshold": 10.0, "trend": []},
        "error_rate": {"current_value": 0.0, "baseline": 0.0, "threshold": 0.15, "trend": []}
    }

def load_signals():
    global _signals
    with _lock:
        if not SIGNALS_FILE.exists():
            _signals = _init_signals()
            return
        try:
            with open(SIGNALS_FILE, "r", encoding="utf-8") as f:
                loaded = json.load(f)
                _signals = _init_signals()
                for k, v in loaded.items():
                    if k in _signals:
                        _signals[k].update(v)
        except Exception:
            _signals = _init_signals()

def save_signals():
    with _lock:
        INTEROCEPTION_DIR.mkdir(parents=True, exist_ok=True)
        with open(SIGNALS_FILE, "w", encoding="utf-8") as f:
            json.dump(_signals, f, indent=2)


def _get_signals() -> dict:
    """Return flat dict of {signal_name: current_value} for quick access."""
    try:
        if not _signals:
            load_signals()
        result = {}
        for name, sig in _signals.items():
            if isinstance(sig, dict):
                result[name] = sig.get("current_value", 0)
            else:
                result[name] = sig
        return result
    except Exception:
        return {}

def update_signal(name: str, value: float):
    with _lock:
        if not _signals:
            load_signals()
        if name in _signals:
            sig = _signals[name]
            sig["current_value"] = value
            sig["trend"].append(value)
            if len(sig["trend"]) > 10:
                sig["trend"].pop(0)

def get_alerts():
    """Returns signals exceeding thresholds and publishes them to the event bus."""
    with _lock:
        if not _signals:
            load_signals()
        alerts = []
        for name, sig in _signals.items():
            val = sig["current_value"]
            thresh = sig["threshold"]
            
            # For extraction yield, lower is worse. Everything else, higher is worse.
            is_alert = False
            if name == "extraction_yield":
                if val < thresh:
                    is_alert = True
            else:
                if val > thresh:
                    is_alert = True
            
            if is_alert:
                alert_data = {
                    "signal": name,
                    "value": val,
                    "threshold": thresh,
                    "trend": sig["trend"][:]
                }
                alerts.append(alert_data)
                event_bus.publish("interoception.alert", "interoception", alert_data)
        return alerts

def _sample(external_metrics: dict = None):
    """Samples real system state and updates signals."""
    with _lock:
        if not _signals:
            load_signals()

        # 1. memory_pressure (DB size in MB)
        db_size_mb = 0.0
        if DB_PATH.exists():
            db_size_mb = DB_PATH.stat().st_size / (1024 * 1024)
        update_signal("memory_pressure", round(db_size_mb, 2))

        # 2. agent_load (active + claimed tasks)
        load_count = 0
        if TASK_BOARD_FILE.exists():
            try:
                with open(TASK_BOARD_FILE, "r", encoding="utf-8") as f:
                    tb = json.load(f)
                    tasks = []
                    for d in tb.get("dispatches", []):
                        tasks.extend(d.get("tasks", []))
                    load_count = sum(1 for t in tasks if t.get("status") in ["active", "claimed"])
            except Exception:
                pass
        update_signal("agent_load", float(load_count))
        
        # 3. consolidation_health (number of brain files)
        file_count = 0
        if HIPPOCAMPUS_DIR.exists():
            try:
                # Limit to json/jsonl mapping roughly
                file_count = sum(1 for _ in HIPPOCAMPUS_DIR.rglob("*.json*"))
            except Exception:
                pass
        update_signal("consolidation_health", float(file_count))

        # Update other signals if provided via external_metrics
        if external_metrics:
            for k in ["search_latency", "extraction_yield", "error_rate"]:
                if k in external_metrics:
                    update_signal(k, float(external_metrics[k]))

        save_signals()
get_signals = _get_signals


sample = _sample
