# 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 logging
import threading
import time
from typing import List, Optional

import psutil

from olc.collector.collector import Measurable, Meter, T, AbsCollector

logger = logging.getLogger(__name__)


class LocalResourceMeasurable(Measurable):
    """本地资源测量结果"""
    cpu: float
    memory: float
    io: float
    network: float
    loadaverage: float

    def __init__(self, cpu: float, memory: float, io: float, network: float, loadaverage: float):
        self.cpu = cpu
        self.memory = memory
        self.io = io
        self.network = network
        self.loadaverage = loadaverage

    @classmethod
    def get_properties(cls) -> List[str]:
        return ["cpu", "memory", "io", "network", "loadaverage"]

    def __str__(self) -> str:
        return (f"LocalResourceMeasurable("
                f"cpu={self.cpu}, memory={self.memory}, io={self.io},"
                f"network={self.network}, loadaverage={self.loadaverage}")

    def __repr__(self):
        return self.__str__()


class LocalResourceMeter(Meter[LocalResourceMeasurable]):

    _instance = None
    _lock = threading.Lock()

    def __init__(self):
        # 磁盘 IO 上一次采样快照:(read_bytes, write_bytes) 与采样时间
        self._prev_disk_io: Optional[tuple] = None
        self._prev_disk_time: float = 0.0
        # 网络 IO 上一次采样快照:(bytes_sent, bytes_recv) 与采样时间
        self._prev_net_io: Optional[tuple] = None
        self._prev_net_time: float = 0.0

    @classmethod
    def get_instance(cls) -> "LocalResourceMeter":
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = cls()
        return cls._instance

    def measure(self) -> Optional[LocalResourceMeasurable]:
        default_value = -1.0
        try:
            cpu = self._get_cpu_usage()
        except Exception as e:
            logger.warning(f"get cpu usage faild : %s", e)
            cpu = default_value
        try:
            memory = self._get_memory_usage()
        except Exception as e:
            logger.warning(f"get memory usage faild : %s", e)
            memory = default_value
        try:
            io = self._get_io_usage()
        except Exception as e:
            logger.warning(f"get io usage faild : %s", e)
            io = default_value
        try:
            network = self._get_network_usage()
        except Exception as e:
            logger.warning(f"get network usage faild : %s", e)
            network = default_value
        try:
            load = self._get_load_usage()
        except Exception as e:
            logger.warning(f"get load usage faild : %s", e)
            load = default_value


        return LocalResourceMeasurable(cpu, memory, io, network, load)

    def _get_cpu_usage(self) -> float:
        """
        获取 CPU 使用率(0.0-1.0)。

        实现说明:
        - 使用 psutil.cpu_percent(interval=0) 进行非阻塞采样。
        - interval=0 表示不等待立即返回,数值基于自上一次调用以来的
          CPU 时间计算,因此连续两次调用之间需要间隔一段时间
          才能得到有意义的数值;首次调用通常返回 0.0。
        - 返回值除以 100 转换为 0.0-1.0 的浮点数。
        """
        return psutil.cpu_percent(interval=0) / 100.0

    def _get_memory_usage(self) -> float:
        """
        获取物理内存使用率(0.0-1.0)。

        实现说明:
        - 通过 psutil.virtual_memory() 获取系统整体的虚拟内存信息。
        - .percent 字段表示已用内存占总物理内存的百分比,
          与主流监控工具(如 free、top、Prometheus node_exporter)口径一致。
        - 返回值除以 100 转换为 0.0-1.0 的浮点数。
        """
        return psutil.virtual_memory().percent / 100.0

    def _get_io_usage(self) -> float:
        """
        获取磁盘 IO 使用率(0.0-1.0)。

        实现说明:
        - 使用 psutil.disk_io_counters() 累计所有磁盘的读写字节数。
        - 由于该 API 返回的是自系统启动以来的累计值,无法直接反映
          瞬时 IO 负载,需要通过两次采样的差值计算单位时间吞吐。
        - 状态保存在 self._prev_disk_io / self._prev_disk_time:
            * 首次调用:没有历史数据,仅记录当前快照,返回 0.0。
            * 后续调用:计算 (read_bytes + write_bytes) 的字节增量
              与时间增量,得到平均吞吐(MB/s),再以 500MB/s 作为
              参考带宽上限归一化到 0.0-1.0,避免出现超过 1.0 的不合理值。
        - 500MB/s 是消费级 SSD / 普通磁盘阵列的常见参考值,
          部署在高性能 NVMe 或更高速存储时可按需调高该基准。
        """
        counters = psutil.disk_io_counters()
        if counters is None:
            # 部分环境(如无盘、容器)可能取不到 IO 计数,按 0 处理
            return 0.0
        current_time = time.time()
        read_bytes = counters.read_bytes
        write_bytes = counters.write_bytes
        if self._prev_disk_io is not None and self._prev_disk_time > 0:
            prev_read, prev_write = self._prev_disk_io
            bytes_delta = (read_bytes - prev_read) + (write_bytes - prev_write)
            time_delta = current_time - self._prev_disk_time
            if time_delta > 0:
                # 字节/秒 -> MB/s
                rate_mbps = bytes_delta / time_delta / 1024 / 1024
                # 以 500MB/s 为满载参考,归一化到 0.0-1.0
                usage = min(rate_mbps / 500.0, 1.0)
                self._prev_disk_io = (read_bytes, write_bytes)
                self._prev_disk_time = current_time
                return usage
        # 首次采样:仅记录历史,不返回有效值
        self._prev_disk_io = (read_bytes, write_bytes)
        self._prev_disk_time = current_time
        return 0.0

    def _get_network_usage(self) -> float:
        """
        获取网络使用率(0.0-1.0)。

        实现说明:
        - 使用 psutil.net_io_counters() 获取所有网卡累计收发字节数。
        - 与磁盘 IO 类似,累计值无法直接反映瞬时流量,
          需要通过两次采样的差值计算平均吞吐。
        - 状态保存在 self._prev_net_io / self._prev_net_time:
            * 首次调用:没有历史数据,仅记录当前快照,返回 0.0。
            * 后续调用:计算 (bytes_sent + bytes_recv) 的字节增量
              与时间增量,得到平均吞吐(MB/s),按 1000MB/s
              (≈1Gbps 满载)归一化到 0.0-1.0。
        - 若服务器配备万兆(10Gbps)或更高带宽,应按比例调高基准。
        """
        counters = psutil.net_io_counters()
        current_time = time.time()
        bytes_sent = counters.bytes_sent
        bytes_recv = counters.bytes_recv
        if self._prev_net_io is not None and self._prev_net_time > 0:
            prev_sent, prev_recv = self._prev_net_io
            bytes_delta = (bytes_sent - prev_sent) + (bytes_recv - prev_recv)
            time_delta = current_time - self._prev_net_time
            if time_delta > 0:
                # 字节/秒 -> MB/s
                rate_mbps = bytes_delta / time_delta / 1024 / 1024
                # 以 1000MB/s(≈1Gbps)作为满载参考
                usage = min(rate_mbps / 1000.0, 1.0)
                self._prev_net_io = (bytes_sent, bytes_recv)
                self._prev_net_time = current_time
                return usage
        # 首次采样:仅记录历史,不返回有效值
        self._prev_net_io = (bytes_sent, bytes_recv)
        self._prev_net_time = current_time
        return 0.0

    def _get_load_usage(self) -> float:
        """
        获取系统负载使用率(0.0-1.0)。

        实现说明:
        - 优先使用 psutil.getloadavg() 获取 1/5/15 分钟的负载平均值,
          与 Linux `uptime` / `top` 的 load average 概念一致:
          表示单位时间内处于可运行 + 不可中断状态的平均任务数。
        - 将 1 分钟负载除以 CPU 核心数,得到每核心的负载比例,
          直接归一化到 0.0-1.0:
            * load1 == cpu_count 时,约为 1.0(满载边界);
            * load1 > cpu_count 时,说明存在等待队列,
              值会被截断到 1.0。
        - 兼容性处理:
            * Windows 平台没有 getloadavg,会抛出 AttributeError;
            * 部分受限环境可能抛出 OSError。
            上述两种情况下回退使用 CPU 使用率作为负载的近似值,
            以保证采集器在跨平台场景下不会失败。
        """
        try:
            load1, _, _ = psutil.getloadavg()
            cpu_count = psutil.cpu_count()
            if cpu_count and cpu_count > 0:
                # 每核负载 -> 0.0-1.0,超过 1.0 视为满载
                return min(load1 / cpu_count, 1.0)
            # 拿不到核心数时不做归一化,直接以负载值近似
            return min(load1, 1.0)
        except (AttributeError, OSError):
            # Windows / 不支持 getloadavg 的环境回退到 CPU 使用率
            return psutil.cpu_percent(interval=0) / 100.0


class LocalResourceCollector(AbsCollector):
    """本地资源采集器"""
    NAME = "localresource"

    def __init__(self, meter: Optional[LocalResourceMeter] = None):
        if meter is None:
            meter = LocalResourceMeter.get_instance()
        super().__init__(meter, LocalResourceMeasurable, True, True, "olc-localresource-collector-pool")
        self._measurements: List[LocalResourceMeasurable] = []

    def get_name(self) -> str:
        return self.NAME

    def get_storage(self) -> List[LocalResourceMeasurable]:
        with self._lock:
            return self._measurements

    def get_properties(self) -> List[str]:
        return LocalResourceMeasurable.get_properties()