# -*- coding: utf8 -*-

import urllib
import urlparse
import hashlib
import time
import json
import math
import datetime
import random
import base64
from hashlib import sha1
from functools import wraps, partial

import requests
from django.conf import settings

from gm_upload import IMG_TYPE, upload
from gm_types.gaia import (
    PHONE400_VENDOR,
    ZTTH_400_PHONE_STATUS,
    UNION_400_PHONE_STATUS,
    ZTTH_400_PHONE_ERROR_CODE_STATUS
)

from rpc.tool.log_tool import info_logger, logging_exception
from api.tool.common_tool import upload_name


SUCCESS_CODE = '000000'


def md5(str):
    m = hashlib.md5()
    m.update(str)
    return m.hexdigest()


def get_400_request(phone_service_method, json_load=False, sessionid=None, method=None, phone=None, key=None):
    id = settings.CUSTOM_SERVICE_PHONE_API_ID
    password = settings.CUSTOM_SERVICE_PHONE_API_PASS
    data = urllib.urlencode({
        "accountSid": id,
        "signature": md5(id + md5(password) + time.strftime('%Y%m%d%H%M%S', time.localtime())),
        "method": method,
        "key": key,
        "value": phone,
        "fetchDate": time.strftime('%Y%m%d%H%M%S', time.localtime()),
        "sessionid": sessionid,
    })
    if phone_service_method == 'phone_service_transfer':
        url = settings.CUSTOM_SERVICE_PHONE_API_ENDPOINT
    elif phone_service_method == 'phone_service_status_report':
        url = settings.CUSTOM_SERVICE_PHONE_REPORT_API_ENDPOINT
    elif phone_service_method == 'phone_service_audio_url':
        url = settings.CUSTOM_SERVICE_PHONE_AUDIO_API_ENDPOINT

    try:
        result = urllib.urlopen(url, data).read()
        if json_load:
            result = json.loads(result)
    except Exception:
        result = ''

    return result


def _request(method, phone=None, key=None):
    """
    电话分机转接
    """
    result = get_400_request('phone_service_transfer', json_load=True, method=method, phone=phone, key=key)
    if result:
        data = result
    else:
        return False, u'网络连接错误'.encode('utf8')

    if 'getAll' in method or 'getByValue' in method:
        if 'respCode' in data.keys():
            return data['respCode'], data['result'].encode("utf8")
        return SUCCESS_CODE, data['result']

    return data['respCode'], data['result'].encode("utf8")


def get_download_from_400_upload_to_qiniu(audio_url):
    """
    下载音频文件,上传到qiniu上
    """
    try:
        ext = audio_url.split('.')[-1]
    except:
        ext = None

    save_name = upload_name(ext)
    retry_times = 0

    while retry_times <= settings.AUDIO_UPLOAD_MAX_RETRY_TIMES:
        try:
            file = urllib.urlopen(audio_url).read()
            file_url = upload(file, save_name=save_name, img_type=IMG_TYPE.AUDIO)
        except:
            file_url = ''

        if file_url:
            break
        else:
            # 指数增长重试时间
            time_sec = int(math.pow(2, retry_times))
            time.sleep(time_sec)
            retry_times = retry_times + 1

    if retry_times > settings.AUDIO_UPLOAD_MAX_RETRY_TIMES:
        info_logger.info('audio %s download_upload retry max time', audio_url)

    return file_url


