import json
from typing import Dict, Any, Optional, List, Union, Tuple

import pandas as pd
import requests
from pysubway.utils import udict
from pysubway.utils.encryption.aes import AESCrypt
from pysubway.utils.encryption.rsa2 import RSAEncryption


class BoolDataBase:
    """
    BoolDataBase params frame:
    sign
    biz_data
    institution_id

    BoolDataBase result frame:
    {
        'resp_code':'',
        'resp_data': {},
        "resp_msg": "认证成功",
        "resp_order": "018011562838612454492953",
        "timestamp": "20190711175017"
    }
    """
    urls = {
        'local': 'http://localhost:8000/api/lightning/product/query',
        'sit': 'http://guard.shouxin168.net/api/lightning/product/query',
        'prod': 'https://guard.shouxin168.com/api/lightning/product/query'
    }
    resp_code_biz_data_error = ('SW0017',
                                'SW0018',
                                'SW0019',
                                'SW0020',
                                'SW1009',
                                'SW1010',
                                'SW1011',
                                'SW1012'
                                )
    df_default_header: Tuple[str, ...] = tuple()

    def __init__(self, rsa_prv_key: str, aes_key: str, company_uuid: str, url: str = ''):
        """

        :param rsa_prv_key:
        :param aes_key:
        :param company_uuid:
        :param url:

        self.resp_data is BoolDataBase resp result

        """
        self.url: str = url or self.urls['prod']
        self.rsa_prv_key: str = rsa_prv_key
        self.aes_key: str = aes_key
        self.company_uuid: str = company_uuid
        self.response: Dict = dict()
        self.biz_data: Dict = dict()

    @staticmethod
    def generate_biz_data(*args: str) -> Dict[str, Any]:
        raise NotImplementedError()

    @property
    def default_resp_data(self) -> Dict[str, Any]:
        raise NotImplementedError()

    def set_biz_data(self, *args: str) -> 'BoolDataBase':
        self.biz_data = self.generate_biz_data(*args)
        return self

    def encrypt_biz_data(self, biz_data: Dict[str, str]) -> str:
        sorted_str = json.dumps(biz_data, sort_keys=True)
        return AESCrypt(self.aes_key).encrypt(sorted_str)

    def generate_sign(self, biz_data: Dict[str, str]) -> str:
        sorted_str = udict.Dict(biz_data).sort_keys()
        print('sorted_str ++++++++++', sorted_str)
        return RSAEncryption.generate_signature(sorted_str, self.rsa_prv_key)

    def request(self, biz_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        biz_data = biz_data or self.biz_data
        data = {
            'sign': self.generate_sign(biz_data),
            'biz_data': self.encrypt_biz_data(biz_data),
            'institution_id': self.company_uuid,
        }
        resp = requests.post(self.url, data=data)
        if resp.ok:
            self.response = resp.json()
            return self.response
        else:
            raise ValueError(resp.text)

    @staticmethod
    def is_success_request(resp: Dict[str, Any]) -> bool:
        if isinstance(resp, dict) and resp.get('resp_code') == 'SW0000':
            return True
        else:
            print(resp)
            return False

    def is_succeed(self, resp: Dict[str, Any] = None) -> bool:
        """
        the function is same to is_success_request,
        but support to chain operate
        :param resp:
        :return:
        """
        response = self.response if resp is None else resp
        return self.is_success_request(response)

    def result_as_df(self, valuable_part: Union[List, Dict, None],
                     default_part: Union[List, Dict, None],
                     append_biz_data: bool = True) -> pd.DataFrame:
        """
        transfer booldata result(type dict) to pandas.dataframe
        this method often is used to generate excel.

        :param valuable_part: valuable part of booldata.resp_data
            example: self.response.get('resp_data', {}).get('data_list')
        :param default_part: return default part when valuable_part is empty,
            default value refer to person_law.
        :param append_biz_data: append biz_data to return value
        if data_list is empty, return default_data_list
        if append_biz_data, append biz_data to return value
        :return:
        """
        # valuable = valuable_part or self.response.get('resp_data', {})
        # interface personal_law valuable_part is {'data_list': [], 'total': 0}
        valuable = valuable_part
        valuable = valuable or default_part
        if append_biz_data:
            biz_data = udict.Dict(self.biz_data).remove(('service', 'mode'))
            if isinstance(valuable, list):
                new_valuable = []
                for val_data in valuable:
                    val_data.update(biz_data)
                    new_valuable.append(val_data)
                valuable = new_valuable
            elif isinstance(valuable, dict):
                valuable.update(biz_data)
        try:
            return pd.DataFrame(valuable)
        except ValueError as e:
            return pd.DataFrame(valuable, index=[0])

    # ValueError: If using all scalar values, you must pass an index

    def result_as_df_partial(self, append_biz_data: bool = True) -> pd.DataFrame:
        """
        subclass wrap method result_as_df partially
        :param append_biz_data:
        :return:
        """
        raise NotImplementedError()
