#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
#   Author  :   RobertDing
#   E-mail  :   robertdingx@gmail.com
#   Date    :   16/06/12 19:34:08
#   Desc    :   加密解密请求
#

import json
import time
import uuid
import string
import random
import base64
import hashlib

from datetime import datetime

import requests
from django.conf import settings
from Crypto.PublicKey import RSA
from Crypto.Cipher import ARC4
from Crypto.Cipher import PKCS1_v1_5

from gm_types.gaia import ID_IMAGE_TYPE
from gm_types.gaia import XIAOYING_CONFIRM_CODE, XIAOYING_INSTALLMENT_STATUS

from api.models import (
    Settlement,
    RefundOrder,
    REFUND_STATUS,
    ORDER_OPERATION_TYPE,
    ORDER_OPERATION_ROLE,
    Person,
)

from rpc.tool.error_code import CODES, gen
from rpc.tool.log_tool import installment_logger as log

from pay.models.installment import BorrowerImage
from pay.tool.installment_error import InstallmentError
from pay.tool.order_lock_tool import is_locked
from pay.tool.order_lock_tool import lock
from pay.tool.types import REFUND_TYPE
from pay.tool.new_order_tool import refund_send_notification_and_sms


YINGTZ_KEY = None
GMEI_KEY = None


class Request(object):

    def __init__(self, subject):
        self.subject = subject
        pass

    def handler(self, endpoint):

        def _fetch(**kwargs):
            data = self.encrypt_param(kwargs)
            url = settings.XYBASE_URL.format(
                subject=self.subject, endpoint=endpoint)
            request_id = uuid.uuid4().hex
            args = {'url': url, 'data': kwargs, 'encrypt': data, 'request_id': request_id}
            log.info(json.dumps(args, indent=4))
            try:
                resp = requests.post(url, data=data)
            except:
                log.info({'info': 'request error', 'request_id': request_id})
                raise
            log.info({'request_id': request_id, 'response': resp.content})
            raw_content = self.decrypt(resp.content)
            log.info({'request_id': request_id, 'content': raw_content})
            content = json.loads(raw_content)
            if content['ret'] != 0:
                raise InstallmentError(endpoint, content, request_id)
            return content.get('data', {})
        return _fetch

    @classmethod
    def encrypt_param(cls, params):
        one_time_key = ''.join([random.choice(string.ascii_letters+string.digits) for _ in range(128)])
        rsa_cipher = PKCS1_v1_5.new(YINGTZ_KEY)
        key1 = base64.b16encode(rsa_cipher.encrypt(one_time_key[:117]))
        key2 = base64.b16encode(rsa_cipher.encrypt(one_time_key[-11:]))

        content = json.dumps(params)
        r4_cipher = ARC4.new(one_time_key)
        raw_content = r4_cipher.encrypt(content)
        encoded_content = base64.b16encode(raw_content)

        sign = hashlib.md5(raw_content+settings.XYMD5KEY).hexdigest()
        return {
            'key': key1+key2,
            'sign': sign,
            'content': encoded_content,
            'partner': settings.XYPARTNER
        }

    @classmethod
    def decrypt(cls, raw):
        resp = json.loads(raw) if isinstance(raw, str) else raw
        key = base64.b16decode(resp['key'].upper())
        raw_content = base64.b16decode(resp['content'].upper())
        cls.check_sign(raw_content, resp['sign'])

        sentinel = object()
        cipher = PKCS1_v1_5.new(GMEI_KEY)
        key1 = cipher.decrypt(key[:256], sentinel)
        key2 = cipher.decrypt(key[-256:], sentinel)
        if key1 is sentinel or key2 is sentinel:
            raise ValueError('Decrypt Error')
        content = ARC4.new(key1+key2).decrypt(raw_content)
        return content

    @classmethod
    def check_sign(cls, content, sign):
        cal_sign = hashlib.md5(content+settings.XYMD5KEY).hexdigest()
        if sign != cal_sign:
            raise ValueError('check sign error')


