from datetime import date, datetime
from decimal import Decimal
from enum import Enum
from json import JSONEncoder
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type

from pait.model.response import PaitResponseModel
from pait.model.status import PaitStatus


class CustomJSONEncoder(JSONEncoder):
    def default(self, obj: Any) -> Any:
        if type(obj) == date:
            return datetime.strptime(str(obj), "%Y-%m-%d")
        elif isinstance(obj, datetime):
            # 返回时间戳格式
            return int(obj.timestamp())
        elif isinstance(obj, Decimal):
            return float(obj)
        elif isinstance(obj, Enum):
            return obj.value
        else:
            return super().default(obj)


class Config(object):
    """
    Provide pait parameter configuration, the init_config method is valid only before the application service is started
    """

    __initialized: bool = False

    def __init__(self) -> None:
        self.author: Tuple[str, ...] = ("",)
        self.status: PaitStatus = PaitStatus.undefined
        self.enable_mock_response: bool = False
        self.enable_mock_response_filter_fn: Optional[Callable[[Type[PaitResponseModel]], bool]] = None
        self.block_http_method_set: Set[str] = set()
        self.default_response_model_list: List[Type[PaitResponseModel]] = []
        self.json_encoder: Type[JSONEncoder] = CustomJSONEncoder
        self.json_type_default_value_dict: Dict[str, Any] = {
            "null": None,
            "bool": True,
            "boolean": True,
            "string": "",
            "number": 0.0,
            "float": 0.0,
            "integer": 0,
            "object": {},
            "array": [],
        }
        self.python_type_default_value_dict: Dict[type, Any] = {
            bool: True,
            float: 0.0,
            int: 0,
            str: "",
            list: [],
            tuple: [],
            datetime: 0,
            Decimal: 0,
        }

    def __setattr__(self, key: Any, value: Any) -> None:
        if not self.__initialized:
            super().__setattr__(key, value)
        else:
            raise RuntimeError("Can not set new value in runtime")

    def init_config(
        self,
        author: Tuple[str, ...] = ("",),
        status: PaitStatus = PaitStatus.undefined,
        block_http_method_set: Optional[Set[str]] = None,
        default_response_model_list: Optional[List[Type[PaitResponseModel]]] = None,
        json_type_default_value_dict: Optional[Dict[str, Any]] = None,
        python_type_default_value_dict: Optional[Dict[type, Any]] = None,
        json_encoder: Type[JSONEncoder] = CustomJSONEncoder,
        enable_mock_response: bool = False,
        enable_mock_response_filter_fn: Optional[Callable[[Type[PaitResponseModel]], bool]] = None,
    ) -> None:
        """
        :param author:  Only @pait(author=None) will be called to change the configuration
        :param status:  Only @pait(status=None) will be called to change the configuration
        :param block_http_method_set:
            Under normal circumstances, pait.load_app can obtain the http method of the routing handle.
            However, some application frameworks such as flask will automatically add optional http methods
             to the handle, which is great, but you may not want to use them in pait, and pait will not
             automatically recognize them, so you can use parameters to disable certain http method
        :param default_response_model_list: Add a default response structure for all routing handles
        :param json_type_default_value_dict: Configure default values for each json type
        :param python_type_default_value_dict: Configure default values for each python type
        :param json_encoder: Define certain types of serialization rules
        :param enable_mock_response:
            If True, the routing handle will return an example value, which is automatically generated by
             the first response body in response_list
        :param enable_mock_response_filter_fn:
            There are multiple response bodies in response_list, you can filter by changing the function
        :return:
        """
        self.author = author
        self.status = status
        self.block_http_method_set = block_http_method_set or set()
        self.default_response_model_list = default_response_model_list or []
        if json_type_default_value_dict:
            self.json_type_default_value_dict.update(json_type_default_value_dict)
        if python_type_default_value_dict:
            self.python_type_default_value_dict.update(python_type_default_value_dict)
        self.json_encoder = json_encoder
        self.enable_mock_response = enable_mock_response
        self.enable_mock_response_filter_fn = enable_mock_response_filter_fn

        self.__initialized = True

    @property
    def initialized(self) -> bool:
        """Judge whether it has been initialized, it can only be initialized once per run"""
        return self.__initialized
