#!/usr/bin/env python3
from __future__ import annotations

import json
import threading
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Callable, Dict

try:
    import fcntl
except ImportError:  # pragma: no cover
    fcntl = None

try:
    from .cheapclaw_runtime_state import CheapClawPaths, ensure_runtime_layout, load_panel_for_paths, save_panel_for_paths, now_iso
    from .tool_runtime_helpers import ensure_conversation, refresh_conversation_context_file
except ImportError:
    from cheapclaw_runtime_state import CheapClawPaths, ensure_runtime_layout, load_panel_for_paths, save_panel_for_paths, now_iso
    from tool_runtime_helpers import ensure_conversation, refresh_conversation_context_file


class CheapClawPanelStore:
    def __init__(self, paths: CheapClawPaths, history_preview_limit: int = 50):
        self.paths = paths
        self.history_preview_limit = max(1, int(history_preview_limit))
        self._thread_lock = threading.RLock()
        self.ensure_layout()

    def ensure_layout(self) -> None:
        ensure_runtime_layout(self.paths)

    @contextmanager
    def _file_lock(self):
        self.paths.panel_dir.mkdir(parents=True, exist_ok=True)
        with self._thread_lock:
            with open(self.paths.panel_lock_path, "a+", encoding="utf-8") as lock_file:
                if fcntl is not None:
                    fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
                try:
                    yield
                finally:
                    if fcntl is not None:
                        fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)

    def load_panel(self) -> Dict[str, Any]:
        return load_panel_for_paths(self.paths)

    def save_panel(self, panel: Dict[str, Any], *, backup: bool = True) -> Dict[str, Any]:
        with self._file_lock():
            return save_panel_for_paths(self.paths, panel, backup=backup)

    def mutate(self, updater: Callable[[Dict[str, Any]], Dict[str, Any] | None]) -> Dict[str, Any]:
        with self._file_lock():
            current = load_panel_for_paths(self.paths)
            updated = updater(current)
            panel = current if updated is None else updated
            return save_panel_for_paths(self.paths, panel, backup=True)

    def record_social_message(self, **kwargs) -> Dict[str, Any]:
        timestamp = kwargs.get("timestamp") or now_iso()
        channel = str(kwargs.get("channel") or "").strip()
        conversation_id = str(kwargs.get("conversation_id") or "").strip()
        message_text = str(kwargs.get("message_text") or "").strip()
        attachments = kwargs.get("attachments") or []
        if not channel or not conversation_id or (not message_text and not attachments):
            raise ValueError("channel, conversation_id and message_text/attachments are required")

        def _update(panel: Dict[str, Any]) -> Dict[str, Any]:
            conv = ensure_conversation(
                panel,
                channel=channel,
                conversation_id=conversation_id,
                conversation_type=str(kwargs.get("conversation_type") or "group"),
                display_name=kwargs.get("display_name") or conversation_id,
                require_mention=bool(kwargs.get("require_mention", True)),
            )
            event = {
                "message_id": str(kwargs.get("message_id") or "").strip(),
                "timestamp": timestamp,
                "sender_id": str(kwargs.get("sender_id") or "").strip(),
                "sender_name": str(kwargs.get("sender_name") or "").strip(),
                "text": message_text,
                "attachments": attachments,
                "is_mention_to_bot": bool(kwargs.get("is_mention_to_bot", False)),
                "direction": "inbound",
            }
            history_path = Path(conv["message_history_path"])
            history_path.parent.mkdir(parents=True, exist_ok=True)
            with open(history_path, "a", encoding="utf-8") as fh:
                fh.write(json.dumps(event, ensure_ascii=False) + "\n")
            conv.setdefault("messages", []).append(event)
            del conv["messages"][:-self.history_preview_limit]
            conv.setdefault("pending_events", []).append({"type": "social_message", "timestamp": timestamp, "message_id": event["message_id"]})
            conv["updated_at"] = timestamp
            conv["latest_user_message_at"] = timestamp
            conv["unread_event_count"] = len(conv["pending_events"])
            return panel

        panel = self.mutate(_update)
        refresh_conversation_context_file(channel, conversation_id, panel)
        return panel

    def set_main_agent_state(self, *, running: bool, run_id: str = "") -> Dict[str, Any]:
        def _update(panel: Dict[str, Any]) -> Dict[str, Any]:
            state = panel["service_state"]
            state["main_agent_running"] = bool(running)
            if run_id:
                state["main_agent_run_id"] = run_id
            if running:
                state["main_agent_last_started_at"] = now_iso()
            else:
                state["main_agent_last_finished_at"] = now_iso()
            return panel

        return self.mutate(_update)

    def mark_watchdog_tick(self) -> Dict[str, Any]:
        def _update(panel: Dict[str, Any]) -> Dict[str, Any]:
            panel["service_state"]["watchdog_last_run_at"] = now_iso()
            return panel

        return self.mutate(_update)
