# coding=utf-8
from __future__ import absolute_import, unicode_literals

import json
import datetime
import dateutil.parser

from django.db import transaction
from django.conf import settings
from django.http import HttpResponse

from helios.rpc.exceptions import RPCFaultException
from gm_types.gaia import PAYMENT_CHANNEL, ORDER_STATUS, ORDER_OPERATION_TYPE, ORDER_OPERATION_ROLE
from gm_types.pay import CHANNEL, METHOD, APP_TYPE

from api.tool.user_tool import get_user_from_context

from api.tool.log_tool import wechat_pay_logger
from api.tool.log_tool import alipay_pay_logger
from api.tool.log_tool import apple_pay_logger

from pay.tool.alipay_tool import AlipayAppPayTools
from pay.models import ApplePay
from pay.models import Wechat
from pay.tool import apple_tool, new_order_tool
from pay.tool import purchase_tool
from pay.tool import wechat_pay_tool

from rpc.tool.error_code import CODES
from rpc.tool.error_code import gen
from rpc.decorators import bind, bind_context
from rpc.tool.log_tool import logging_exception
from rpc.exceptions import GaiaRPCFaultException
from rpc.all import get_rpc_remote_invoker

from sms.utils.smsfactory import send_sms

from maidan.models import MaidanPayment, MaidanOrder
from api.models.settlement import SETTLEMENT_STATUS

from services.doctor_notify_service import DoctorNotifyService
from services.custom_phone_service import PhoneService

MAIDAN = APP_TYPE.MAIDAN

# 这个key会被使用在url中
_channel_key_apple = str("apple")
_channel_key_alipay = str("alipay")
_channel_key_wechat = str("wechat")
_channel_key_installment = str("installment")

_pay_tools_key_maidan_wechat_pay_notify_url = "_pay_tools_key_maidan_wechat_pay_notify_url"

_PayTools = {}


def _init_PayTools():
    notify_url_base = settings.MAIDAN_PAY_NOTIFY_URL_BASE

    tools = AlipayAppPayTools()
    tools.use_old_conifg()
    # todo: 人被杀就会死
    tools.notify_url = notify_url_base + _channel_key_alipay
    _PayTools[_channel_key_alipay] = tools

    apple_pay = ApplePay()
    # todo: 人被杀就会死
    apple_pay.pay_notify_url = notify_url_base + _channel_key_apple
    _PayTools[_channel_key_apple] = apple_pay

    wechat = wechat_pay_tool.get_wechat_obj_by_name(wechat_pay_tool.WECHAT_OBJ_NAME_FOR_APP)

    _PayTools[_pay_tools_key_maidan_wechat_pay_notify_url] = notify_url_base + _channel_key_wechat
    _PayTools[_channel_key_wechat] = wechat


def _is_my_order(user, order_id):
    from maidan.models import MaidanOrder
    try:
        order = MaidanOrder.objects.get(pk=order_id)
        if order.user != user:
            raise gen(CODES.NO_PERMISSION)
    except MaidanOrder.DoesNotExist:
        raise gen(CODES.SETTLEMENT_DOES_NOT_EXIST)
    else:
        return order


def alipay_prepay(ctx, order_id, ip):
    """
    支付请求参数参考
        https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.WlyfbF&treeId=193&articleId=105465&docType=1
    """
    alipay_pay_logger.info("maidan/pay/prepay/alipay： order_id=%s, ip=%s", order_id, ip)
    user = get_user_from_context(ctx)

    if not _PayTools:
        _init_PayTools()

    order = _is_my_order(user, order_id)

    payment_id = order.payment_id
    total_amount = str(order.payment_cent / 100.0)

    biz_content = {
        "out_trade_no": payment_id,
        "subject": order.maidan_name[:256],
        "body": order.maidan_name[:128],
        "total_amount": total_amount,
    }

    order_info = _PayTools[_channel_key_alipay].build_order_info_str_v1(
        out_trade_no=biz_content["out_trade_no"],
        subject=biz_content["subject"],
        body=biz_content["body"],
        total_amount=biz_content["total_amount"],
    )
    # 在之前的接口是直接返回order_info然后backend再包装一层order_info的，这个版本改为由这里完成
    result = {"order_info": order_info}

    return result