class PhoneService(object):
    """400 phone service."""

    @classmethod
    def get_phone_prefix(cls, ext):
        """get 400 phone prefix by ext."""
        ext = ext.strip()
        if not ext:
            # if ext it not provided, return empty string
            return ''

        length = len(ext)
        assert length in (4, 6), 'ext length invalid'

        prefix = Phone400Service().client.get_host_num()
        return prefix

    @classmethod
    def get_host_num(cls):
        return Phone400Service().client.get_host_num()

    @classmethod
    def get_phone_num(cls, ext):
        """get 400 phone number with format as `400,,ext`."""
        pre = cls.get_phone_prefix(ext)
        if not pre:
            # get_phone_prefix will check if ext valid, if not
            # it returns '', so we should return empty string directly
            return ''

        return '%s,,%s' % (pre, ext)

    def get_status_report(self):
        """
        通话状态报告接口
        """
        result = get_400_request('phone_service_status_report', json_load=True)
        data = result if result else {}
        return data

    def get_audio_file(self, sessionid):
        """
        获取录音音频接口
        """
        result = get_400_request('phone_service_audio_url', sessionid=sessionid, json_load=True)
        data = result if result else ''
        return data

    def update(self, phone, ext):
        """update a new phone/ext pair.
        #
        response
        {
        success: boolean,
        msg: string,
        }
         """
        code, res = self._update(phone=phone, key=ext)
        success = True
        msg = 'success'
        if code != SUCCESS_CODE:
            success = False
            msg = res

        return {
            'success': success,
            'msg': msg
        }

    def save(self, phone, ext):
        code, res = self._save(phone=phone, key=ext)
        success = True
        msg = 'success'
        if code != SUCCESS_CODE:
            success = False
            msg = res

        return {
            'success': success,
            'msg': msg
        }

    def _save(self, phone, key):
        """
        return: 状态码，说明
        """
        return _request(method='save', phone=phone, key=key)

    def _update(self, phone, key):
        return _request(method='update', phone=phone, key=key)

    def _delete_by_key(self, key):
        return _request(method='deleteByKey', key=key)

    def _get_by_key(self, key):
        return _request(method='getByKey', key=key)

    def _get_by_value(self, value):
        return _request(method='getByValue', phone=value)

    def _get_all(self):
        """
        return:
        000000, [{u'value': u'18244681661', u'key': u'001'}, {u'value': u'18244681662', u'key': u'002'}]
        """
        return _request(method='getAll')


class Phone400ServiceException(Exception):
    pass


def multi_instances_execute(fn):
    @wraps(fn)
    def _wrap(self, *args, **kwargs):
        for instance in self.instances:
            getattr(instance, fn.__name__)(*args, **kwargs)
        return True
    return _wrap


class FourThundred(object):
    """base 400 service."""

    _vendor = None

    def __init__(self, account, user, passwd, api_domain):
        # NOTE: account 主机号码
        self.account = account
        self.user = user
        self.passwd = passwd
        self.api_domain = api_domain
        self.vendor = self._vendor

    def get_host_num(self):
        return self.account

    def get_url(self, endpoint):
        return urlparse.urljoin(self.api_domain, endpoint)

    def create(self, ext, phone):
        raise NotImplementedError

    def update(self, ext, phone):
        raise NotImplementedError

    def save_record(self, record):
        raise NotImplementedError

    def fetch_phone_records_iter(self, start_time, end_time):
        raise NotImplementedError


