from abc import ABC, abstractclassmethod
from typing import Union, Literal
import hmac, hashlib
import requests
import logging
import time

logger = logging.getLogger(__name__)

class MexcSDK(ABC):
    """
    Initializes a new instance of the class with the given `api_key` and `api_secret` parameters.

    :param api_key: A string representing the API key.
    :param api_secret: A string representing the API secret.
    :param base_url: A string representing the base URL of the API.
    """
    def __init__(self, api_key: str, api_secret: str, base_url: str, proxies: dict = None):
        self.api_key = api_key
        self.api_secret = api_secret

        self.recvWindow = 5000

        self.base_url = base_url

        self.session = requests.Session()
        self.session.headers.update({
            "Content-Type": "application/json",
        })

        if proxies:
            self.session.proxies.update(proxies)


    @abstractclassmethod
    def sign(self, **kwargs) -> str:
        ...
    
    @abstractclassmethod
    def call(self, method: Union[Literal["GET"], Literal["POST"], Literal["PUT"], Literal["DELETE"]], router: str, *args, **kwargs) -> dict:
        ...

class _SpotHTTP(MexcSDK):
    def __init__(self, api_key: str = None, api_secret: str = None, proxies: dict = None):
        super().__init__(api_key, api_secret, "https://api.mexc.com", proxies = proxies)

        self.session.headers.update({
            "X-MEXC-APIKEY": self.api_key
        })

    def sign(self, **kwargs) -> str:
        """
        Generates a signature for an API request using HMAC SHA256 encryption.

        Args:
            **kwargs: Arbitrary keyword arguments representing request parameters.

        Returns:
            A hexadecimal string representing the signature of the request.
        """
        # Generate signature
        query_string = "&".join([f"{k}={v}" for k, v in sorted(kwargs.items())])
        signature = hmac.new(self.api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest()
        return signature

    def call(self, method: Union[Literal["GET"], Literal["POST"], Literal["PUT"], Literal["DELETE"]], router: str, *args, **kwargs) -> dict:
        if not router.startswith("/"):
            router = f"/{router}"

        # clear None values
        kwargs = {k: v for k, v in kwargs.items() if v is not None}

        if kwargs.get('params'):
            kwargs['params'] = {k: v for k, v in kwargs['params'].items() if v is not None}
        else:
            kwargs['params'] = {}

        if self.api_key and self.api_secret:
            # add signature
            timestamp = str(int(time.time() * 1000))
        kwargs['params']['recvWindow'] = self.recvWindow
        kwargs['params']['timestamp']  = timestamp
        kwargs['params']['signature']  = self.sign(**kwargs['params'])

        response = self.session.request(method, f"{self.base_url}{router}", *args, **kwargs)

        return response.json()
    
class _FuturesHTTP(MexcSDK):
    def __init__(self, api_key: str = None, api_secret: str = None, proxies: dict = None):
        super().__init__(api_key, api_secret, "https://contract.mexc.com", proxies = proxies)

        self.session.headers.update({
            "Content-Type": "application/json",
            "ApiKey": self.api_key
        })

    def sign(self, timestamp: str, **kwargs) -> str:
        """
        Generates a signature for an API request using HMAC SHA256 encryption.

        :param timestamp: A string representing the timestamp of the request.
        :type timestamp: str
        :param kwargs: Arbitrary keyword arguments representing request parameters.
        :type kwargs: dict

        :return: A hexadecimal string representing the signature of the request.
        :rtype: str
        """
        # Generate signature
        query_string = "&".join([f"{k}={v}" for k, v in sorted(kwargs.items())])
        query_string = self.api_key + timestamp + query_string
        signature = hmac.new(self.api_secret.encode('utf-8'), query_string.encode('utf-8'), hashlib.sha256).hexdigest()
        return signature
    
    def call(self, method: Union[Literal["GET"], Literal["POST"], Literal["PUT"], Literal["DELETE"]], router: str, *args, **kwargs) -> dict:
        """
        Makes a request to the specified HTTP method and router using the provided arguments.
        
        :param method: A string that represents the HTTP method(GET, POST, PUT, or DELETE) to be used.
        :type method: str
        :param router: A string that represents the API endpoint to be called.
        :type router: str
        :param *args: Variable length argument list.
        :type *args: list
        :param **kwargs: Arbitrary keyword arguments.
        :type **kwargs: dict
        
        :return: A dictionary containing the JSON response of the request.
        """
        
        if not router.startswith("/"):
            router = f"/{router}"
        
        # clear None values
        kwargs = {k: v for k, v in kwargs.items() if v is not None}

        for variant in ('params', 'json'):
            if kwargs.get(variant):
                kwargs[variant] = {k: v for k, v in kwargs[variant].items() if v is not None}
            
                if self.api_key and self.api_secret:
                    # add signature
                    timestamp = str(int(time.time() * 1000))

                    kwargs['headers'] = {
                        "Request-Time": timestamp,
                        "Signature": self.sign(timestamp, **kwargs[variant])
                    }

        response = self.session.request(method, f"{self.base_url}{router}", *args, **kwargs)

        return response.json()