def apple_prepay(ctx, order_id, ip):
    apple_pay_logger.info("maidan/pay/prepay/apple： order_id=%s, ip=%s", order_id, ip)

    user = get_user_from_context(ctx)

    if not _PayTools:
        _init_PayTools()

    order = _is_my_order(user, order_id)

    payment_id = order.payment_id
    total_amount = str(order.payment_cent / 100.0)

    dt_order = order.created_time.strftime('%Y%m%d%H%M%S')
    name_goods = order.maidan_name[:40]
    info_order = order.maidan_name[:255]
    money_order = total_amount
    user_id = user.person.id

    apple = _PayTools[_channel_key_apple]
    params = apple.pay_params(
        user_id=user_id,
        no_order=payment_id,
        dt_order=dt_order,
        name_goods=name_goods,
        info_order=info_order,
        money_order=money_order,
    )
    return params


def wechat_prepay(ctx, order_id, ip):
    wechat_pay_logger.info("maidan/pay/prepay/wechat： order_id=%s, ip=%s", order_id, ip)

    user = get_user_from_context(ctx)

    if not _PayTools:
        _init_PayTools()

    order = _is_my_order(user, order_id)

    payment_id = order.payment_id
    total_amount = str(order.payment_cent)

    notify_url = _PayTools[_pay_tools_key_maidan_wechat_pay_notify_url]

    params = _PayTools[_channel_key_wechat].unifiedorder_for_app(
        out_trade_no=payment_id,
        ip=ip,
        fee=total_amount,
        body=u'更美',
        detail=order.maidan_name,
        notify_url=notify_url,
    )

    return params


_channel_prepay_route = {
    _channel_key_apple: apple_prepay,
    _channel_key_alipay: alipay_prepay,
    _channel_key_wechat: wechat_prepay,
}


@bind_context('maidan/pay/old_prepay', login_required=True)
def pay_prepay(ctx, channel, order_id, ip):
    func = _channel_prepay_route.get(channel)

    if not func:
        return gen(CODES.UNKNOWN_ERROR)

    return func(ctx, order_id, ip)

sign_to_channel = {
    _channel_key_apple: CHANNEL.LIANLIAN,
    _channel_key_alipay: CHANNEL.ALIPAY,
    _channel_key_wechat: CHANNEL.WECHAT,
}
sign_to_method = {
    _channel_key_apple: METHOD.APP,
    _channel_key_alipay: METHOD.ALI_OLD_APP,
    _channel_key_wechat: METHOD.APP,
}


@bind_context('maidan/pay/prepay', login_required=True)
def new_pay_prepay(ctx, channel, order_id, ip):
    with transaction.atomic():

        user = get_user_from_context(ctx)
        channel_sign = channel
        channel = sign_to_channel.get(channel_sign, None)
        method = sign_to_method.get(channel_sign, None)
        if not channel or not method:
            return gen(CODES.UNKNOWN_ERROR)

        try:
            order = MaidanOrder.objects.select_for_update().get(pk=order_id)
            if order.user != user:
                raise gen(CODES.NO_PERMISSION)
            payment = MaidanPayment.objects.get(id=order.payment_id)
        except (MaidanOrder.DoesNotExist, MaidanPayment.DoesNotExist):
            raise gen(CODES.SETTLEMENT_DOES_NOT_EXIST)

        # 校验状态
        if payment.status == SETTLEMENT_STATUS.PAID:
            raise gen(CODES.SETTLEMENT_PAID)
        elif payment.status == SETTLEMENT_STATUS.CANCEL:
            raise gen(CODES.SETTLEMENT_CANCELED)

        title = u"更美--{}".format(order.maidan_name)
        prv_res= ctx.rpc_remote['cadus/unify/get_or_create_payment'](app_type=MAIDAN,
                                                                     app_order_id=payment.id,
                                                                     app_notify_url="maidan/pay/union_notify",
                                                                     total_fee=str(payment.total_amount_cent),
                                                                     title=title).unwrap()
        extra = {}
        if channel == CHANNEL.WECHAT:
            extra["ip"] = ip
        elif channel == CHANNEL.LIANLIAN:
            extra['user_id'] = user.id
            extra['info_order'] = order.maidan_name[:255]
        try:
            result = ctx.rpc_remote['cadus/unify/prepay'](app_type=MAIDAN,
                                                          app_order_id=payment.id,
                                                          channel=channel,
                                                          method=method,
                                                          extra=extra).unwrap()
        except RPCFaultException:
            logging_exception()
            return gen(CODES.ORDER_PAY_STATUS_ERROR)
        if channel == CHANNEL.WECHAT:
            return result
        elif channel == CHANNEL.ALIPAY:
            return {"order_info": result}
        else:
            return result