class ZTTH400(FourThundred):

    _vendor = PHONE400_VENDOR.ZTTH

    def parse_response_result(self, url, data=None, code=ZTTH_400_PHONE_STATUS.SUCCESS_CODE):
        if data:
            response = self.caller(url, data=data)
        else:
            response = self.caller(url)
        jsonobj = json.loads(response.content)
        # 一般成功码是200，但是有部分接口是500，而且未来可能对方接口可能把500改成200
        if jsonobj['code'] != ZTTH_400_PHONE_STATUS.SUCCESS_CODE and jsonobj['code'] != code:
            raise ZTTH400Exception(response.content)

        return jsonobj

    def get_password_digest(self, s, c, n):
        return base64.encodestring(sha1('%s%s%s' % (base64.decodestring(n), c, s)).digest()).strip()

    @property
    def caller(self):
        """return 400 interface call http method."""
        s = self.passwd
        c = int(time.time())
        n = base64.encodestring('%s' % random.randint(1000, 9999)).strip()

        X_WSSE = u'UsernameToken Username="{username}",PasswordDigest="{pwdigest}",Nonce="{nonce}",Created="{created}"'
        args = {
            'username': self.user,
            'pwdigest': self.get_password_digest(s, c, n),
            'nonce': n,
            'created': c,
        }
        X_WSSE = X_WSSE.format(**args)
        headers = {
            'X-WSSE': X_WSSE,
            'Content-Type': 'application/x-www-form-urlencoded',
        }
        return partial(requests.post, headers=headers)

    def create(self, ext, phone):
        # self.add_agent(ext)
        # self.agent_login(ext, phone)
        pass

    def update(self, ext, phone):
        # 坐席二次迁出会报错,迁出后绑定的电话号码还是上一次,如果被占用删除也会报错
        # try:
        #     self.agent_logout(ext)
        #     agents = self.query_agents(ext)
        #     agents_data = agents.get('data', [])
        #     if len(agents_data) == 1:
        #         _phone = agents_data[0]['pho_num']

        #         phones = self.query_phone_ext(_phone)
        #         phones_data = phones.get('data', [])
        #         if len(phones_data) == 1:
        #             phone_id = phones_data[0]['id']
        #             self.delete_phone_ext(phone_id)
        # except:
        #     logging_exception()

        # self.agent_login(ext, phone)
        pass

    def save_record(self, record):

        if record.get('id'):
            # 接听录音记录
            return self.save_accept_record(record)
        elif record.get('call_id'):
            # 未接听记录
            return self.save_error_record(record)

    def save_accept_record(self, record):
        from api.models import PhoneServiceRecord, Doctor, PhoneCallRecord
        objs = PhoneServiceRecord.objects.filter(vendor=self.vendor, record_id=record['id'])
        if objs.exists():
            return objs.first(), False
        psr = PhoneServiceRecord()
        phone_ext = record.get('ag_num', '')
        call_id = record.get('call_id', '')
        doctor = None
        if call_id:
            try:
                phone_ext = PhoneCallRecord.objects.get(call_id=call_id).phone_ext
                doctor = Doctor.objects.get(phone_ext=phone_ext)
            except:
                doctor = None
        psr.doctor = doctor
        psr.call_phone = record.get('cus_phone', '')
        psr.dest_phone = record.get('ag_phone', '')
        psr.call_time = record.get('start_time', None)
        psr.transfer_phone = phone_ext

        psr.call_status = ZTTH_400_PHONE_STATUS.GENGMEI_ACCEPT_CODE

        record_url = record.get('record_file', '')
        if record_url:
            record_url = settings.ZTTH400_AUDIO_RECORD + record_url.replace('/var/recall', '')
            psr.audio_url = get_download_from_400_upload_to_qiniu(record_url)
        else:
            psr.audio_url = ''
        psr.duration = record.get('conn_secs', 0)
        psr.vendor = self.vendor
        psr.record_id = record.get('id', '')
        psr.origin_data = record
        psr.save()
        return psr, True

    def save_error_record(self, record):
        from api.models import PhoneServiceRecord, Doctor, PhoneCallRecord
        objs = PhoneServiceRecord.objects.filter(vendor=self.vendor, record_id=record['call_id'])
        if objs.exists():
            return objs.first(), False
        psr = PhoneServiceRecord()
        phone_ext = record.get('ag_num', '')
        call_id = record.get('call_id', '')
        doctor = None
        if call_id:
            try:
                phone_ext = PhoneCallRecord.objects.get(call_id=call_id).phone_ext
                doctor = Doctor.objects.get(phone_ext=phone_ext)
            except:
                doctor = None
        psr.doctor = doctor
        psr.call_phone = record.get('caller', '')
        psr.dest_phone = record.get('called', '')
        psr.call_time = record.get('start_time', None)
        psr.transfer_phone = phone_ext

        psr.call_status = ZTTH_400_PHONE_STATUS.GENGMEI_FAIL_CODE

        psr.audio_url = ''
        psr.duration = record.get('conn_secs', 0)
        psr.vendor = self.vendor
        psr.record_id = record.get('call_id', '')
        psr.origin_data = record
        psr.save()
        return psr, True

    def fetch_phone_records(self, start_time, end_time):
        data = []
        for rows in self.fetch_phone_records_iter(start_time, end_time):
            data.append(rows)
        return data

    def fetch_phone_records_iter(self, start_time, end_time):
        try:
            ret = self.fetch_phone_records_one_page(
                start_time, end_time
            )
            rows = ret['data']
            for row in rows:
                yield row

            ret = self.fetch_fail_listen_phone_records_one_page(
                start_time, end_time, result=ZTTH_400_PHONE_ERROR_CODE_STATUS.ERROR_CODE_V3
            )
            rows = ret['data']
            for row in rows:
                yield row

            ret = self.fetch_fail_listen_phone_records_one_page(
                start_time, end_time, result=ZTTH_400_PHONE_ERROR_CODE_STATUS.ERROR_CODE_V1
            )
            rows = ret['data']
            for row in rows:
                yield row

        except ZTTH400Exception:
            logging_exception()
        except:
            logging_exception()
        return

    def fetch_phone_records_one_page(self, start_time, end_time):
        def dtfmt(d):
            return d.strftime('%Y-%m-%d %H:%M:%S')
        # API DOC: http://dev.icsoc.net/wintelapi/record.html
        url = self.get_url('/v2/wintelapi/record/recordlist')
        filter = {
            'filter': {
                'start_time': dtfmt(start_time),
                'end_time': dtfmt(end_time),
            }
        }
        filter = json.dumps(filter)
        url = url + '?info=' + filter

        data = {
            'vcc_code': settings.ZTTH_CONF['user'],
        }
        return self.parse_response_result(url, data=data)

    def fetch_fail_listen_phone_records_one_page(self, start_time, end_time, result):
        def dtfmt(d):
            return d.strftime('%Y-%m-%d %H:%M:%S')
        # API DOC: http://dev.icsoc.net/wintelapi/report.html#id14
        url = self.get_url('/v2/wintelapi/detail/callin')
        filter = {
            'filter': {
                'start_time': dtfmt(start_time),
                'end_time': dtfmt(end_time),
                'result': result,
            }
        }
        filter = json.dumps(filter)
        url = url + '?info=' + filter

        data = {
            'vcc_code': settings.ZTTH_CONF['user'],
        }
        return self.parse_response_result(url, data=data)


