# 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.

from abc import ABC, abstractmethod
from dataclasses import dataclass, field

from olc.config.properties_loader import OlcConfigManager
from olc.statistic.request_stat_window import RequestStatSlidingWindow
from olc.utils.atomic_num import AtomicNum


@dataclass
class WindowSnapshot:
    total_request_num: int = 0
    pass_request_num: int = 0
    block_request_num: int = 0
    pass_avg_latency_ms: float = 0.0
    pass_max_latency_ms: float = 0.0
    pass_min_latency_ms: float = 0.0


@dataclass
class StatisticSnapshot:
    cur_thread_num: int = 0
    second_snap: WindowSnapshot = field(default_factory=WindowSnapshot)
    custom_snap: WindowSnapshot = field(default_factory=WindowSnapshot)

    @property
    def total_request_num_pre_second(self) -> int:
        return self.second_snap.total_request_num

    @property
    def pass_request_num_pre_second(self) -> int:
        return self.second_snap.pass_request_num

    @property
    def block_request_num_pre_second(self) -> int:
        return self.second_snap.block_request_num

    @property
    def pass_avg_latency_ms_pre_second(self) -> float:
        return self.second_snap.pass_avg_latency_ms

    @property
    def pass_max_latency_ms_pre_second(self) -> float:
        return self.second_snap.pass_max_latency_ms

    @property
    def pass_min_latency_ms_pre_second(self) -> float:
        return self.second_snap.pass_min_latency_ms


class IStatistic(ABC):

    @abstractmethod
    def inc_thread_num(self) -> None:
        """
            增加当前并发数
        :return:
        """

    @abstractmethod
    def dec_thread_num(self) -> None:
        """
            减少当前并发数,最低限制为 0
        :return:
        """

    @abstractmethod
    def cur_thread_num(self) -> int:
        """
            获取当前并发数
        :return: 当前并发数
        """

    @abstractmethod
    def total_request_num(self) -> int:
        """
            获取秒级滑动窗口内的总请求数(通过数 + 拒绝数)
        :return: 最近 1 秒内的总请求数
        """

    @abstractmethod
    def inc_pass_request_num(self) -> None:
        """
            增加通过请求计数(秒级和分钟级滑动窗口同时记录)
        :return:
        """

    @abstractmethod
    def pass_request_num(self) -> int:
        """
            获取秒级滑动窗口内的通过请求数
        :return: 最近 1 秒内的通过请求数
        """

    @abstractmethod
    def inc_block_request_num(self) -> None:
        """
            增加拒绝请求计数(秒级和分钟级滑动窗口同时记录)
        :return:
        """

    @abstractmethod
    def block_request_num(self) -> int:
        """
            获取秒级滑动窗口内的拒绝请求数
        :return: 最近 1 秒内的拒绝请求数
        """

    @abstractmethod
    def record_pass_latency_ms(self, latency_ms: float) -> None:
        """
            记录单次通过请求延迟(秒级和分钟级滑动窗口同时记录)
        :param latency_ms: 单次请求延迟(毫秒),负数会被忽略
        :return:
        """

    @abstractmethod
    def pass_avg_latency_ms(self) -> float:
        """
            获取秒级滑动窗口内的通过请求平均延迟
        :return: 最近 1 秒内的平均延迟(毫秒),无记录时返回 0.0
        """

    @abstractmethod
    def pass_max_latency_ms(self) -> float:
        """
            获取秒级滑动窗口内的通过请求最大延迟
        :return: 最近 1 秒内的最大延迟(毫秒)
        """

    @abstractmethod
    def pass_min_latency_ms(self) -> float:
        """
            获取秒级滑动窗口内的通过请求最小延迟
        :return: 最近 1 秒内的最小延迟(毫秒)
        """

    @abstractmethod
    def snapshot(self) -> StatisticSnapshot:
        """
            获取当前统计快照,包含秒级和分钟级双粒度数据
        :return: 统计快照对象
        """