@bind_context('maidan/pay/union_notify')
def new_union_notify(ctx, data):
    """data
        'app_type': payment_order.app_type,
        'app_order_id': payment_order.app_order_id,
        'out_trade_no': payment_order.the_success_record.out_trade_no

        'channel': payment_order.channel,
        'method': payment_order.method,
        'total_fee': payment_order.total_fee,
        'status': payment_order.status,

        'paid_time': payment_order.paid_time.isoformat(),
        """
    with transaction.atomic():
        try:
            payment = MaidanPayment.objects.get(pk=data['app_order_id'])
            order = MaidanOrder.objects.select_for_update().get(id=payment.maidan_order_id)
        except (MaidanPayment.DoesNotExist, MaidanOrder.DoesNotExist):
            return gen(CODES.SETTLEMENT_DOES_NOT_EXIST)

        if data['total_fee'] != payment.total_amount_cent:
            raise Exception('通知金额不一致')
        if order.status not in (ORDER_STATUS.PAYING, ORDER_STATUS.NOT_PAID):
            raise Exception("买单状态异常, 已经不是等待付款中了")
        if payment.status == SETTLEMENT_STATUS.PAID:  # 重复通知
            return "SUCCESS"

        payment.out_trade_no = data['out_trade_no']

        channel = new_order_tool.get_channel_to_payment_channel(data['channel'])

        payment.channel = channel
        payment.payment_time = dateutil.parser.parse(data['paid_time'])
        payment.status = SETTLEMENT_STATUS.PAID
        payment.channel_transaction_id = data['transaction_id']
        payment.notify_info = json.dumps(data)
        payment.save()

        order.payment_channel = payment.channel
        order.payment_time = payment.payment_time
        order.operate(order.user.person, ORDER_OPERATION_TYPE.PAY, ORDER_OPERATION_ROLE.USER)
    after_settlement_paid(order)
    return "SUCCESS"


def after_settlement_paid(settlement):
    try:
        send_sms(
            settlement.doctor.phone,
            29,
            [
                {'doctor_name': settlement.doctor.name},
                {'time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')},
                {'order_id': settlement.id},
            ]
        )

        ext = settlement.doctor.phone_ext or '6666'
        send_sms(
            settlement.user.person.phone,
            36,
            [
                {'service': settlement.maidan_name},
                {'order_id': settlement.id},
                {'kf_phone': PhoneService.get_phone_prefix(ext)},
                {'ext': ext},
            ]
        )
    except:
        logging_exception()


def settlement_paid_with_log(ctx, order, settlement,
                             payment_channel, channel_transaction_id, pay_time, notify_info):
    order.payment_channel = payment_channel
    order.payment_time = pay_time
    order.operate(order.user.person, ORDER_OPERATION_TYPE.PAY, ORDER_OPERATION_ROLE.USER)

    settlement.channel = payment_channel
    settlement.payment_time = pay_time

    settlement.status = SETTLEMENT_STATUS.PAID
    settlement.channel_transaction_id = channel_transaction_id
    settlement.notify_info = notify_info

    settlement.save()

    ctx.logger.app(settlement_paid={
        'settlement_id': settlement.id,
    })


def _get_order_by_payment_id(payment_id):
    from maidan.models import MaidanOrder
    try:
        order = MaidanOrder.objects.get(payment_id=payment_id)
    except MaidanOrder.DoesNotExist:
        raise gen(CODES.ORDER_NOT_FOUND)
    else:
        return order


def _is_callback_payment_match(total_amount_cent, notiry_number_int):
    if int(total_amount_cent) != int(notiry_number_int):
        return gen(CODES.ORDER_PAY_CHECK_ERROR)