class ZTTH400Exception(Phone400ServiceException):
    pass


class Union400Exception(Phone400ServiceException):
    pass


class Union400(FourThundred):

    _vendor = PHONE400_VENDOR.XTWY

    def generate_secret(self, params, token):
        # 1.删除secret
        if 'secret' in params:
            params.pop('secret')

        sign_str = ''
        # 2.对参数排序
        sorted_params = sorted(params)

        for i in sorted_params:
            # 3.将参数名和参数值串在一起
            sign_str += '%s%s' % (i, urllib.quote(str(params[i])))
        sign_str += token
        # 4.使用MD5加密    5.转成字符串
        signed_str = hashlib.md5(sign_str).hexdigest().upper()
        return signed_str

    def _request(self, endpoint, params, token=None):
        """do the request, if errorcode != 0, raise exception.

        return
            {u'data': None, u'errcode': 0, u'errmsg': u'\u6210\u529f'}
            or
            {u'data': {'k': v}, u'errcode': 0, u'errmsg': u'\u6210\u529f'}
        """
        headers = {
            'content_type': 'application/x-www-form-urlencoded',
        }
        post_data = self.build_params(params)
        if token is None:
            token = self.get_token()
        post_data['secret'] = self.generate_secret(post_data, token)
        resp = requests.post(self.get_url(endpoint), data=post_data, headers=headers)
        resp_json = resp.json()
        if 'errcode' in resp_json and resp_json['errcode'] == 0:
            return resp_json

        raise Union400Exception(resp_json)

    def build_params(self, params):
        dt = datetime.datetime
        default = {
            'appver': 1,
            'timestamp': dt.strftime(dt.now(), '%Y%m%d%H%M%S'),
        }
        _params = default.copy()
        _params.update(params)
        return _params

    def get_token(self):
        endpoint = '/api/union400Login.action'
        params = {
            'user': self.user,
        }
        ret = self._request(endpoint, params, self.passwd)
        return ret['data']['token']

    def add_phone_ext(self, ext, phone):
        data = [{
            'dn': ext,
            'tel': phone,
        }]
        return self.batch_add_phone_ext(data)

    def batch_add_phone_ext(self, phones):
        endpoint = '/api/call/addExtension.action'
        data = json.dumps(phones)
        params = {
            'user': self.user,
            'account': self.account,
            'data': data.replace(' ', ''),  # NOTE: fucking undocumented hack
        }
        return self._request(endpoint, params)

    def query_phone_ext(self, ext):
        data = [ext]
        ret = self.batch_query_phone_ext(data)
        return ret.get(ext, '')

    def batch_query_phone_ext(self, ext_lst):
        endpoint = '/api/call/queryExtension.action'
        data = ','.join(ext_lst)
        params = {
            'user': self.user,
            'account': self.account,
            'dn': data,
        }
        ret = self._request(endpoint, params)
        phones = {d['dn']: d['tel'] for d in ret['data']}
        return phones

    def fetch_phone_records(self, start_time, end_time):
        data = []
        for rows in self.fetch_phone_records_iter(start_time, end_time):
            data.extend(rows)

    def fetch_phone_records_one_page(self, start_time, end_time, page=1, size=100):
        def dtfmt(d):
            return d.strftime('%Y%m%d%H%M%S')

        endpoint = '/api/call/queryCallRecord.action'
        params = {
            'user': self.user,
            'account': self.account,
            'startTime': dtfmt(start_time),
            'endTime': dtfmt(end_time),
            'pageSize': size,
            'pageNow': page,
        }
        ret = self._request(endpoint, params)
        return ret

    # ========== reserved method ==========
    def create(self, ext, phone):
        self.add_phone_ext(ext, phone)

    def update(self, ext, phone):
        self.add_phone_ext(ext, phone)

    def fetch_phone_records_iter(self, start_time, end_time):
        import time
        i = 1
        while True:
            try:
                ret = self.fetch_phone_records_one_page(start_time, end_time, i, 500)
                rows = ret['data']['rows']
                if not len(rows):
                    break
                for row in rows:
                    yield row
            except Union400Exception:
                logging_exception()
                break
            except:
                logging_exception()
            i += 1
            time.sleep(5)
        return

    def save_record(self, record):
        from api.models import PhoneServiceRecord, Doctor
        objs = PhoneServiceRecord.objects.filter(vendor=self.vendor, record_id=record['serviceSn'])
        if objs.exists():
            return objs.first(), False
        psr = PhoneServiceRecord()
        phone_ext = record.get('extension', '')
        doctor = None
        if phone_ext:
            try:
                doctor = Doctor.objects.get(phone_ext=phone_ext)
            except:
                doctor = None
        psr.doctor = doctor
        psr.call_phone = record.get('callingid', '')
        psr.dest_phone = record.get('calledid', '')
        psr.call_time = record.get('startTime', None)
        psr.transfer_phone = record.get('extension', '')

        rec_status = record.get('recStatus', 0)
        vender_accept_code = UNION_400_PHONE_STATUS.VENDER_ACCEPT_CODE
        gengmei_accept_code = UNION_400_PHONE_STATUS.GENGMEI_ACCEPT_CODE
        psr.call_status = rec_status if str(rec_status) != vender_accept_code else gengmei_accept_code  # 接通状态统一为2

        record_url = record.get('recordUrl', '')
        if record_url:
            psr.audio_url = get_download_from_400_upload_to_qiniu(record_url)
        else:
            psr.audio_url = ''
        psr.duration = record.get('duration', 0)
        psr.vendor = self.vendor
        psr.record_id = record.get('serviceSn', '')
        psr.origin_data = record
        psr.save()
        return psr, True


