# coding=utf-8
from __future__ import absolute_import
# Don't import unicode_literals from __future__
import time
import hashlib
import requests
from yattag import Doc
from random import Random
import xml.etree.ElementTree as ElementTree
from django.db import models

from api.models import Settlement
from rpc.tool.log_tool import wechat_pay_logger
from rpc.tool.log_tool import wechat_refund_logger
from pay.manager import WechatSettlementManager

def get_cdata(root, key):
    try:
        text = root.iter(key).next().text
    except StopIteration:
        text = ''
    return text

class XML(object):
    def __init__(self, text):
        self.root = ElementTree.fromstring(text)

    def cdata(self, key):
        return get_cdata(self.root, key)


PREPAY_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"
REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund"
ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery"


def random_str(randomlength=32):
    str = ''
    chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
    length = len(chars) - 1
    random = Random()
    for i in range(randomlength):
        str += chars[random.randint(0, length)]
    return str


def utf8(source):
    if isinstance(source, unicode):
        return source.encode("utf8")
    else:
        return source


class WechatOrder(models.Model):
    class Meta:
        verbose_name = u'微信订单'
        verbose_name_plural = u'微信订单'
        db_table = 'pay_wechatorder'
        app_label = 'pay'

    order_no = models.CharField(max_length=12, verbose_name=u'订单号', primary_key=True)
    appid = models.CharField(max_length=32)
    bank_type = models.CharField(max_length=32)
    cash_fee = models.IntegerField(default=0)
    fee_type = models.CharField(max_length=16)
    is_subscribe = models.CharField(max_length=4)
    mch_id = models.CharField(max_length=32)
    nonce_str = models.CharField(max_length=32, verbose_name=u'随机字符串')
    openid = models.CharField(max_length=32)
    out_trade_no = models.CharField(max_length=32, verbose_name=u'商户订单号')
    result_code = models.CharField(max_length=32)
    sign = models.CharField(max_length=32, verbose_name=u"签名")
    total_fee = models.IntegerField(default=0, verbose_name=u"网关总金额")
    time_end = models.CharField(max_length=32)
    trade_type = models.CharField(max_length=16)
    transaction_id = models.CharField(max_length=32)
    created_time = models.DateTimeField(auto_now_add=True, help_text=u"创建时间")
    # 暂时保留, 将老订单中的此字段迁移后,删除
    out_refund_no = models.CharField(max_length=32, verbose_name=u"用户退款单号")

class WechatSettlement(models.Model):
    class Meta:
        verbose_name = u'微信结算单'
        db_table = u'pay_wechatsettlement'
        app_label = u'pay'

    settlement = models.ForeignKey(Settlement, verbose_name=u'关联的结算单')
    appid = models.CharField(max_length=32)
    bank_type = models.CharField(max_length=32)
    cash_fee = models.IntegerField(default=0)
    fee_type = models.CharField(max_length=16)
    is_subscribe = models.CharField(max_length=4)
    mch_id = models.CharField(max_length=32)
    nonce_str = models.CharField(max_length=32, verbose_name=u"随机字符串")
    openid = models.CharField(max_length=32)
    out_trade_no = models.CharField(max_length=32, verbose_name=u"商户订单号")
    result_code = models.CharField(max_length=32)
    sign = models.CharField(max_length=32, verbose_name=u"签名")
    total_fee = models.FloatField(default=0, verbose_name=u"网关总金额") # 换成元!!!
    time_end = models.CharField(max_length=32)
    trade_type = models.CharField(max_length=16)
    transaction_id = models.CharField(max_length=32)
    created_time = models.DateTimeField(auto_now_add=True, help_text=u"创建时间")

    objects = WechatSettlementManager()

class WechatRefund(models.Model):
    class Meta:
        verbose_name = u'微信退款'
        verbose_name_plural = u'微信退款'
        db_table = u'pay_wechatrefund'
        app_label = u'pay'

    # 所有平台均有
    order_no = models.CharField(max_length=12, verbose_name='订单号')
    refund_fee = models.IntegerField(default=0)
    cash_refund_fee = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=u"创建时间")
    # 微信平台特有
    refund_id = models.CharField(max_length=32, default='')
    nonce_str = models.CharField(max_length=32, verbose_name=u"随机字符串", default='')
    out_trade_no = models.CharField(max_length=32, verbose_name=u"商户订单号", default='')
    out_refund_no = models.CharField(max_length=32, verbose_name=u"用户退款单号", default='')
    err_code = models.CharField(max_length=30, verbose_name=u'微信退款错误号', default='')