class OlcStatistic(IStatistic):

    def __init__(self):
        window_interval_in_sec = OlcConfigManager.get_instance().get_sdk_config().window_interval_in_sec
        window_duration_ms = OlcConfigManager.get_instance().get_sdk_config().window_length_in_ms
        self._cur_thread_num = AtomicNum(0)
        self._second_window = RequestStatSlidingWindow(total_window_ms=1000, window_duration_ms=100)
        self._custom_window = RequestStatSlidingWindow(total_window_ms=window_interval_in_sec * 1000,
                                                       window_duration_ms=window_duration_ms)

    def inc_thread_num(self) -> None:
        self._cur_thread_num.add(1)

    def dec_thread_num(self) -> None:
        self._cur_thread_num.dec_and_limit(1, 0)

    def cur_thread_num(self) -> int:
        return int(self._cur_thread_num.get())

    def total_request_num(self) -> int:
        buckets = self._second_window.get_all_buckets()
        total: int = 0
        for bucket in buckets:
            total += bucket.get_pass_num() + bucket.get_block_num()
        return total

    def inc_pass_request_num(self) -> None:
        self._second_window.record_pass_num()
        self._custom_window.record_pass_num()

    def pass_request_num(self) -> int:
        return self._second_window.get_pass_num_sum()

    def inc_block_request_num(self) -> None:
        self._second_window.record_block_num()
        self._custom_window.record_block_num()

    def block_request_num(self) -> int:
        return self._second_window.get_block_num_sum()

    def record_pass_latency_ms(self, latency_ms: float) -> None:
        if latency_ms < 0:
            return
        self._second_window.record_pass_rt(latency_ms)
        self._custom_window.record_pass_rt(latency_ms)

    def pass_avg_latency_ms(self) -> float:
        return self._second_window.get_pass_avg_rt()

    def pass_max_latency_ms(self) -> float:
        max_val = self._second_window.get_pass_rt_max()
        return 0.0 if max_val is None else max_val

    def pass_min_latency_ms(self) -> float:
        min_val = self._second_window.get_pass_rt_min()
        return 0.0 if min_val is None else min_val

    def _build_window_snapshot(self, window: RequestStatSlidingWindow) -> WindowSnapshot:
        buckets = window.get_all_buckets()
        pass_num: int = 0
        block_num: int = 0
        pass_rt_sum: float = 0.0
        pass_rt_min: float | None = None
        pass_rt_max: float | None = None

        for bucket in buckets:
            pass_num += bucket.get_pass_num()
            block_num += bucket.get_block_num()
            pass_rt_sum += bucket.get_pass_rt()
            bucket_min = bucket.get_min_pass_rt()
            if bucket_min is not None:
                if pass_rt_min is None or bucket_min < pass_rt_min:
                    pass_rt_min = bucket_min
            bucket_max = bucket.get_max_pass_rt()
            if bucket_max is not None:
                if pass_rt_max is None or bucket_max > pass_rt_max:
                    pass_rt_max = bucket_max

        latency_min = 0.0 if pass_rt_min is None else pass_rt_min
        latency_max = 0.0 if pass_rt_max is None else pass_rt_max
        latency_avg = pass_rt_sum / pass_num if pass_num > 0 else 0.0
        return WindowSnapshot(
            total_request_num=pass_num + block_num,
            pass_request_num=pass_num,
            block_request_num=block_num,
            pass_avg_latency_ms=latency_avg,
            pass_max_latency_ms=latency_max,
            pass_min_latency_ms=latency_min,
        )

    def snapshot(self) -> StatisticSnapshot:
        return StatisticSnapshot(
            cur_thread_num=int(self._cur_thread_num.get()),
            second_snap=self._build_window_snapshot(self._second_window),
            custom_snap=self._build_window_snapshot(self._custom_window),
        )