#! usr/bin/python
# coding=utf-8

# Copyright 2014 Baidu, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the
# License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific language governing permissions
# and limitations under the License.


"""
This module provides a client class for SMS.
"""

import copy
import json
import logging
import uuid

import http.client

import baidubce.services.sms.model as model
from baidubce import utils
from baidubce.auth import bce_v1_signer
from baidubce.bce_base_client import BceBaseClient
from baidubce.bce_client_configuration import BceClientConfiguration
from baidubce.exception import BceClientError
from baidubce.exception import BceServerError
from baidubce.http import bce_http_client
from baidubce.http import http_headers
from baidubce.http import http_methods
from baidubce.services import sms
from baidubce.utils import required

_logger = logging.getLogger(__name__)


def _parse_result(http_response, response):
    if http_response.status / 100 == http.client.CONTINUE / 100:
        raise BceClientError('Can not handle 1xx http status code')
    bse = None
    body = http_response.read()
    if body:
        d = json.loads(body)

        if 'message' in d and 'code' in d and 'requestId' in d:
            r_code = d['code']
            # 1000 means success
            if r_code != '1000':
                bse = BceServerError(d['message'],
                                     code=d['code'],
                                     request_id=d['requestId'])
            else:
                response.__dict__.update(
                    json.loads(body, object_hook=utils.dict_to_python_object).__dict__)
                http_response.close()
                return True
        elif http_response.status / 100 == http.client.OK / 100:
            response.__dict__.update(
                json.loads(body, object_hook=utils.dict_to_python_object).__dict__)
            http_response.close()
            return True
    elif http_response.status / 100 == http.client.OK / 100:
        return True

    if bse is None:
        bse = BceServerError(http_response.reason, request_id=response.metadata.bce_request_id)
    bse.status_code = http_response.status
    raise bse