class Client(object):

    _request = Request('apiBorrower')

    def __init__(self, uid=None):
        self.uid = uid
        global YINGTZ_KEY
        global GMEI_KEY
        if YINGTZ_KEY is None:
            YINGTZ_KEY = RSA.importKey(base64.b64decode(open(settings.YINGTZ_KEY).read()))
            GMEI_KEY = RSA.importKey(open(settings.GMEI_PRIVATE_KEY_PKCS8).read())

    def register(self, name, password, mobile, id_no):
        kwargs = {
            'mobile': mobile,
            'password': password,
            'name': name,
            'idNo': id_no,
            'channel': settings.XYCHANNEL,
            'partnerId': settings.XYPARTNER,
            'idType': settings.ID_TYPE,
            'sendPassword': settings.SEND_PASSWORD
        }
        return self._request.handler('registerAndVerify')(**kwargs)

    def check_bank(self, bank_code, card_no, phone, province, city):
        assert(self.uid)
        kwargs = {
            'uid': self.uid,
            'bank': bank_code,
            'cardNo': card_no,
            'phone': phone,
            'province': province,
            'city': city
        }
        return self._request.handler('checkSecureBank')(**kwargs)

    def bind_bank(self, verified_data, sms_code):
        assert(self.uid)
        kwargs = {
            'uid': self.uid,
            'verifiedData': verified_data,
            'smsCode': sms_code
        }
        return self._request.handler('bindSecureBank')(**kwargs)

    def create_loan(self, amount, periods, extend):
        assert(self.uid)
        kwargs = {
            'uid': self.uid,
            'amount': amount,
            'periods': periods,
            'extendInfo': extend,
            'periodType': settings.PERIOD_TYPE,
            'repayType': settings.REPAY_TYPE,
            'applyTime': int(time.time()),
            'type': settings.UNDERLYING,
        }
        return self._request.handler('createLoan')(**kwargs)

    def loan_audit_detail(self, loan_order_id):
        assert(self.uid)
        kwargs = {
            'uid': self.uid,
            'loanOrderId': loan_order_id,
        }
        return self._request.handler('loanAuditDetail')(**kwargs)

    def loan_detail(self, loan_order_id):
        assert(self.uid)
        kwargs = {
            'uid': int(self.uid),
            'loanOrderId': int(loan_order_id),
        }
        return self._request.handler('loanDetail')(**kwargs)

    def confirm_loan(self, loan_order_id, result):
        assert(self.uid)
        assert(loan_order_id)
        kwargs = {
            'uid': self.uid,
            'loanOrderId': loan_order_id,
            'result': result,
        }
        return self._request.handler('confirmLoan')(**kwargs)

    def loan_replenish(self, loan_order_id, info):
        assert(self.uid)
        assert(loan_order_id)
        kwargs = {
            'uid': self.uid,
            'loanOrderId': loan_order_id,
            'info': info
        }
        return self._request.handler('loanReplenish')(**kwargs)

    def calculate(self, amount, periods):
        assert(isinstance(amount, (int, float, long)))
        assert(isinstance(periods, int))
        kwargs = {
            'amount': amount,
            'monthNumber': periods,
            'repayType': settings.REPAY_TYPE,
            'loanRate': settings.LOAN_RATE,
        }
        return Request('apiTools').handler('calculate')(**kwargs)

    def city(self):
        url = settings.XYBASE_URL.format(subject='apiPublic', endpoint='bankCity')
        content = requests.get(url).content
        return json.loads(content)['data']['list']


def create_borrower_image(partner, borrower, card):
    BorrowerImage.objects.filter(borrower=borrower).update(deleted=True)
    front = BorrowerImage.objects.create(
        partner=partner, borrower=borrower, image_type=ID_IMAGE_TYPE.FRONT,
        url=card['id_card_front'])
    back = BorrowerImage.objects.create(
        partner=partner, borrower=borrower, image_type=ID_IMAGE_TYPE.BACK,
        url=card['id_card_back'])
    hold = BorrowerImage.objects.create(
        partner=partner, borrower=borrower, image_type=ID_IMAGE_TYPE.HANDHELD,
        url=card['id_card_hold'])
    return front, back, hold


