"""UCD 信号 / 图案应用服务层。

服务层是 GUI ↔ Driver 的唯一通道，负责：
- 将 UI 字符串（"BT.709"、"10bit"、"YCbCr 4:4:4" 等）翻译成 :class:`SignalFormat`；
- 将各 panel 的 timing 字符串翻译成 :class:`TimingSpec`；
- 协调 :meth:`IUcdDevice.configure` / ``set_pattern`` / ``apply`` 的调用顺序；
- 通过 :class:`EventBus` 让 GUI 订阅状态变化，而非主动轮询。

本层不直接 import UniTAP，也不读取 :mod:`tkinter` 变量；
所有输入都是显式参数，便于单测。
"""

from __future__ import annotations

from contextlib import contextmanager
import logging
import sys
import threading

from app.ucd_domain import (
    Colorimetry,
    DynamicRange,
    EventBus,
    PatternKind,
    PatternSpec,
    SignalFormat,
    TimingSpec,
    UcdError,
    bit_depth_str_to_bpc,
    color_space_to_colorimetry,
    data_range_to_dynamic_range,
    output_format_to_color_format,
    parse_timing_str,
)
from drivers.ucd_driver import IUcdDevice

log = logging.getLogger(__name__)

_LOCK_TIMEOUT_SECONDS = 8.0
_DEBUG_LOCK_TIMEOUT_SECONDS = 0.3
_PATTERN_LOCK_TIMEOUT_SECONDS = 0.8


# ─── 视图字符串 → 值对象 转换工具 ────────────────────────────────


def build_signal_format(
    *,
    color_space: str,
    output_format: str,
    bit_depth: str,
    data_range: str = "Full",
) -> SignalFormat:
    """根据下拉框字符串组装 :class:`SignalFormat`。

    各参数解析失败抛 :class:`UcdConfigError`。
    """
    return SignalFormat(
        color_format=output_format_to_color_format(output_format),
        colorimetry=color_space_to_colorimetry(color_space),
        bpc=bit_depth_str_to_bpc(bit_depth),
        dynamic_range=data_range_to_dynamic_range(data_range),
    )


def build_timing(timing_str: str) -> TimingSpec:
    """``"DMT 3840x2160@60Hz"`` → :class:`TimingSpec`。"""
    return parse_timing_str(timing_str)


def solid_rgb_pattern(rgb: tuple[int, int, int] | list[int]) -> PatternSpec:
    r, g, b = rgb[0], rgb[1], rgb[2]
    return PatternSpec(kind=PatternKind.SOLID, solid_rgb=(int(r), int(g), int(b)))


def image_pattern(path: str) -> PatternSpec:
    return PatternSpec(kind=PatternKind.IMAGE, image_path=path)


# ─── 服务 ────────────────────────────────────────────────────────