class SmsClient(BceBaseClient):
    """
    Sms sdk client
    """

    def __init__(self, config=None):
        if config is not None:
            self._check_config_type(config)
        BceBaseClient.__init__(self, config)

    @required(config=BceClientConfiguration)
    def _check_config_type(self, config):
        return True

    @required(signature_id=str, template_id=str, type=str, mobile=str, content_var_dict=dict)
    def send_message(self, signature_id, template_id, mobile, content_var_dict, config=None, custom=None,
                     user_ext_id=None, merchant_url_id=None):
        """
        Send message

        :param signature_id: The unique code identifying message signature, can be obtained from cloud.baidu.com
        :type  signature_id: string or unicode

        :param template_id: The unique code identifying message content template, can be obtained from cloud.baidu.com
        :type  template_id: string or unicode

        :param mobile: The target mobile, use "," as separators if you have multiple targets.
        :type  mobile: string or unicode

        :param content_var_dict: A map like "{"template param name": "template param content"}
        :type  content_var_dict: dict

        :param config: None
        :type  config: BceClientConfiguration

        :param custom: The user-defined param
        :type  custom: string or unicode

        :param user_ext_id: The user-defined channel code
        :type  user_ext_id: string or unicode

        :param merchant_url_id: The id of callback url specified by user
        :type  merchant_url_id: string or unicode

        :return: Object
            {
              "request_id": "5e6dacd5-8815-4183-8255-4ff079bf24e6",
              "code": "1000",
              "message": "成功",
              "data": [
                {
                  "code": "1000",
                  "message": "成功",
                  "mobile": "13800138000",
                  "message_id": "e325ea68-02c1-47ad-8844-c7b93cafaeba_13800138000"
                }
              ]
            }
        """
        data = {
            'signatureId': signature_id,
            'template': template_id,
            'mobile': mobile,
            'contentVar': content_var_dict,
            'custom': custom,
            'userExtId': user_ext_id,
            'merchantUrlId': merchant_url_id
        }

        return self._send_request(http_methods.POST, 'sendSms', body=json.dumps(data), config=config, api_version=1)

    @required(content=str, content_type=str, country_type=str)
    def create_signature(self, content, content_type, description=None, country_type="DOMESTIC",
                         signature_file_base_64=None, signature_file_format=None, config=None):
        """
        Create signature
        :param content: Signature content, only Chinese and English characters and numbers are allowed.
        :type  content: string or unicode

        :param content_type: Signature type, one of "Enterprise, MobileApp, Web, WeChatPublic, Brand, Else"
        :type  content_type: string or unicode

        :param description: Description of the signature
        :type  description: string or unicode

        :param country_type: The country or region in which the template can be used. Default value is "DOMESTIC".
                             The value of countryType could be DOMESTIC or INTERNATIONAL or GLOBAL.
                             DOMESTIC: the template can only be used in Mainland China.
                             INTERNATIONAL: the template can only be used out of Mainland China.
                             GLOBAL: the template can only be used all over the world.
        :type  country_type: string or unicode

        :param signature_file_base_64: The base64 encoding string of the signature certificate picture
        :type  signature_file_base_64: string or unicode

        :param signature_file_format: The format of the signature certificate picture, only one of JPG, PNG,
                                      JPEG allowed.
        :type  signature_file_format: string or unicode

        :param config: None
        :type  config: BceClientConfiguration
        :return: Object
            {
              "signature_id": "sms-sign-WWejWQ54455",
              "status": "SUBMITTED"
            }

        """
        data = {
            "content": content,
            "contentType": content_type,
            "countryType": country_type
        }
        if description:
            data["description"] = description
        if signature_file_base_64:
            data["signatureFileBase64"] = signature_file_base_64
        if signature_file_format:
            data["signatureFileFormat"] = signature_file_format
        return model.CreateSignatureResponse(self._send_request(http_methods.POST, 'signatureApply',
                                                                params={"clientToken": uuid.uuid4()},
                                                                body=json.dumps(data), config=config, api_version=2))

    @required(content=str, content_type=str, country_type=str, signature_id=str)
    def update_signature(self, content, content_type, country_type, signature_id, description=None,
                         signature_file_base_64=None, signature_file_format=None, config=None):
        """
        Update signature
        :param content: Signature content
        :type  content: string or unicode

        :param content_type: Signature type, one of "Enterprise, MobileApp, Web, WeChatPublic, Brand, Else"
        :type  content_type: string or unicode

        :param signature_id: The unique code identifying the signature
        :type  signature_id: string or unicode

        :param description: Description of the signature
        :type  description: string or unicode

        :param country_type: The country or region in which the template can be used.
                             The value of countryType could be DOMESTIC or INTERNATIONAL or GLOBAL.
                             DOMESTIC: the template can only be used in Mainland China.
                             INTERNATIONAL: the template can only be used out of Mainland China.
                             GLOBAL: the template can only be used all over the world.
        :type  country_type: string or unicode

        :param signature_file_base_64: The base64 encoding string of the signature certificate picture
        :type  signature_file_base_64: string or unicode

        :param signature_file_format: The format of the signature certificate picture, only one of JPG, PNG,
                                      JPEG allowed.
        :type  signature_file_format: string or unicode

        :param config: None
        :type  config: BceClientConfiguration
        :return: Object
            {
              "content": "Baidu",
              "content_type": "Enterprise",
              "description": "test sdk",
              "country_type": "DOMESTIC",
              "signature_file_base64": "test-string-base64encoded",
              "signature_file_format": "png"
            }
        """
        data = {
            "content": content,
            "contentType": content_type,
            "countryType": country_type
        }

        if description:
            data["description"] = description
        if signature_file_base_64:
            data["signatureFileBase64"] = signature_file_base_64
        if signature_file_format:
            data["signatureFileFormat"] = signature_file_format
        return self._send_request(http_methods.PUT, 'signatureApply', key=signature_id,
                                  body=json.dumps(data), config=config, api_version=2)

    @required(signature_id=str)
    def get_signature_detail(self, signature_id, config=None):
        """
        Get signature detail
        :param signature_id: The unique code identifying the signature
        :type  signature_id: string or unicode

        :param config: None
        :type  config: BceClientConfiguration

        :return: Object
            {
              "signature_id": "sms-sign-WWejWQ54455",
              "user_id": "bbede3f8c42e4113b6971fd09a57f494",
              "content": "Baidu",
              "content_type": "MobileApp",
              "description": "test sdk",
              "review": "",
              "status": "SUBMITTED",
              "country_type": "GLOBAL",
            }
        """
        return model.GetSignatureResponse(self._send_request(http_methods.GET, 'signatureApply', key=signature_id,
                                                             config=config, api_version=2))

    @required(signature_id=str)
    def delete_signature(self, signature_id, config=None):
        """
        Delete signature
        :param signature_id: The unique code identifying the signature
        :type  signature_id: string or unicode

        :param config: None
        :type  config: BceClientConfiguration

        :return:
        """
        return self._send_request(http_methods.DELETE, 'signatureApply', key=signature_id, config=config, api_version=2)

    @required(name=str, content=str, sms_type=str, country_type=str, description=str)
    def create_template(self, name, content, sms_type, country_type, description, config=None):
        """
        Create template with specific name and content

        :param name: Template name
        :type  name: string or unicode

        :param content: Template content like 'this is ${APP}, your code is ${VID}'
        :type  content: string or unicode

        :param sms_type: Business type of the template content, can be obtained from cloud.baidu.com
        :type  sms_type: string or unicode

        :param country_type: The country or region in which the template can be used.
                             The value of countryType could be DOMESTIC or INTERNATIONAL or GLOBAL.
                             DOMESTIC: the template can only be used in Mainland China.
                             INTERNATIONAL: the template can only be used out of Mainland China.
                             GLOBAL: the template can only be used all over the world.
        :type  country_type: string or unicode

        :param description: Description of the template
        :type  description: string or unicode

        :param config: None
        :type  config: BceClientConfiguration

        :return: Object
            {
                "template_id": "sms-tmpl-wHoJXL09355",
                "status": "SUBMITTED",
            }

        :rtype: baidubce.bce_response.BceResponse
        """
        data = {'name': name,
                'content': content,
                'smsType': sms_type,
                'countryType': country_type,
                'description': description}

        return model.CreateTemplateResponse(self._send_request(http_methods.POST, 'template',
                                                               params={"clientToken": uuid.uuid4()},
                                                               body=json.dumps(data), config=config, api_version=2))

    @required(template_id=str, name=str, content=str, sms_type=str, country_type=str)
    def update_template(self, template_id, name, content, sms_type, country_type, description=None, config=None):
        """
        Update template when audition failed.
        :param template_id: The unique code identifying the template
        :type  template_id: string or unicode

        :param name: the name of template
        :type  name: string or unicode

        :param content: the content of template,such as 'this is ${APP}, your code is ${VID}'
        :type  content: string or unicode

        :param sms_type: The business type of the template content, can be obtained from cloud.baidu.com
        :type  sms_type: string or unicode

        :param country_type: The country or region in which the template can be used.
                             The value of countryType could be DOMESTIC or INTERNATIONAL or GLOBAL.
                             DOMESTIC: the template can only be used in Mainland China.
                             INTERNATIONAL: the template can only be used out of Mainland China.
                             GLOBAL: the template can only be used all over the world.
        :type  country_type: string or unicode

        :param description: Description of the template
        :type  description: string or unicode

        :param config: None
        :type  config: BceClientConfiguration

        :return:
        """
        data = {'name': name,
                'content': content,
                'smsType': sms_type,
                'countryType': country_type}
        if description:
            data["description"] = description
        return self._send_request(http_method=http_methods.PUT, function_name='template', key=template_id,
                                  body=json.dumps(data), config=config, api_version=2)

    @required(template_id=str)
    def get_template_detail(self, template_id, config=None):
        """
        Get template detail
        :param template_id: The ID of message template
        :type  template_id: string or unicode

        :param config: None
        :type  config: BceClientConfiguration

        :return:
            {
              "template_id": "sms-tmpl-wHoJXL09355",
              "user_id": "bbede3f8c42e4113b6971fd09a57f494",
              "name": "TemplateNameTest",
              "content": "${code}",
              "sms_type": "CommonNotice",
              "description": "test modify",
              "review": "通过",
              "status": "APPROVED",
              "country_type": "INTERNATIONAL",
            }
        """
        return model.GetTemplateResponse(self._send_request(http_methods.GET, function_name='template', key=template_id,
                                                            config=config, api_version=2))

    @required(template_id=str)
    def delete_template(self, template_id, config=None):
        """
        Delete template
        :param template_id: The ID of message template
        :type  template_id: string or unicode

        :param config: None
        :type  config: BceClientConfiguration

        :return:
        """
        return self._send_request(http_method=http_methods.DELETE, function_name='template', key=template_id,
                                  config=config, api_version=2)

    def query_quota_rate(self, config=None):
        """
        Query quota and rate-limit detail
        :param config: None
        :type  config: BceClientConfiguration

        :return:
            {
              "quota_oer_day": 100,
              "quota_per_month": 1000,
              "quota_remain_today": 100,
              "quota_remain_this_month": 1000,
              "apply_check_status": "PASS",
              "check_reply": "",
              "apply_quota_per_day": 10,
              "apply_quota_per_month": 10,
              "rate_limit_per_mobile_per_sign_by_minute": 5,
              "rate_limit_per_mobile_per_sign_by_hour": 10,
              "rate_limit_per_mobile_per_sign_by_day": 50,
              "rate_limit_white_list": true
            }
        """
        return model.QueryQuotaResponse(self._send_request(http_methods.GET, function_name="quota",
                                                           params={"userQuery": ""}, config=config, api_version=2))

    @required(quota_per_day=int, quota_per_month=int, rate_limit_per_mobile_per_sign_by_minute=int,
              rate_limit_per_mobile_per_sign_by_hour=int, rate_limit_per_mobile_per_sign_by_day=int)
    def update_quota_rate(self, quota_per_day, quota_per_month, rate_limit_per_mobile_per_sign_by_minute,
                          rate_limit_per_mobile_per_sign_by_hour, rate_limit_per_mobile_per_sign_by_day, config=None):
        """

        :param quota_per_day: Upper bound of the response-success request counts in one natural day.
        :type  quota_per_day: int

        :param quota_per_month: Upper bound of the response-success request counts in one natural month.
        :type  quota_per_month: int

        :param rate_limit_per_mobile_per_sign_by_minute: The limit with same mobile and signature in one minute
        :type  rate_limit_per_mobile_per_sign_by_minute: int

        :param rate_limit_per_mobile_per_sign_by_hour: Hourly limit with same mobile and signature
        :type  rate_limit_per_mobile_per_sign_by_hour: int

        :param rate_limit_per_mobile_per_sign_by_day: Daily rate limit with same mobile and signature
        :type  rate_limit_per_mobile_per_sign_by_day: int

        :param config: None
        :type  config: BceClientConfiguration

        :return:
        """
        data = {
            "quotaPerDay": quota_per_day,
            "quotaPerMonth": quota_per_month,
            "rateLimitPerMobilePerSignByMinute": rate_limit_per_mobile_per_sign_by_minute,
            "rateLimitPerMobilePerSignByHour": rate_limit_per_mobile_per_sign_by_hour,
            "rateLimitPerMobilePerSignByDay": rate_limit_per_mobile_per_sign_by_day
        }
        return self._send_request(http_methods.PUT, function_name="quota", body=json.dumps(data),
                                  config=config, api_version=2)

    @staticmethod
    def _get_path_v3(config, function_name=None, key=None):
        return utils.append_uri(sms.URL_PREFIX_V3, function_name, key)

    @staticmethod
    def _get_path_v3_2(config, function_name=None, key=None):
        return utils.append_uri(sms.URL_PREFIX_V3_2, function_name, key)

    @staticmethod
    def _bce_sms_sign(credentials, http_method, path, headers, params,
                      timestamp=0, expiration_in_seconds=1800,
                      headers_to_sign=None):

        headers_to_sign_list = [b"host",
                                b"content-md5",
                                b"content-length",
                                b"content-type"]

        if headers_to_sign is None or len(headers_to_sign) == 0:
            headers_to_sign = []
            for k in headers:
                k_lower = k.strip().lower()
                if k_lower.startswith(http_headers.BCE_PREFIX) or k_lower in headers_to_sign_list:
                    headers_to_sign.append(k_lower)
            headers_to_sign.sort()
        else:
            for k in headers:
                k_lower = k.strip().lower()
                if k_lower.startswith(http_headers.BCE_PREFIX):
                    headers_to_sign.append(k_lower)
            headers_to_sign.sort()

        return bce_v1_signer.sign(credentials,
                                  http_method,
                                  path,
                                  headers,
                                  params,
                                  timestamp,
                                  expiration_in_seconds,
                                  headers_to_sign)

    def _merge_config(self, config):
        if config is None:
            return self.config
        else:
            self._check_config_type(config)
            new_config = copy.copy(self.config)
            new_config.merge_non_none_values(config)
            return new_config

    def _send_request(self, http_method, function_name=None, key=None, body=None, headers=None, params=None,
                      config=None, body_parser=None, api_version=1):
        config = self._merge_config(config)
        path = {1: SmsClient._get_path_v3,
                2: SmsClient._get_path_v3_2,
                }[api_version](config, function_name, key)

        if body_parser is None:
            body_parser = _parse_result

        if headers is None:
            headers = {b'Accept': b'*/*', b'Content-Type': b'application/json;charset=utf-8'}

        return bce_http_client.send_request(config, SmsClient._bce_sms_sign, [body_parser], http_method, path, body,
                                            headers, params)

    @required(type=str, phone=str)
    def create_mobile_black(self, type, phone, country_type, sms_type=None, signature_id_str=None, config=None):
        """

        :param type: The value of type could be MerchantBlack or SignatureBlack
        :type type: str

        :param country_type: The value of countryType could be DOMESTIC or INTERNATIONAL
        :type country_type: str

        :param sms_type: Mobile of black, Support multiple mobile phone numbers, up to 200 maximum, separated by comma.
        :type sms_type: str

        :param signature_id_str: When the value of "type" is "SignatureBlack", this field is required.
        :type signature_id_str: str

        :param phone: When the value of "type" is "SignatureBlack", this field is required.
        :type phone: str

        :param config: None
        :type  config: BceClientConfiguration

        """
        data = {
            "type": type,
            "phone": phone,
            "countryType": country_type
        }
        if sms_type:
            data["smsType"] = sms_type

        if signature_id_str:
            data["signatureIdStr"] = signature_id_str

        return self._send_request(http_methods.POST, function_name="blacklist", body=json.dumps(data),
                                  config=config, api_version=2)

    def get_mobile_black(self, phone=None, country_type=None, sms_type=None, signature_id_str=None, start_time=None,
                         end_time=None, page_no=None, page_size=None, config=None):
        """
        Get mobile black
        :param phone: Support multiple mobile phone numbers, up to 200 maximum, separated by comma.
        :type phone: str

        :param country_type: The value of countryType could be DOMESTIC or INTERNATIONAL
        :type  country_type: str

        :param sms_type: smsType
        :type  sms_type: str

        :param signature_id_str: signatureIdStr
        :type  signature_id_str: str

        :param start_time: format is yyyy-MM-dd
        :type  start_time: str

        :param end_time: format is yyyy-MM-dd
        :type  end_time: str

        :param page_no: The current page number
        :type  page_no: int

        :param page_size: The current page size, range from 1 to 99999
        :type  page_size: int

        :param config: None
        :type  config: BceClientConfiguration

        :return: Object
            {
                "blacklists": [
                    {
                        "phone": "17611111111",
                        "type": "SignatureBlack",
                        "smsType": "CommonNotice",
                        "signatureIdStr": "1234",
                        "updateDate": "2023-07-14 14:23:41"
                    }
                ],
                "totalCount": 1,
                "pageNo": 1,
                "pageSize": 100
            }
        """

        req_params = {}
        if phone:
            req_params["phone"] = phone

        if country_type:
            req_params["countryType"] = country_type

        if sms_type:
            req_params["smsType"] = sms_type

        if signature_id_str:
            req_params["signatureIdStr"] = signature_id_str

        if start_time:
            req_params["startTime"] = start_time

        if end_time:
            req_params["endTime"] = end_time

        if page_no:
            req_params["pageNo"] = page_no

        if page_size:
            req_params["pageSize"] = page_size

        return model.GetMobileBlackResponse(self._send_request(http_methods.GET, function_name="blacklist",
                                                               params=req_params, config=config, api_version=2))

    @required(phones=str)
    def delete_mobile_black(self, phones, config=None):
        """
        Delete mobile_black
        :param phones: Support multiple mobile phone numbers, up to 200 maximum, separated by comma.
        :type  phones: string

        :param config: None
        :type  config: BceClientConfiguration

        :return:
        """

        req_params = {
            "phones": phones
        }
        return self._send_request(http_method=http_methods.DELETE, function_name='blacklist/delete', params=req_params,
                                  config=config, api_version=2)

    @required(start_time=str, end_time=str)
    def list_statistics(self, start_time, end_time, sms_type='all', country_type='domestic',
                        signature_id=None, template_code=None, config=None):
        """
        Get messages statistics list
        :param start_time: The start of time condition, format: yyyy-MM-dd
        :type  start_time: str

        :param end_time: The end of time condition, format: yyyy-MM-dd
        :type  end_time: str

        :param sms_type: Queried message type, "all" as default
        :type  sms_type: str

        :param signature_id: Queried signature id
        :type  signature_id: str

        :param template_code: Queried template code, for instance: "sms-tmpl-xxxxxxxx"
        :type  template_code: str

        :param country_type: Queried country type, available values: "domestic", "international"
        :type  country_type: str

        :param config: None
        :type  config: BceClientConfiguration

        :return: Object:
            {
                "statisticsResults": [
                    {
                        "datetime": "合计",
                        "countryAlpha2Code": "",
                        "submitLongCount": "10",
                        "submitCount": "100",
                        "deliverSuccessCount": "99",
                        "deliverSuccessLongCount": "10",
                        "deliverFailureCount": "1",
                        "unknownCount": "0",
                        "deliverSuccessProportion": "0.99",
                        "deliverFailureProportion": "0.01",
                        "notExistCount": "0",
                        "signatureOrTemplateCount": "0",
                        "abnormalCount": "0",
                        "overclockingCount": "0",
                        "otherErrorCount": "0",
                        "blacklistCount": "0",
                        "routeErrorCount": "0",
                        "issueFailureCount": "0",
                        "parameterErrorCount": "0",
                        "illegalWordCount": "0",
                        "anomalyCount": "1",
                        "unknownErrorCount": "0",
                        "receiptProportion": "1.0",
                        "unknownProportion": "0",
                        "responseSuccessCount": "100",
                        "responseTimeoutCount": "0",
                        "responseSuccessProportion": "1.0"
                    },
                    {
                        "datetime": "2023-10-30",
                        "countryAlpha2Code": "",
                        "submitLongCount": "10",
                        "submitCount": "100",
                        "deliverSuccessCount": "99",
                        "deliverSuccessLongCount": "10",
                        "deliverFailureCount": "1",
                        "unknownCount": "0",
                        "deliverSuccessProportion": "0.99",
                        "deliverFailureProportion": "0.01",
                        "notExistCount": "0",
                        "signatureOrTemplateCount": "0",
                        "abnormalCount": "0",
                        "overclockingCount": "0",
                        "otherErrorCount": "0",
                        "blacklistCount": "0",
                        "routeErrorCount": "0",
                        "issueFailureCount": "0",
                        "parameterErrorCount": "0",
                        "illegalWordCount": "0",
                        "anomalyCount": "1",
                        "unknownErrorCount": "0",
                        "receiptProportion": "1.0",
                        "unknownProportion": "0",
                        "responseSuccessCount": "100",
                        "responseTimeoutCount": "0",
                        "responseSuccessProportion": "1.0"
                    },
                ]
            }
        """

        req_params = {
            "startTime": start_time + " 00:00:00",
            "endTime": end_time + " 23:59:59",
            "smsType": sms_type,
            "dimension": "day"
        }

        if country_type:
            req_params["countryType"] = country_type

        if signature_id:
            req_params["signatureId"] = signature_id

        if template_code:
            req_params["templateCode"] = template_code

        return model.ListStatisticsResponse(self._send_request(http_method=http_methods.GET, function_name='summary',
                                            params=req_params, config=config, api_version=2))