def generate_extend_info(borrower, settlement, period):
    info = {'loanApplyNo': settlement.id}

    # other info
    # bank = borrower.binds.exclude(sms_code="").latest('created_time')
    # info['bankAccount'] = bank.card_code
    # info['bankName'] = bank.name

    # person info
    info['user'] = {
        'name': borrower.name,
        'idcard_no': borrower.card_id,
        'district': borrower.district,
        'province': borrower.province,
        'city': borrower.city,
        'birthday': borrower.age,
        'gender': borrower.gender,
        'address': borrower.address,
        'education': borrower.education,
        'phone_mobile': borrower.phone,
        'marry_status': borrower.marriage,
        'province': borrower.province,
        'user_relations': [
            {'name': x.name, 'mobile': x.phone, 'relation_type': x.relationship}
            for x in borrower.relatives.filter(deleted=False)
        ],
    }

    i = borrower.images.filter(deleted=False)
    info['files'] = {
        'id_card_front_url': i.get(image_type=ID_IMAGE_TYPE.FRONT).public_url,
        'id_card_back_url': i.get(image_type=ID_IMAGE_TYPE.BACK).public_url,
        'id_card_hold_url': i.get(image_type=ID_IMAGE_TYPE.HANDHELD).public_url,
    }

    info['pact'] = {
        'argee_loan_contract': 1,
        'argee_consulting_contract': 1,
        'argee_credit_inquiry_authorization': 1,
    }

    # gmei info
    order = settlement.items.first().order
    info['assets'] = {
        'orders': [
            {
                'order_id': order.id,
                'order_create_at': order.created_time.isoformat(),
                'order_price': int(order.service_price*100),
                'shop_name': "",
                'receiver': "",
                'receiver_mobile': '',
                'receiver_province': '',
                'receiver_city': '',
                'receiver_district': '',
                'receiver_address': '',
                'products': [
                    {
                        'p_id': order.service.id,
                        'p_title': order.service.name,
                        'p_price': int(order.service.gengmei_price*100),
                        # 'p_brand': '',
                        'p_brand': 'no brand',
                        'p_url': '',
                        'p_provider': '',
                        'p_address': '',
                    }
                ],
            }
        ]
    }

    info['apply'] = {
        'apply_price': int(settlement.service_price*100),
        'periods': period,
        'city_code': None
    }

    hospital_name = '未知'
    try:
        hospital_name = order.servicesnapshot.hospital.name
    except:
        pass

    info['ext'] = {
        'hospital_name': hospital_name,
        'first_pay_money': 1,
    }
    return info


def confirm_loan(settlement_id):
    """
    通知小赢放款
    """
    settlement = Settlement.objects.get(id=settlement_id)
    installment = settlement.installment
    Client(installment.borrower.third_uid).confirm_loan(
        installment.third_loan_order_id, XIAOYING_CONFIRM_CODE.POSITIVE)
    installment.status = XIAOYING_INSTALLMENT_STATUS.WAIT_LEND
    installment.save()


def xiaoying_refund(order_id):
    """update order/refund status, there's no need to return cash back to client."""
    try:
        refund_order = RefundOrder.objects.get(order_id=order_id)
    except RefundOrder.DoesNotExist:
        return gen(CODES.ORDER_NOT_FOUND)

    is_locked(order_id=order_id, refund_type=REFUND_TYPE.REFUND)

    # 商户同意退款
    # 商户超时操作, 自动默认退款
    # 仲裁同意退款
    if refund_order.status not in (
            REFUND_STATUS.DOCTOR_APPROVE,
            REFUND_STATUS.REFUND_APPLY_SELLER_TIMEOUT,
            REFUND_STATUS.ARBIT_APPROVE):
        raise gen(CODES.ORDER_REFUDN_STATUS_ERROR)

    lock(order_id=order_id, refund_type=REFUND_TYPE.REFUND)
    operator = Person.objects.get(user_id=settings.BOSS)
    refund_order.order.operate(operator, ORDER_OPERATION_TYPE.REFUNDED, ORDER_OPERATION_ROLE.SYSTEM)
    from api.manager import order_manager
    order_manager.send_order_refunded_event(refund_order.order)

    refund_send_notification_and_sms(refund_order.order)