class Wechat(object):
    def __init__(self, app_id, mch_id, key):
        self.app_id = app_id
        self.mch_id = mch_id
        self.key = key

    def set_cert(self, key, cert):
        self.apiclient_key = key
        self.apiclient_cert = cert

    def unifiedorder(self, settlement_id, ip, fee, body, detail, openid="", notify_url="http://127.0.0.1/pay/wechat/notify"):
        settlement_id, ip, body, detail = map(utf8, [settlement_id, ip, body, detail])
        url = PREPAY_URL
        doc, tag, text = Doc().tagtext()
        self.noncestr = random_str()
        data = {
            "appid": self.app_id,
            "mch_id": self.mch_id,
            "nonce_str": self.noncestr,
            "body": body,
            "detail": detail,
            "out_trade_no": settlement_id,
            "total_fee": str(int(fee)),
            "spbill_create_ip": ip,
            "notify_url": notify_url,
            "trade_type": "JSAPI",
            "openid": openid.encode("ISO-8859-1"),
        }
        sign = self.sign(data)
        with tag("xml"):
            with tag("appid"):
                text(data["appid"])
            with tag("mch_id"):
                text(data["mch_id"])
            with tag("nonce_str"):
                text(data["nonce_str"])
            with tag("body"):
                text(data["body"])
            with tag("detail"):
                text(data["detail"])
            with tag("out_trade_no"):
                text(data["out_trade_no"])
            with tag("total_fee"):
                text(data["total_fee"])
            with tag("spbill_create_ip"):
                text(data["spbill_create_ip"])
            with tag("notify_url"):
                text(data["notify_url"])
            with tag("trade_type"):
                text(data["trade_type"])
            with tag("openid"):
                text(data["openid"])
            with tag("sign"):
                text(sign)
        xml_data = doc.getvalue()
        r = requests.post(url, data=xml_data)
        text = r.text.encode("ISO-8859-1")
        wechat_pay_logger.info("settlement_id: %s" % settlement_id + "\nTencent return: \n" + text)
        root = XML(text)
        prepay_id = root.cdata("prepay_id")
        noncestr = root.cdata("nonce_str")
        timeStamp = str(int(time.time()))

        data = {
            'appId': self.app_id,
            'timeStamp': timeStamp,
            'nonceStr': noncestr,
            'package': 'prepay_id=%s' % prepay_id,
            'signType': 'MD5',
        }
        data['paySign'] = self.sign(data)
        return data

    def unifiedorder_for_app(self, out_trade_no, ip, fee, body, detail, notify_url="", attach=""):
        out_trade_no, ip, body, detail = map(utf8, [out_trade_no, ip, body, detail])
        url = PREPAY_URL
        doc, tag, text = Doc().tagtext()
        self.noncestr = random_str()
        data = {
            "appid": self.app_id,
            "mch_id": self.mch_id,
            "nonce_str": self.noncestr,
            "body": body,
            "notify_url": notify_url,
            "out_trade_no": out_trade_no,
            "spbill_create_ip": ip,
            "total_fee": str(int(fee)),
            "trade_type": "APP",
            "detail": detail,
        }

        if attach:
            data['attach'] = attach

        sign = self.sign(data)

        with tag("xml"):
            with tag("appid"):
                text(data["appid"])
            with tag('attach'):
                text(attach)
            with tag("body"):
                text(data["body"])
            with tag("detail"):
                text(detail)
            with tag("mch_id"):
                text(data["mch_id"])
            with tag("nonce_str"):
                text(data["nonce_str"])
            with tag("notify_url"):
                text(data["notify_url"])
            with tag("out_trade_no"):
                text(data["out_trade_no"])
            with tag("spbill_create_ip"):
                text(data["spbill_create_ip"])
            with tag("total_fee"):
                text(data["total_fee"])
            with tag("trade_type"):
                text(data["trade_type"])
            with tag("sign"):
                text(sign)

        xml_data = doc.getvalue()
        r = requests.post(url, data=xml_data)
        text = r.text.encode("ISO-8859-1")
        root = XML(text)
        app_id = root.cdata("appid")
        prepay_id = root.cdata("prepay_id")
        noncestr = root.cdata("nonce_str")
        sign = root.cdata("sign")
        package = "prepay_id=%s" % prepay_id
        package = "Sign=WXPay"
        timestamp = str(int(time.time()))
        sign_data = {
            "appid": app_id,
            "noncestr": noncestr,
            "package": package,
            "prepayid": prepay_id,
            "partnerid": self.mch_id,
            "timestamp": timestamp,
        }
        sign_data["sign"] = self.sign(sign_data)
        return sign_data

    def sign(self, params):
        keys = params.keys()
        keys.sort()
        joined_array = ['%s=%s' % (key, params[key]) for key in keys]
        joined_string = '&'.join(joined_array)
        joined_string += '&key=' + self.key
        sign = hashlib.md5(joined_string).hexdigest().upper()
        return sign

    def refund(self, transaction_id, out_refund_no, total_fee, refund_fee):
        url = REFUND_URL
        noncestr = random_str()
        data = {
            "appid": self.app_id,
            "mch_id": self.mch_id,
            "nonce_str": noncestr,
            "transaction_id": transaction_id,
            "out_refund_no": out_refund_no,
            "total_fee": str(int(total_fee)),
            "refund_fee": str(int(refund_fee)),
            "op_user_id": self.mch_id
        }
        sign = self.sign(data)
        doc, tag, text = Doc().tagtext()
        with tag("xml"):
            with tag("appid"):
                text(data["appid"])
            with tag("mch_id"):
                text(data["mch_id"])
            with tag("nonce_str"):
                text(data["nonce_str"])
            with tag("sign"):
                text(sign)
            with tag("transaction_id"):
                text(data["transaction_id"])
            with tag("out_refund_no"):
                text(data["out_refund_no"])
            with tag("total_fee"):
                text(data["total_fee"])
            with tag("refund_fee"):
                text(data["refund_fee"])
            with tag("op_user_id"):
                text(data["op_user_id"])
        xml_data = doc.getvalue()
        wechat_refund_logger.info(xml_data)

        r = requests.post(url, data=xml_data, cert=(self.apiclient_cert, self.apiclient_key))
        text = r.text.encode("ISO-8859-1")
        wechat_refund_logger.info(text)
        root = XML(text)
        return_code = root.cdata("result_code")

        retry_count = 2
        data = {}
        while retry_count > 0:

            if return_code == "SUCCESS":
                data["refund_id"] = root.cdata("refund_id")
                data["nonce_str"] = root.cdata("nonce_str")
                data["refund_fee"] = root.cdata("refund_fee")
                data["cash_refund_fee"] = root.cdata("cash_refund_fee")
                data["out_trade_no"] = root.cdata("out_trade_no")
                data["out_refund_no"] = root.cdata("out_refund_no")
                return data

            elif root.cdata("err_code") == 'SYSTEMERROR':
                retry_count -= 1
                # 已知微信系统错误,按照官方要求重新发起
                continue
            # 没成功也不是微信系统错误, 直接退出
            else:
                data["err_code"] = root.cdata("err_code") or root.cdata("return_msg")
                data["err_code_des"] = root.cdata("err_code_des")
                return data
        # must be systemerror
        data["err_code"] = root.cdata("err_code") or root.cdata("return_msg")
        data["err_code_des"] = root.cdata("err_code_des")
        return data

    def order_query(self, transaction_id):
        url = ORDER_QUERY_URL
        noncestr = random_str()
        data = {
            "appid": self.app_id,
            "mch_id": self.mch_id,
            "nonce_str": noncestr,
            "transaction_id": transaction_id,
        }
        sign = self.sign(data)
        doc, tag, text = Doc().tagtext()
        with tag("xml"):
            with tag("appid"):
                text(data["appid"])
            with tag("mch_id"):
                text(data["mch_id"])
            with tag("nonce_str"):
                text(data["nonce_str"])
            with tag("sign"):
                text(sign)
            with tag("transaction_id"):
                text(data["transaction_id"])
        xml_data = doc.getvalue()
        r = requests.post(url, data=xml_data)
        text = r.text.encode("ISO-8859-1")
        root = XML(text)
        result_code = root.cdata("result_code")

        assert result_code == "SUCCESS"
        data = {}
        data["trade_state"] = root.cdata("trade_state")
        return data

    def apitest_getsignkey(self):
        url = "https://apitest.mch.weixin.qq.com/sandboxnew/pay/getsignkey"
        noncestr = random_str()
        data = {
            "mch_id": self.mch_id,
            "nonce_str": noncestr,
        }
        sign = self.sign(data)
        doc, tag, text = Doc().tagtext()
        with tag("xml"):
            with tag("mch_id"):
                text(data["mch_id"])
            with tag("nonce_str"):
                text(data["nonce_str"])
            with tag("sign"):
                text(sign)
        xml_data = doc.getvalue()
        r = requests.post(url, data=xml_data)
        text = r.text.encode("ISO-8859-1")
        print text
        root = XML(text)
        result_code = root.cdata("return_code")

        return result_code == "SUCCESS"