class SignalService:
    """协调 SignalFormat / Timing / Pattern 的写入与提交。

    使用线程锁串行化所有对外的 ``apply_*`` 调用，避免多个测试线程
    同时操作 UCD 造成 SDK 状态错乱。
    """

    def __init__(self, device: IUcdDevice, bus: EventBus):
        self._dev = device
        self._bus = bus
        self._lock = threading.RLock()
        self._lock_owner_tid: int | None = None
        self._lock_owner_name: str | None = None

    def _effective_lock_timeout(self, timeout_override: float | None = None) -> float:
        """调试模式下缩短锁等待，避免单步时表现为 UI 长时间无响应。"""
        if timeout_override is not None:
            return timeout_override
        if sys.gettrace() is not None:
            return _DEBUG_LOCK_TIMEOUT_SECONDS
        return _LOCK_TIMEOUT_SECONDS

    @contextmanager
    def _acquire_service_lock(self, op_name: str, timeout_override: float | None = None):
        timeout = self._effective_lock_timeout(timeout_override)
        current = threading.current_thread()
        log.info(
            "SignalService.%s acquiring lock timeout=%.1fs tid=%s thread=%s owner_tid=%s owner_thread=%s",
            op_name,
            timeout,
            threading.get_ident(),
            current.name,
            self._lock_owner_tid,
            self._lock_owner_name,
        )
        acquired = self._lock.acquire(timeout=timeout)
        if not acquired:
            raise UcdError(
                "UCD busy: lock timeout in "
                f"SignalService.{op_name} ({timeout:.1f}s), "
                f"owner_tid={self._lock_owner_tid}, owner_thread={self._lock_owner_name}"
            )
        prev_owner_tid = self._lock_owner_tid
        prev_owner_name = self._lock_owner_name
        self._lock_owner_tid = threading.get_ident()
        self._lock_owner_name = current.name
        log.info(
            "SignalService.%s lock acquired tid=%s thread=%s",
            op_name,
            self._lock_owner_tid,
            self._lock_owner_name,
        )
        try:
            yield
        finally:
            self._lock_owner_tid = prev_owner_tid
            self._lock_owner_name = prev_owner_name
            self._lock.release()

    # -- 高层接口 ------------------------------------------------

    def apply(
        self,
        *,
        signal: SignalFormat,
        timing: TimingSpec,
        pattern: PatternSpec,
    ) -> bool:
        """一次性提交信号格式 + timing + 图案。

        Returns:
            ``format_changed``——本次相对上一次 :meth:`apply` 是否变化。
        """
        with self._acquire_service_lock("apply"):
            log.info(
                "SignalService.apply signal=%s timing=%s pattern=%s",
                signal,
                timing,
                pattern.kind.value,
            )
            changed = self._dev.configure(signal, timing)
            self._dev.set_pattern(pattern)
            self._dev.apply()
            return changed

    def send_pattern(self, pattern: PatternSpec) -> None:
        """在已 configure 的信号上仅更新图案后 apply。"""
        with self._acquire_service_lock("send_pattern", _PATTERN_LOCK_TIMEOUT_SECONDS):
            log.info("SignalService.send_pattern pattern=%s", pattern.kind.value)
            self._dev.set_pattern(pattern)
            self._dev.apply()

    def send_solid_rgb(self, rgb: tuple[int, int, int] | list[int]) -> None:
        self.send_pattern(solid_rgb_pattern(rgb))

    def send_image(self, path: str) -> None:
        self.send_pattern(image_pattern(path))

    # -- 过渡期 API（Phase 2）-----------------------------------
    # 现有 GUI 回调以"仅更新信号格式、不切换图案"的方式调用
    # ``ucd.apply_signal_format(color_space=..., color_format=..., bit_depth=...)``。
    # 新代码统一通过本方法走 SignalService；内部仍委托给底层
    # controller 的同名旧接口，迁移完成后将替换为纯净实现。

    def update_signal_format(
        self,
        *,
        color_space: str,
        output_format: str,
        bit_depth: str,
        data_range: str = "Full",
        max_cll: int | None = None,
        max_fall: int | None = None,
    ) -> bool:
        """仅将信号格式 stage 到 SDK（沿用上一次的 timing），不切换图案。

        UI 字符串先经域层解析做参数校验；解析失败抛 :class:`UcdConfigError`。
        """
        # 解析仅做校验；当前实现走 raw controller 的旧 API
        _ = build_signal_format(
            color_space=color_space,
            output_format=output_format,
            bit_depth=bit_depth,
            data_range=data_range,
        )
        ctrl = getattr(self._dev, "raw_controller", None)
        if ctrl is None:
            raise UcdError("update_signal_format 暂仅支持 UCD323Device")
        with self._acquire_service_lock("update_signal_format"):
            return bool(
                ctrl.apply_signal_format(
                    color_space=color_space,
                    color_format=output_format,
                    bit_depth=bit_depth,
                    data_range=data_range,
                    max_cll=max_cll,
                    max_fall=max_fall,
                )
            )

    # -- 透传给上层的查询 ---------------------------------------

    @property
    def device(self) -> IUcdDevice:
        return self._dev

    def current_resolution(self) -> tuple[int, int]:
        return self._dev.current_resolution()

    @property
    def is_connected(self) -> bool:
        """UCD 设备是否已打开。供 GUI 做前置校验。"""
        ctrl = getattr(self._dev, "raw_controller", None)
        return bool(ctrl and getattr(ctrl, "status", False))

    # -- 过渡期 API（Phase 5）：config 驱动的写入 -----------------
    # 现有 GUI / Service 通过 ``PQConfig`` 对象描述当次测试参数，
    # 由 :class:`UCDController.set_ucd_params` 翻译为色彩/Timing/Pattern。
    # 在配置层重构落地前，这两个方法作为 SignalService 的统一入口，
    # 让上层不再直接接触 ``self.app.ucd``。

    def apply_config(self, config) -> bool:
        """按 :class:`PQConfig` 写入色彩 / Timing / 当前 Pattern（不 apply 输出）。"""
        ctrl = getattr(self._dev, "raw_controller", None)
        if ctrl is None:
            raise UcdError("apply_config 暂仅支持 UCD323Device")
        with self._acquire_service_lock("apply_config"):
            return bool(ctrl.set_ucd_params(config))

    def send_pattern_params(self, params) -> bool:
        """以 ``params`` 更新当前 pattern 的参数并 apply。"""
        ctrl = getattr(self._dev, "raw_controller", None)
        if ctrl is None:
            raise UcdError("send_pattern_params 暂仅支持 UCD323Device")
        with self._acquire_service_lock("send_pattern_params"):
            return bool(ctrl.send_current_pattern_params(params))

    def apply_and_run(self, config, pattern_params) -> bool:
        """``set_ucd_params`` + ``set_pattern`` + ``run`` 的复合操作。

        服务于 custom_template_panel 单步流程。
        """
        ctrl = getattr(self._dev, "raw_controller", None)
        if ctrl is None:
            raise UcdError("apply_and_run 暂仅支持 UCD323Device")
        with self._acquire_service_lock("apply_and_run"):
            if not ctrl.set_ucd_params(config):
                return False
            if not ctrl.set_pattern(ctrl.current_pattern, pattern_params):
                return False
            return bool(ctrl.run())


__all__ = [
    "SignalService",
    "build_signal_format",
    "build_timing",
    "solid_rgb_pattern",
    "image_pattern",
    # 重导出常用域类型方便上层 import 一次到位
    "SignalFormat",
    "TimingSpec",
    "PatternSpec",
    "PatternKind",
    "Colorimetry",
    "DynamicRange",
    "UcdError",
]