class _Phone400Service(object):
    """400 services holder.

    call register to register 400 service
    after register all services, call validate to verify settings

    NOTE: all services should be register in this module
    """

    _instances = {}
    _default_vendor = None

    @classmethod
    def register(cls, vendor, obj):
        if vendor in cls._instances:
            raise Exception('%s already registered!' % vendor)

        if vendor == settings.DEFAULT_400_VENDOR:
            cls._default_vendor = vendor

        cls._instances[vendor] = obj
        return True

    @property
    def client(self):
        """get default 400 service."""
        return self.vendor_client(self._default_vendor)

    @classmethod
    def get_service_by_vendor(cls, vendor):
        return cls._instances[vendor]

    def vendor_client(self, vendor):
        return self._instances.get(vendor, None)

    def create(self, ext, phone):
        for instance in self._instances.itervalues():
            try:
                instance.create(ext, phone)
            except:
                logging_exception()
        return True

    def update(self, ext, phone):
        for instance in self._instances.itervalues():
            try:
                instance.update(ext, phone)
            except:
                logging_exception()
        return True

    def get_by_ext(self, ext):
        pass

    @classmethod
    def validate(cls):
        # validate settings
        assert cls._default_vendor is not None, 'default 400 service required'


_union_400 = Union400(**settings.UNION400_CONF)
_Phone400Service.register(_union_400.vendor, _union_400)