def apple_notify(ctx, trade_data, ip):
    '''
    apple pay支付
    {
    "bank_code":"98000008",
    "dt_order":"20160519183431",
    "info_order":"20160519183431",
    "money_order":"0.01",
    "no_order":"LL20160519183431",
    "oid_partner":"201603031000747503",
    "oid_paybill":"2016051912516307",
    "pay_type":"R",
    "result_pay":"SUCCESS",
    "settle_date":"20160519",
    "sign":"9d9df70a2085b1153e6bc57ebea715e3",
    "sign_type":"MD5"
    }
    '''
    apple_pay_logger.info("maidan/pay/apple/notify, trade_data: " + json.dumps(trade_data) + " ip: " + ip)

    try:
        settlement = MaidanPayment.objects.get(pk=trade_data['no_order'])
    except MaidanPayment.DoesNotExist:
        return gen(CODES.SETTLEMENT_DOES_NOT_EXIST)

    trade_status = trade_data.get('result_pay', None)

    if (trade_status == 'SUCCESS' and settlement.status in (
            SETTLEMENT_STATUS.PAYING,
            SETTLEMENT_STATUS.NOT_PAID,
            SETTLEMENT_STATUS.CANCEL
    )):
        # TODO: 安全校验
        apple_tool.check_is_from_apple(trade_data)

        total_fee = trade_data.get('money_order', 0)

        total_amount_cent = settlement.total_amount_cent
        total_fee_cent = int(float(total_fee) * 100)

        _is_callback_payment_match(total_amount_cent, total_fee_cent)

        with transaction.atomic():
            order = _get_order_by_payment_id(settlement.id)
            # 连连的apple pay 没有精确的支付时间, 以接收到回调时间为准
            # 但是如果不是第一次回调成功的情况, 会不准
            pay_time = datetime.datetime.now()
            channel_transaction_id = trade_data.get('oid_paybill', "")
            settlement_paid_with_log(
                ctx=ctx,
                order=order,
                settlement=settlement,
                payment_channel=PAYMENT_CHANNEL.APPLEPAY,
                channel_transaction_id=channel_transaction_id,
                pay_time=pay_time,
                notify_info=json.dumps(trade_data),
            )
            after_settlement_paid(order)

            return gen(CODES.SUCCESS)
    return gen(CODES.UNKNOWN_ERROR)


