"""插件宿主可变事务的停机准入。"""

from __future__ import annotations

import threading
from collections.abc import Iterator
from contextlib import contextmanager
from contextvars import ContextVar
from dataclasses import dataclass

from app.schemas.exception import PluginMutationRejectedError


@dataclass(slots=True)
class _MutationContext:
    """记录一个可跨协程和受控线程传播的事务上下文。"""

    admission: "PluginMutationAdmission"
    holders: int = 0
    open: bool = True


class PluginMutationAdmission:
    """在停机封口与插件可变事务之间维护真实 owner 计数。"""

    def __init__(self) -> None:
        """初始化开放准入、活动 owner 计数和事务上下文。"""
        self._condition = threading.Condition()
        self._accepting = True
        self._active_count = 0
        self._current_context: ContextVar[_MutationContext | None] = ContextVar(
            "plugin_mutation_context",
            default=None,
        )

    @property
    def active_count(self) -> int:
        """返回尚未退出的 lease 数量，用于停机诊断和严格卸载判断。"""
        with self._condition:
            return self._active_count

    @property
    def accepting(self) -> bool:
        """返回当前生命周期是否仍接纳新的根事务。"""
        with self._condition:
            return self._accepting

    def is_held(self) -> bool:
        """判断当前执行上下文是否持有仍有效的本 admission lease。"""
        context = self._current_context.get()
        with self._condition:
            return bool(
                context
                and context.admission is self
                and context.open
                and context.holders > 0
            )

    @contextmanager
    def hold(self, operation: str) -> Iterator[None]:
        """取得可变事务 lease；封口后仅允许已获准事务的嵌套调用。"""
        context = self._current_context.get()
        context_token = None
        acquired = False
        try:
            with self._condition:
                nested = bool(
                    context
                    and context.admission is self
                    and context.open
                    and context.holders > 0
                )
                if not self._accepting and not nested:
                    raise PluginMutationRejectedError(operation)
                if not nested:
                    context = _MutationContext(admission=self)
                    context_token = self._current_context.set(context)
                assert context is not None
                self._active_count += 1
                context.holders += 1
                acquired = True
            yield
        finally:
            if acquired:
                with self._condition:
                    self._active_count -= 1
                    context.holders -= 1
                    if context.holders == 0:
                        context.open = False
                    self._condition.notify_all()
            if context_token is not None:
                self._current_context.reset(context_token)

    def seal(self) -> int:
        """原子停止接纳根事务，并返回封口瞬间的活动 lease 数量。"""
        with self._condition:
            self._accepting = False
            return self._active_count

    def wait_until_idle(self) -> None:
        """自然等待全部已获准 lease 退出，不取消或遗失其 owner。"""
        with self._condition:
            while self._active_count:
                self._condition.wait()

    def reopen(self) -> bool:
        """仅在没有遗留 lease 时为新的应用生命周期重新开放准入。"""
        with self._condition:
            if self._active_count:
                return False
            self._accepting = True
            return True
