"""
Swarm Blackboard — in-process dict with file persistence for cross-agent coordination.

All agents (Macro Commander, Micro Sniper, Risk Commissar) read/write shared
state through a Blackboard instance, backed by a JSON file for crash recovery.
"""

from __future__ import annotations

import json
import os
import pathlib
import threading
import time
from typing import Any, Optional


class Blackboard(dict):
    """Dict-backed blackboard with JSON file persistence.

    Each section (e.g. "Macro_Commander") is a nested dict stored under a
    top-level key.  The optional _file_path auto-saves on every write.
    """

    def __init__(self, file_path: Optional[str] = None):
        super().__init__()
        self.file_path: Optional[str] = file_path
        self._lock = threading.Lock()
        if file_path:
            self._load()

    def update(self, namespace: str, key: str, value: Any) -> bool:
        with self._lock:
            section = self.setdefault(namespace, {})
            if not isinstance(section, dict):
                section = {}
                self[namespace] = section
            section[key] = value
            self._save()
            return True

    def get_snapshot(self, include_logs: int = 1) -> dict:
        """Return a shallow copy of the blackboard state.

        When include_logs=0, strip ``_logs`` lists from every namespace
        so downstream consumers don't carry around unbounded log arrays.
        """
        with self._lock:
            data = dict(self)
        if include_logs == 0:
            for section in data.values():
                if isinstance(section, dict) and "_logs" in section:
                    section = dict(section)
                    section.pop("_logs", None)
        return data

    def add_log(self, role: str, msg_type: str, text: str) -> None:
        with self._lock:
            section = self.setdefault(role, {})
            log_list = section.setdefault("_logs", [])
            log_list.append({
                "ts": time.time(),
                "type": msg_type,
                "text": text,
            })
            if len(log_list) > 200:
                section["_logs"] = log_list[-200:]
            self._save()

    def _ensure(self) -> Blackboard:
        return self

    def _load(self) -> None:
        if not self.file_path:
            return
        try:
            p = pathlib.Path(self.file_path)
            if p.exists():
                raw = p.read_text(encoding="utf-8")
                if raw.strip():
                    data = json.loads(raw)
                    if isinstance(data, dict):
                        Blackboard._deep_merge(self, data)
        except Exception:
            pass

    @staticmethod
    def _deep_merge(base: dict, incoming: dict) -> None:
        for key, value in incoming.items():
            if key in base and isinstance(base[key], dict) and isinstance(value, dict):
                Blackboard._deep_merge(base[key], value)
            else:
                base[key] = value

    def _save(self) -> None:
        if not self.file_path:
            return
        try:
            p = pathlib.Path(self.file_path)
            p.parent.mkdir(parents=True, exist_ok=True)
            # Reload from disk and deep-merge so that writes from other
            # Blackboard instances (e.g. gold_trader vs pipeline) are
            # merged rather than clobbered.
            if p.exists():
                try:
                    raw = p.read_text(encoding="utf-8")
                    if raw.strip():
                        data = json.loads(raw)
                        if isinstance(data, dict):
                            Blackboard._deep_merge(self, data)
                except Exception:
                    pass
            # Atomic write to avoid corruption from concurrent writers.
            tmp_path = p.with_suffix(".tmp" + str(hash(str(p))))
            tmp_path.write_text(json.dumps(dict(self), ensure_ascii=False, default=str), encoding="utf-8")
            tmp_path.replace(p)
        except Exception:
            pass


from src.data_layer import blackboard_state_path


def _resolve_default_blackboard_path() -> str:
    try:
        p = blackboard_state_path()
        p.parent.mkdir(parents=True, exist_ok=True)
        return str(p)
    except Exception:
        return str(pathlib.Path.home() / ".gold_autotrade" / "blackboard_state.json")


global_blackboard = Blackboard(_resolve_default_blackboard_path())