def alipay_notify(ctx, trade_data, ip):
    """settlement callback after pay(alipay).
    客户端,backedn 支付宝异步通知
    args:
        trade_data:
        {
            "seller_email": "zhengxuan@wanmeizhensuo.com",
             "refund_status": "REFUND_SUCCESS",
             "gmt_refund": "2015-07-08 11:26:04",
             "sign": "Orry7Mv5JvPeg9osthrP0Anyezk3bnPwX9D4h8xquHBvi4NGcvHqntMxE87ZWCyktVcGRRXSKlM8saACm9flN1QI3454T
                      tKsiY3R7qBU+Rq7j2Rf9zwLpmGI050MFPaAM+vnPcc7OwSF5qa5l/md6hnXcTnoQiL9mPks0AWNoPA=",
             "subject": "\u3010\u5317\u4eac@\u674e\u7acb\u4ef2\u3011\u9f3b\u7efc\u5408",
             "is_total_fee_adjust": "N",
             "gmt_create": "2015-07-08 02:07:53",
             "out_trade_no": "9781350875",
             "sign_type": "RSA",
             "body": "\u3010\u5317\u4eac@\u674e\u7acb\u4ef2\u3011\u9f3b\u7efc\u5408",
             "price": "300.00",
             "buyer_email": "1529092949@qq.com",
             "discount": "0.00",
             "trade_status": "TRADE_SUCCESS",
             "gmt_payment": "2015-07-08 02:07:54",
             "trade_no": "2015070800001000370056067210",
             "seller_id": "2088211612339882",
             "use_coupon": "N",
             "payment_type": "1",
             "total_fee": "300.00",
             "notify_time": "2015-07-08 02:07:54",
             "buyer_id": "2088802950074373",
             "notify_id": "16b8736a64d9d466f8dc6be2bdf9b09342",
             "notify_type": "trade_status_sync",
             "quantity": "1"
         }
    """
    alipay_pay_logger.info(json.dumps(trade_data) + " ip: " + ip)

    if not _PayTools:
        _init_PayTools()

    _oldAlipayAppPayTools = _PayTools[_channel_key_alipay]

    valid = _oldAlipayAppPayTools.verify_alipay_notify_sign(trade_data)

    if not valid:
        alipay_pay_logger.error("verify_alipay_notify_sign_fail. trade_data:" + json.dumps(trade_data))
        raise gen(CODES.VERIFY_FAILED)

    param_notify_id = trade_data.get("notify_id", None)
    param_seller_id = trade_data.get("seller_id", None)

    seller_id_valid = _oldAlipayAppPayTools.verify_seller_id(param_seller_id)
    if not seller_id_valid:
        alipay_pay_logger.error("verify_alipay_notify_seller_id_fail. trade_data:" + json.dumps(trade_data))
        raise gen(CODES.VERIFY_FAILED)

    try:
        settlement = MaidanPayment.objects.get(pk=trade_data['out_trade_no'])
    except MaidanPayment.DoesNotExist:
        alipay_pay_logger.error(
            "alipay_notify_fail. CODES.SETTLEMENT_DOES_NOT_EXIST. trade_data:" + json.dumps(trade_data))
        return gen(CODES.SETTLEMENT_DOES_NOT_EXIST)

    trade_status = trade_data.get("trade_status", None)
    refund_status = trade_data.get("refund_status", None)

    if (trade_status == 'TRADE_SUCCESS' and
                settlement.status in (SETTLEMENT_STATUS.PAYING, SETTLEMENT_STATUS.NOT_PAID, SETTLEMENT_STATUS.CANCEL)):
        # 支付宝付款成功, 更新订单状态, 并存储支付信息

        alipay_pay_logger.info("alipay_notify_start_update_status. trade_data_notify_id:" + param_notify_id)

        total_amount_cent = settlement.total_amount_cent
        total_fee_cent = int(float(trade_data['total_fee']) * 100)

        _is_callback_payment_match(total_amount_cent, total_fee_cent)

        if not purchase_tool.check_from_alipay(trade_data['notify_id']):
            alipay_pay_logger.error("verify_alipay_notify_notify_id_fail. trade_data:" + json.dumps(trade_data))
            raise gen(CODES.VERIFY_FAILED)

        alipay_pay_logger.info("alipay_notify_begin_atomic. trade_data_notify_id:" + param_notify_id)

        with transaction.atomic():
            order = _get_order_by_payment_id(settlement.id)
            pay_time = trade_data["gmt_payment"]
            channel_transaction_id = trade_data["trade_no"]
            settlement_paid_with_log(
                ctx=ctx,
                order=order,
                settlement=settlement,
                payment_channel=PAYMENT_CHANNEL.ALIPAY,
                channel_transaction_id=channel_transaction_id,
                pay_time=pay_time,
                notify_info=json.dumps(trade_data),
            )

            alipay_pay_logger.info("alipay_notify_begin_after_settlement_paid. trade_data_notify_id:" + param_notify_id)
            after_settlement_paid(order)

        alipay_pay_logger.info("alipay_notify_success. trade_data_notify_id:" + param_notify_id)
        return gen(CODES.SUCCESS)

    elif trade_status != 'TRADE_SUCCESS':
        # 其余逻辑(退款等)发给了支付接口不处理, 直接返回正常
        alipay_pay_logger.info("alipay_notify_success. trade_data_notify_id:" + param_notify_id)
        return gen(CODES.SUCCESS)

    if 'REFUND_SUCCESS' == refund_status:
        alipay_pay_logger.info("alipay_notify_success. trade_data_notify_id:" + param_notify_id)
        return gen(CODES.SUCCESS)

    alipay_pay_logger.error("alipay_notify_fail. CODES.UNKNOWN_ERROR. trade_data:" + json.dumps(trade_data))
    return gen(CODES.UNKNOWN_ERROR)