_ztth_400 = ZTTH400(**settings.ZTTH_CONF)
_Phone400Service.register(_ztth_400.vendor, _ztth_400)

_Phone400Service.validate()
Phone400Service = _Phone400Service


def test_union400():
    # FOR UNION400
    USER = '4006333227_dev'
    ACCOUNT = '4006333227'
    PWD = '2e6b055l665q6b5b5e55252ssbyl5s2e'
    API_DOMAIN = 'http://xtapi.union400.com'

    union = Union400(ACCOUNT, USER, PWD, API_DOMAIN)
    # print union.add_phone_ext('111111', '13913000267')
    print union.query_phone_ext('222222')
    ret = union.fetch_phone_records(start_time=datetime.date(2016, 11, 5), end_time=datetime.date(2016, 11, 30))
    print len(ret)


def test_ztth400():
    # USER = ''
    # ACCOUNT = ''
    # PWD = ''
    # API_DOMAIN = ''

    # p4s = Phone400Service()
    # ret = ztth.update_agent('217275', ag_name='894535')
    # ztth.agent_logout('2333')
    # print p4s.update('99999', '15277026382')
    # print ztth.delete_phone_ext('32762')
    # ztth.agent_logout(phone_ext)
    # print ztth.add_phone_ext('111111', '13716255042') 205092 213288
    # ztth.agent_signin('8311')
    pass


if __name__ == '__main__':
    # print(help(urllib))
    # phone_service = PhoneService()
    # res = phone_service._get_all()
    # code, res = phone_service._get_by_key('004')
    # code, res = phone_service._save('18244681662', '003')
    # code, res = phone_service._update('18244681662', '006')
    # code, res = phone_service._delete_by_key('003')
    # code, res = phone_service._get_by_value('18244681661')
    # print("code: {code}, re: {res}".format(code = code, res = res))

    # res = phone_service.update('18244681662', '007')
    # print("suc:{suc}  msg:{msg}".format(suc=res['success'], msg=res['msg']))
    # code, res = phone_service._get_all()
    # print("code: {code}, re: {res}".format(code=code, res=res))

    test_union400()
    # test_ztth400()
