# Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved.
# OpenOLC is licensed under Mulan PSL v2.
# You can use this software according to the terms and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#         `http://license.coscl.org.cn/MulanPSL2`
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.

import threading
import time
from abc import ABC, abstractmethod
from collections import deque
from typing import Generic, TypeVar

T = TypeVar('T')


class TimeWindow(Generic[T]):

    def __init__(self, start_time: int, value: T):
        self.start_time: int = start_time
        self.value: T = value


class SlidingWindow(ABC, Generic[T]):
    """
    通用滑动窗口抽象基类
    仅负责时间分片骨架(桶对齐 / 桶复用 / 并发安全 / 桶生命周期),
    不感知任何业务字段。具体业务桶类型由子类通过泛型参数 T 指定,
    具体业务读写接口由子类实现。
    通过 total_window_ms(总窗口时长)和 window_duration_ms(每个小窗口时长)控制
    窗口数量 = total_window_ms / window_duration_ms。
    构造时预分配 window_count 个桶,运行期通过环形 deque 原地复用,不再创建或丢弃桶对象。
    """

    def __init__(self, total_window_ms: int, window_duration_ms: int):
        if total_window_ms <= 0 or window_duration_ms <= 0:
            raise ValueError("total_window_ms and window_duration_ms must be positive")
        if total_window_ms % window_duration_ms != 0:
            raise ValueError("total_window_ms must be divisible by window_duration_ms")
        self._total_window_ms = total_window_ms
        self._window_duration_ms = window_duration_ms
        self._window_count = total_window_ms // window_duration_ms
        self._buckets: deque[TimeWindow[T]] = deque(maxlen=self._window_count)
        self._lock = threading.Lock()
        for _ in range(self._window_count):
            self._buckets.append(TimeWindow(-1, self._create_bucket()))

    @abstractmethod
    def _create_bucket(self) -> T:
        """
            创建一个新的桶值实例,由子类决定具体桶类型
            仅在 __init__ 预分配阶段调用,运行期不再调用
        :return: 新桶值实例
        """

    @abstractmethod
    def _reset_bucket(self, bucket: T) -> None:
        """
            把已存在的桶值复位到与新建状态等价
            子类需要把所有业务字段恢复初始值
        :param bucket: 待复位的桶值
        :return:
        """

    def _now_ms(self) -> int:
        return int(time.perf_counter() * 1000)

    def current_bucket(self) -> TimeWindow[T]:
        """
            根据当前时间戳获取当前窗口, 同时清理已过期的时间窗口, 保证对象复用防止重复创建对象
            通过 time_id 对窗口数量取模定位环形数组中的桶位置,
            若桶的起始时间与当前时间窗口不匹配则复位后复用
        :return: 当前时间窗口对应的桶
        """
        now_ms = self._now_ms()
        window_start = now_ms - (now_ms % self._window_duration_ms)
        tail = self._buckets[-1]
        if tail.start_time == window_start:
            return tail
        else:
            with self._lock:
                tail = self._buckets[-1]
                if tail.start_time == window_start:
                    return tail
                oldest = self._buckets.popleft()
                self._reset_bucket(oldest.value)
                oldest.start_time = window_start
                self._buckets.append(oldest)
                return oldest

    def get_all_buckets(self) -> list[T]:
        """
            无锁返回当前 deque 中所有【有效】桶的快照列表
            过滤掉哨兵桶(window_start=-1)与已过期桶
            利用 CPython deque 的线程安全性:list(deque) 不会因并发修改抛 RuntimeError
        :return: 有效桶引用列表
        """
        now_ms = self._now_ms()
        cutoff = now_ms - self._total_window_ms
        return [b.value for b in self._buckets if b.start_time > cutoff]