def wechat_notify(ctx, trade_data, ip):
    """settlement wechat pay callback.

    args:trade_data
    {
        'appid': 'wx4326da3ad2429149',
        'bank_type': 'CFT',
        'cash_fee': '25600',
        'fee_type': 'CNY',
        'is_subscribe': 'N',
        'mch_id': '1227203101',
        'nonce_str': '1fvra5776b0h1ccv0w7f97vryvnpbjj5',
        'openid': 'oIqEAs7IJNdOuWUFcDrTDDeCD0v4',
        'out_trade_no': '1988193789za453kea',
        'result_code': 'SUCCESS',
        'return_code': 'SUCCESS',
        'sign': '789BD8BAC921441F96E643A160D027E9',
        'time_end': '20150929130625',
        'total_fee': '25600',
        'trade_type': 'APP',
        'transaction_id': '1003250278201509291031209879'
    }
    """
    wechat_pay_logger.info(json.dumps(trade_data) + " ip: " + ip)

    if trade_data['return_code'] != "SUCCESS":
        return gen(CODES.SUCCESS)

    out_trade_no_raw = trade_data['out_trade_no']

    # 过渡代码，因为之前生成的no是买单结算单16位+8位随机字符，等到大概2017年6月（？）就可以去掉这个部分的处理了
    out_trade_no = out_trade_no_raw[:-8] if len(out_trade_no_raw) == 24 else out_trade_no_raw

    try:
        settlement = MaidanPayment.objects.get(pk=out_trade_no)
    except MaidanPayment.DoesNotExist:
        return gen(CODES.SETTLEMENT_DOES_NOT_EXIST)

    total_amount_cent = settlement.total_amount_cent
    total_fee_cent = int(float(trade_data['total_fee']))

    _is_callback_payment_match(total_amount_cent, total_fee_cent)

    # with transaction.atomic():
    #     can_cancled_settlement_2_paid(settlement)
    #     WechatSettlement.objects.create_record(settlement, trade_data)
    #     settlement_paid_with_log(
    #         ctx=ctx,
    #         settlement=settlement,
    #         payment_channel=PAYMENT_CHANNEL.WECHAT,
    #         trade_data=trade_data,
    #     )
    #     after_settlement_paid(settlement)

    with transaction.atomic():
        order = _get_order_by_payment_id(settlement.id)
        pay_time = datetime.datetime.strptime(trade_data["time_end"], "%Y%m%d%H%M%S")
        channel_transaction_id = trade_data["transaction_id"]

        settlement_paid_with_log(
            ctx=ctx,
            order=order,
            settlement=settlement,
            payment_channel=PAYMENT_CHANNEL.WECHAT,
            channel_transaction_id=channel_transaction_id,
            pay_time=pay_time,
            notify_info=json.dumps(trade_data),
        )

        after_settlement_paid(order)

    return gen(CODES.SUCCESS)


def installment_notify(ctx, trade_data, ip):
    out_trade_no = trade_data['transaction_id'][3:]
    try:
        settlement = MaidanPayment.objects.get(pk=MaidanOrder.objects.get(pk=out_trade_no).payment_id)
    except (MaidanPayment.DoesNotExist, MaidanOrder.DoesNotExist):
        logging_exception()
        return
    order = _get_order_by_payment_id(settlement.id)
    if order.status in [ORDER_STATUS.CANCEL, ORDER_STATUS.PAID]:
        get_rpc_remote_invoker()['plutus/installment/user/abandon'](
            person_id=order.user.person.id.hex,
            order_id='md_{}'.format(order.id),
        ).unwrap()
        return
    else:
        get_rpc_remote_invoker()['plutus/installment/user/confirm'](
            person_id=order.user.person.id.hex,
            order_id='md_{}'.format(order.id),
        ).unwrap()
    with transaction.atomic():
        pay_time = datetime.datetime.now()
        channel_transaction_id = trade_data["transaction_id"]
        settlement_paid_with_log(
            ctx=ctx,
            order=order,
            settlement=settlement,
            payment_channel=PAYMENT_CHANNEL.INSTALLMENT,
            channel_transaction_id=channel_transaction_id,
            pay_time=pay_time,
            notify_info=json.dumps(trade_data),
        )
        after_settlement_paid(order)

    return gen(CODES.SUCCESS)


_channel_notify_route = {
    _channel_key_apple: apple_notify,
    _channel_key_alipay: alipay_notify,
    _channel_key_wechat: wechat_notify,
    _channel_key_installment: installment_notify,
}


@bind_context('maidan/pay/notify')
def pay_notify(ctx, channel, trade_data, ip):
    func = _channel_notify_route.get(channel)

    if not func:
        return gen(CODES.UNKNOWN_ERROR)

    return func(ctx, trade_data, ip)
