"""WEBSTART-01: HTTP 대기 종료가 접수한 제어 작업의 취소는 아니다."""

from collections import OrderedDict, deque
from concurrent.futures import Future
from dataclasses import dataclass, field
import threading
import math
from uuid import uuid4


class CommandUnavailable(RuntimeError):
    pass


@dataclass
class CameraOperation:
    operation_id: str
    camera_id: str | int
    future: Future = field(repr=False)
    request: object = field(repr=False)
    action: str = "start"


class CameraCommands:
    def __init__(self, service, capacity=16, history=64):
        if capacity < 1 or history < capacity:
            raise ValueError("Invalid command limits.")
        self.service = service
        self.capacity = capacity
        self.history = history
        self._cv = threading.Condition()
        self._jobs = OrderedDict()
        self._queue = deque()
        self._active = {}
        self._closed = False
        self._thread = None

    def submit(self, camera_id, request=None, action="start"):
        if action not in {"start", "stop"}:
            raise ValueError("Invalid command.")
        key = str(camera_id)
        with self._cv:
            if self._closed:
                raise CommandUnavailable("Camera control is closed.")
            previous = self._active.get(key)
            if previous is not None:
                if previous.action == action and previous.request == request:
                    return previous
                raise CommandUnavailable("Another camera command is pending.")
            if len(self._active) >= self.capacity:
                raise CommandUnavailable("Camera control queue is full.")
            for identifier in list(self._jobs):
                if len(self._jobs) < self.history:
                    break
                if self._jobs[identifier].future.done():
                    del self._jobs[identifier]
            job = CameraOperation(uuid4().hex, camera_id, Future(), request, action)
            self._jobs[job.operation_id] = job
            self._active[key] = job
            self._queue.append(job)
            if self._thread is None:
                self._thread = threading.Thread(target=self._run, name="camera-control", daemon=True)
                self._thread.start()
            self._cv.notify()
            return job

    def get(self, operation_id):
        with self._cv:
            return self._jobs.get(operation_id)

    def _run(self):
        while True:
            with self._cv:
                self._cv.wait_for(lambda: self._closed or bool(self._queue))
                if self._closed and not self._queue:
                    return
                job = self._queue.popleft()
            try:
                result = (self.service.start_camera(job.request) if job.action == "start"
                          else self.service.stop_camera(job.camera_id))
                job.future.set_result(result)
            except Exception as error:
                job.future.set_exception(error)
            finally:
                with self._cv:
                    self._active.pop(str(job.camera_id), None)

    def close(self, timeout=1.0):
        if not math.isfinite(timeout) or timeout < 0:
            raise ValueError("Invalid shutdown timeout.")
        with self._cv:
            self._closed = True
            while self._queue:
                job = self._queue.popleft()
                job.future.set_exception(CommandUnavailable("Camera command cancelled before execution."))
                self._active.pop(str(job.camera_id), None)
            self._cv.notify_all()
            thread = self._thread
        if thread is not None:
            thread.join(timeout)
        return thread is None or not thread.is_alive()
