# 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
from abc import ABC, abstractmethod
from enum import Enum
from typing import Generic, TypeVar, Any, Optional

from olc.alg.alg_context import AlgContext
from olc.utils.num_utils import NumUtils

T = TypeVar("T")

logger = logging.getLogger(__name__)


class AlgType(Enum):
    # 流控决策算法
    FLOWDECISION = "FLOWDECISION"
    # 准入
    ADMISSION = "ADMISSION"


class ParaName:
    def __init__(self, key: str, default_value: Any, dynamic: bool = False):
        self.key = key
        self.default_value = default_value
        self.dynamic = dynamic


class Algorithm(ABC, Generic[T]):

    def __init__(self, alg_type: AlgType, auto: bool = False):
        self.alg_type = alg_type
        self.auto = auto
        self.para_names: dict[str, ParaName] = {}

    @abstractmethod
    def make_decision(self, alg_context: AlgContext[T]) -> T:
        pass

    @abstractmethod
    def check(self, params: dict[str, Any]) -> bool:
        """
            算法参数检查
        :param params: 需要检查的参数
        :return:
        """
        for key in params.keys():
            if key not in self.para_names:
                logger.warning(f"invalid parameter {key}")
                return False
        return True

    def get_param(self, key: str) -> Optional[ParaName]:
        return self.para_names.get(key, None)

    def get_dynamic_param_key(self) -> list[str]:
        """
            获取动态参数Key
        :return: key list
        """
        result = []
        for key, para in self.para_names.items():
            if para.dynamic:
                result.append(key)
        return result

    def valid_min_max_param(
        self, min_name: str, max_name: str, params: dict[str, Any]
    ) -> bool:
        min_obj = params.get(min_name, None)
        max_obj = params.get(max_name, None)

        if min_obj is not None:
            min_val = NumUtils.cast_to_float(min_obj)
        else:
            ins_min_param: Optional[ParaName] = self.get_param(min_name)
            if ins_min_param is None:
                logger.warning(f"params not fount key:{min_name}")
                return False
            min_val = NumUtils.cast_to_float(ins_min_param.default_value)

        if max_obj is not None:
            max_val = NumUtils.cast_to_float(max_obj)
        else:
            ins_max_param: Optional[ParaName] = self.get_param(max_name)
            if ins_max_param is None:
                logger.warning(f"params not fount key:{max_name}")
                return False
            max_val = NumUtils.cast_to_float(ins_max_param.default_value)

        if max_val is None or min_val is None:
            logger.warning(f"check error: {min_name} or {max_name} is None")
            return False

        if max_val < min_val:
            logger.warning(f"check error: {min_name}:{min_val} > {max_name}:{max_val}")
            return False
        return True

    def check_float_param(self, key: str, params: dict[str, Any]) -> bool:
        param_obj = params.get(key, None)
        if param_obj is None:
            ins_param: Optional[ParaName] = self.get_param(key)
            if ins_param is None:
                logger.warning(f"params not fount key:{key}")
                return False
        return True