"""Template supervisor — pair with voice/model_script.py when adding a new model.

The supervisor's job is to run a voice subprocess and tail its stdout for
state transitions + user transcripts. It is brand-specific on purpose:
each model's brain emits log lines in its own format, so each model gets
its own supervisor. See `gemini/subprocess.py` for the working reference.

How to add a new model (e.g. OpenAI Realtime):

  1. cp voice/model_script.py       openai/script.py
  2. cp voice/model_subprocess.py   openai/subprocess.py
  3. In both files: rename `ModelBrain` → `OpenAIRealtimeBrain`,
     `ModelSubprocess` → `OpenAIRealtimeSubprocess`.
  4. In `openai/script.py`: fill in the TODO bodies (connect/send/receive).
     Each `log.info("USER: %s", ...)` / `log.info("BOT: %s", ...)` /
     state message must be a string your supervisor's `_track_line` below
     can detect — keep them in lock-step.
  5. In `openai/subprocess.py`: update `_track_line` to match the strings
     your brain actually emits.
  6. In `main.py`: swap `GeminiSubprocess` → `OpenAIRealtimeSubprocess` in
     the `live_sub = _safe_construct(...)` line. In `voice/sanad_voice.py`,
     add a branch to `_build_brain()` mapping `"openai"` → `OpenAIRealtimeBrain`.
  7. Run with `SANAD_VOICE_BRAIN=openai python3 voice/sanad_voice.py eth0`.

Nothing in `gemini/` needs to change.
"""

from __future__ import annotations

import os
import signal
import subprocess
import sys
import threading
import time
from collections import deque
from datetime import datetime
from pathlib import Path
from typing import Any

from Project.Sanad.config import BASE_DIR, LOGS_DIR, SCRIPTS_DIR
from Project.Sanad.core.config_loader import section as _cfg_section
from Project.Sanad.core.logger import get_logger

log = get_logger("model_subprocess")


class ModelSubprocess:
    """Skeleton supervisor — adapt for your model.

    Contract expected by `main.py` + `dashboard/routes/live_subprocess.py`:
      start()      — sync. Spawns the child, starts the log reader thread.
      stop()       — sync. SIGINT / SIGTERM / SIGKILL escalation.
      status()     — returns {state, state_message, running, pid, log_tail,
                              user_transcript, last_user_text, ...}.
      log_tail        : deque[str]   last N cleaned stdout lines
      user_transcript : deque[str]   user transcripts parsed from child's log
      last_user_text  : str          most recent transcript (convenience)
      state           : str          one of {"stopped", "starting", "connecting",
                                             "listening", "hearing", "interrupting",
                                             "error", "warning", "crashed"}
    """

    def __init__(self):
        # TODO: set a config section key — e.g. `_cfg_section("openai", "subprocess")`.
        # Create `config/<brand>_config.json > subprocess: { ... }` matching
        # gemini_config.json's layout.
        self._cfg = {}  # _cfg_section("<brand>", "subprocess")

        self._lock = threading.Lock()
        self.process: subprocess.Popen | None = None
        self.log_tail: deque[str] = deque(
            maxlen=self._cfg.get("log_tail_size", 2000))
        self.user_transcript: deque[str] = deque(
            maxlen=self._cfg.get("transcript_tail_size", 30))
        self._reader_thread: threading.Thread | None = None
        self._log_file = None
        self.state = "stopped"
        self.state_message = "Idle."
        self.last_user_text = ""

    # ─── spawn / kill ─────────────────────────────────────

    def start(self) -> dict:
        # TODO: build env (include `SANAD_VOICE_BRAIN=<yourbrand>` so
        # sanad_voice.py picks your brain), pick the script path, and
        # `subprocess.Popen(...)`. Copy the gemini/subprocess.py body.
        raise NotImplementedError

    def stop(self, timeout: float = 3.0) -> dict:
        # TODO: send SIGINT → wait → SIGTERM → wait → SIGKILL.
        raise NotImplementedError

    # ─── log parsing — the brand-specific part ────────────

    def _track_line(self, line: str) -> None:
        """Translate your brain's log strings into state + transcripts.

        KEEP THIS IN LOCK-STEP with the `log.info(...)` calls in your
        brain. Minimum required detections:

          connecting   — child opened a session to the model
          listening    — session connected OR a turn finished
          hearing      — user transcript arrived  (APPEND to user_transcript)
          interrupting — barge-in / model interrupted
          error        — fatal session error
          stopped      — clean shutdown
        """
        # Example (replace with your brain's actual strings):
        #
        # if "connecting to OpenAI" in line:
        #     self._set_state("connecting", line)
        # elif "session open" in line:
        #     self._set_state("listening", "Listening for speech.")
        # elif "USER: " in line:
        #     text = line.split("USER: ", 1)[1].strip()
        #     if text:
        #         self.last_user_text = text
        #         self.user_transcript.append(text)
        #         self._set_state("hearing", f"User: {text}")
        # elif "BARGE-IN" in line:
        #     self._set_state("interrupting", line)
        # elif "session error" in line:
        #     self._set_state("error", line)
        # elif "cancelled — stopping" in line:
        #     self._set_state("stopped", line)
        raise NotImplementedError

    def _set_state(self, state: str, msg: str) -> None:
        self.state = state
        self.state_message = msg

    # ─── status + introspection ───────────────────────────

    def status(self) -> dict:
        with self._lock:
            proc = self.process
            running = proc is not None and proc.poll() is None
            return {
                "running": running,
                "pid": proc.pid if running else None,
                "state": self.state,
                "state_message": self.state_message,
                "last_user_text": self.last_user_text,
                "log_tail": list(self.log_tail)[-50:],
                "user_transcript": list(self.user_transcript),
            }
