"""Run the worker as an app-managed subprocess backed by a local runtime."""

from __future__ import annotations

import subprocess
import sys
import threading
import traceback
from pathlib import Path
from typing import TextIO

import structlog

from hive_compute.app.runtime_bootstrap import ensure_worker_runtime, sanitized_runtime_env
from hive_compute.app.state import AppState, WorkerState

logger = structlog.get_logger()

_CONFIG_PATH = "~/.ambient/config.yaml"


class AppWorkerThread:
    """Manage a long-lived worker subprocess from the menu bar app."""

    def __init__(self, state: AppState) -> None:
        self.state = state
        self._thread: threading.Thread | None = None
        self._paused = threading.Event()
        self._stop = threading.Event()
        self._process: subprocess.Popen[str] | None = None
        self._log_handle: TextIO | None = None
        self._client = None

    def start(self) -> None:
        self._stop.clear()
        self._thread = threading.Thread(target=self._run, daemon=True, name="hive-worker")
        self._thread.start()

    def pause(self) -> None:
        self._paused.set()
        self._stop_process()
        self.state.worker_state = WorkerState.PAUSED
        logger.info("Worker paused by user")

    def resume(self) -> None:
        self._paused.clear()
        self.state.error_message = None
        self.state.worker_state = WorkerState.IDLE
        logger.info("Worker resumed by user")

    def stop(self) -> None:
        self._stop.set()
        self._paused.clear()
        self._stop_process()

    def is_paused(self) -> bool:
        return self._paused.is_set()

    def get_client(self):
        """Return an authenticated coordinator client, if available."""
        return self._client

    def _run(self) -> None:
        from hive_compute.worker.api_client import build_coordinator_client
        from hive_compute.worker.config_model import load_worker_config_raw

        config_path = Path(_CONFIG_PATH).expanduser()
        restart_delay_seconds = 2.0
        max_restart_delay_seconds = 30.0
        self.state.worker_state = WorkerState.STARTING
        self.state.error_message = None

        try:
            runtime = ensure_worker_runtime()
            config = load_worker_config_raw(str(config_path))
            self._client = build_coordinator_client(config)
        except Exception as exc:
            _safe_log("exception", "Worker runtime bootstrap failed", error=str(exc))
            self.state.worker_state = WorkerState.ERROR
            self.state.error_message = str(exc)
            return

        _safe_log("info", "Worker runtime ready", worker_id=config.get("worker_id"), runtime=str(runtime.venv_dir))

        while not self._stop.is_set():
            if self._paused.is_set():
                self.state.worker_state = WorkerState.PAUSED
                self._stop.wait(1)
                continue

            if self._process is None:
                self._process = self._start_process(runtime.cli_command("start", "--config", str(config_path)))
                self.state.worker_state = WorkerState.IDLE
                self.state.error_message = None
                restart_delay_seconds = 2.0

            if self._process.poll() is None:
                self.state.worker_state = WorkerState.IDLE
                self._stop.wait(2)
                continue

            exit_code = self._process.returncode
            self._process = None
            self._close_log_handle()
            if self._stop.is_set():
                break

            self.state.worker_state = WorkerState.ERROR
            self.state.error_message = f"Worker runtime exited with code {exit_code}. Restarting…"
            _safe_log("warning", "Worker runtime exited unexpectedly", exit_code=exit_code)
            if self._stop.wait(restart_delay_seconds):
                break
            restart_delay_seconds = min(restart_delay_seconds * 2, max_restart_delay_seconds)

        self._stop_process()
        self.state.worker_state = WorkerState.STOPPED

    def _start_process(self, command: list[str]) -> subprocess.Popen[str]:
        log_path = Path("~/.ambient/logs/app-worker-runtime.log").expanduser()
        log_path.parent.mkdir(parents=True, exist_ok=True)
        if self._log_handle is not None and not self._log_handle.closed:
            self._log_handle.close()
        self._log_handle = log_path.open("a", encoding="utf-8")
        _safe_log("info", "Starting worker runtime", command=command)
        return subprocess.Popen(
            command,
            stdout=self._log_handle,
            stderr=subprocess.STDOUT,
            text=True,
            start_new_session=True,
            env=sanitized_runtime_env(),
        )

    def _stop_process(self) -> None:
        if self._process is None:
            return
        process = self._process
        self._process = None
        if process.poll() is not None:
            self._close_log_handle()
            return
        process.terminate()
        try:
            process.wait(timeout=10)
        except subprocess.TimeoutExpired:
            process.kill()
            process.wait(timeout=5)
        finally:
            self._close_log_handle()

    def _close_log_handle(self) -> None:
        if self._log_handle is not None and not self._log_handle.closed:
            self._log_handle.close()
        self._log_handle = None


def _safe_log(level: str, event: str, **fields: object) -> None:
    payload = dict(fields)
    method_name = "error" if level == "exception" else level
    if level == "exception" and "traceback" not in payload:
        payload["traceback"] = traceback.format_exc()
    method = getattr(logger, method_name)
    try:
        method(event, **payload)
    except Exception:
        rendered_fields = " ".join(f"{key}={value}" for key, value in payload.items())
        fallback = f"[HiveCompute.app] {level.upper()} {event}"
        if rendered_fields:
            fallback = f"{fallback} {rendered_fields}"
        print(fallback, file=sys.stderr)
