# coding=utf-8

import datetime
import random
import time
import json

from django.conf import settings

from helios.rpc.exceptions import RPCFaultException
from gm_types.trade import SETTLEMENT_PAY_MODE, INSURANCE_TYPE
from gm_types.gaia import POINTS_TYPE, GROUPBUY_STATUS
from gm_types.msg import CONVERSATION_TYPE, MESSAGE_TYPE

import point
from api.models import Order
from api.tool.notification_tool import send_notification
from api.models import Shopcart, Doctor
from api.tool.log_tool import (
    logging_exception,
    momo_stat_logger,
)

from api.models import PAYMENT_CHANNEL
from api.models import ORDER_OPERATION_TYPE
from api.models import SETTLEMENT_STATUS, SERVICE_PAYMENT_TYPE
from api.models import User, CouponInfo
from api.manager import order_manager, groupbuy_manager
from api.models import ORDER_OPERATION_ROLE
from api.tasks import order_task

from services.doctor_notify_service import DoctorNotifyService
from services.notify import notify
from services.custom_phone_service import PhoneService
from sms.utils.smsfactory import send_sms

from rpc.tool.protocol import PushUrlProtocol, gm_protocol
from rpc.tool.error_code import gen, CODES
from rpc.tool.log_tool import alipay_refund_logger
from rpc.all import get_rpc_remote_invoker

from message.utils.message import send_one_message
from message.views.message import internal_message_send

from pay.tool import alipay_tool
from pay.models import WechatOrder
from pay.models import YinuoOrder
from pay.models import ServiceSnapshot, PaymentOrder
from pay.tool.yinuo import create_insurance
from pay.tool.yinuo import pay_balance


def is_callback_payment_real(settlement_payment, callback_payment):
    if float(settlement_payment) != float(callback_payment):
        return gen(CODES.ORDER_PAY_CHECK_ERROR)


def generate_num(id_length):
    return str(random.randint(10 ** (id_length - 1), 10 ** id_length - 1))


def generate_password():
    while True:
        password = generate_num(10)
        if Order.objects.filter(password=password).exists():
            continue
        else:
            break
    return password


def check_from_alipay(notify_id):
    is_from_alipay = alipay_tool.check_is_from_alipay(notify_id)
    log_str = "notify_id {notify_id} is from alipay: {is_from_alipay}".format(notify_id=notify_id,
                                                                              is_from_alipay=is_from_alipay)
    if not is_from_alipay:
        alipay_refund_logger.warning(log_str)
        return False
    return True


def _delete_shopcart_item(order):
    try:
        if order.service_item_id:
            Shopcart.objects.filter(person=order.user.person, service_item_id=order.service_item_id).delete()
    except:
        # fail silently
        logging_exception()


def _send_to_doctor(order):
    ss = DoctorNotifyService.get_service(order.service.doctor_id)
    ss.notify_new_order(order)


def _send_to_customer(order):
    snapshot = ServiceSnapshot.objects.get(order=order)
    # 付款成功，推送：无； 通知：有； 短信：有
    # 发送付款成功短信
    service_name = snapshot.name

    # send_sms(
    #         order.phone, '{}支付成功！订单号{}，验证码{}。到院还需支付{}元。联系医生：{}转{}【更美】'.format(
    #                 service_name, order.id,
    #                 order.password, order.hospital_payment,
    #                 PhoneService.get_phone_prefix(order.service.doctor.phone_ext),
    #                 order.service.doctor.phone_ext
    #         ),
    #         13309,
    #         '{}, {}, {}, {}, {}'.format(
    #                 service_name, order.id, order.password,
    #                 order.hospital_payment,
    #                 order.service.doctor.phone_ext
    #         )
    # )

    if order.groupbuy_status == GROUPBUY_STATUS.NOT_GROUPBUY:
        # 非拼团才发送短信
        send_sms(
            order.phone,
            32,
            [
                {'service': service_name},
                {'order_id': order.id},
                {'code': order.password},
                {'price': order.hospital_payment},
                {'kf_phone': PhoneService.get_phone_prefix(order.service.doctor.phone_ext or '6666')},
                {'ext': order.service.doctor.phone_ext if order.service.doctor.accept_call else '6666'},
            ],
        )
    # 7720 修改验证码为部分可读
    try:
        encrypted_password = order.password[:2] + '*' * 6 + order.password[8:]
    except BaseException:
        logging_exception()
        encrypted_password = '*' * 10
    # 发送付款成功通知
    notification_content = (
        u'{}订单付款成功！订单号：{}，验证码：{}。'
        u'到院还需支付{}元。请在确定手术后，'
        u'才将验证密码交给医生/医院。已验证的订单将无法退款哦。联系医生，'
        u'请拨打免费400电话：{}转{}'
    ).format(
        service_name,
        order.id,
        encrypted_password,
        order.hospital_payment,
        PhoneService.get_phone_prefix(order.service.doctor.phone_ext),
        order.service.doctor.phone_ext
    )

    send_notification(
        order.user.id,
        u'付款成功',
        notification_content,
        PushUrlProtocol.ORDER_DETAIL.format(id=order.id)
    )


def pay_insurance(order):
    # 一诺保险现在已经整体迁移到plutus 不再使用此方法
    try:
        yinuo = YinuoOrder.objects.get(order=order)
        pay_balance(product_id=settings.YINUO_PRODUCT_ID, payment_id=yinuo.payment_id)
        get_rpc_remote_invoker()['plutus/insurance/yinuo/paid'](
            user_id=order.user.id,
            order_id=order.id
        ).unwrap()
    except:
        logging_exception()


def buy_insurance(order):
    # 一诺保险现在已经整体迁移到plutus 不再使用此方法
    try:
        policyholder = get_rpc_remote_invoker()['plutus/insurance/common/policyholder'](user_id=order.user.id).unwrap()
        resp = create_insurance(
            product_id=settings.YINUO_PRODUCT_ID,
            plan=settings.YINUO_PLAN,
            mobile=policyholder['phone'] or order.phone,
            id_card=policyholder['id_card'],
            id_name=policyholder['name'],
            money=settings.YINUO_PREMIUM,
            hospital_name=order.service.doctor.hospital.name,
            operation_name=order.service.name
        )
        YinuoOrder.objects.create(
            order=order,
            yinuo_id=resp.get('guarantee_slip_id'),
            payment_id=resp.get('payment_id')
        )
    except:
        logging_exception()


def order_paid(order, payment_channel, pay_time, auto_create_diary=True):
    order.operate(order.user.person, ORDER_OPERATION_TYPE.PAY, ORDER_OPERATION_ROLE.USER)
    order.password = generate_password()

    business_partener_id = None
    try:
        business_partener_id = order.service.doctor.business_partener_id
    except:
        logging_exception()

    order.servicesnapshot.business_partener_at_paid_id = business_partener_id
    order.servicesnapshot.save()

    days = order.service.valid_duration or 90
    order.expired_date = datetime.datetime.now() + datetime.timedelta(days=days)

    order.pay_time = pay_time

    try:
        hospital = order.servicesnapshot.doctor.hospital
        res = get_rpc_remote_invoker()['plutus/insurance/paid'](
            order_id=order.id,
            hospital_city=hospital.city.name,
            hospital_name=hospital.name,
            operation_name=order.service.name,
            item_name=order.servicesnapshot.items,
        ).unwrap()
        if res['insurance_type'] == INSURANCE_TYPE.ZHONGAN:
            send_user = User.objects.get(id=settings.BOSS)
            phone = PhoneService.get_phone_prefix('6666')
            ext = '6666'
            content = settings.ZHONGAN_MESSAGE_CONTENT.format(phone, ext)
            send_one_message(send_user, target_user=order.user, conversation=None,
                             conversation_type=CONVERSATION_TYPE.MESSAGE,
                             msg_type=MESSAGE_TYPE.TEXT,
                             content={'text': content})

    except RPCFaultException as e:
        if e.error == CODES.INSURANCE_ORDER_NOT_EXIST:
            pass
        else:
            logging_exception()
    except:
        logging_exception()

    order.payment_channel = payment_channel

    if auto_create_diary:
        order.create_diary()
    order.save()


def _send_order_message_to_doctor(sender_user_id, target_user_id, content_text=None):
    try:
        # sender_user_id = order.user.id
        # target_user_id = order.service.doctor.user.id

        if sender_user_id is not None and target_user_id is not None and sender_user_id != target_user_id:
            res = internal_message_send(sender_user_id=sender_user_id, target_user_id=target_user_id,
                                        conversation_id=None, conversation_type=CONVERSATION_TYPE.MESSAGE,
                                        msg_type=MESSAGE_TYPE.TEXT, content={'text': content_text}, is_system=1)

            if 'error' in res and res['error'] != CODES.SUCCESS:
                logging_exception()
    except:
        logging_exception()


def _do_groupbuy(settlement, un_lock=False):
    all_order = []
    for item in settlement.items.all():
        order = item.order
        all_order.append(order)

    if len(all_order) == 1:
        # 拼团只有一个订单
        order = all_order[0]

        if order.groupbuy_status != GROUPBUY_STATUS.NOT_GROUPBUY:
            # 只有拼团订单需要处理

            if order.join_groupbuy_team_id:
                # 参团, 先锁定拼团
                gbt = groupbuy_manager.try_lock_groupbuy_team(order.join_groupbuy_team_id, un_lock=un_lock)

                if gbt and order.groupbuy_status in (
                    GROUPBUY_STATUS.GROUPBUY_STARTED, GROUPBUY_STATUS.GROUPBUY_SUCCESSED,
                    GROUPBUY_STATUS.GROUPBUY_FAIL
                ):
                    # 参团的订单成功支付，注意其他状态的时候不要做任何处理
                    groupbuy_manager.groupbuy_user_paid(gbt, order.id, order.user_id)
            else:
                if order.groupbuy_status == GROUPBUY_STATUS.GROUPBUY_NOT_STARTED:
                    # 开团支付成功，此时去创建拼团信息
                    price_info = json.loads(order.servicesnapshot.price_info)

                    rule = price_info["selling_rule"]

                    now = datetime.datetime.now()
                    special_end_time = datetime.datetime.strptime(
                        rule["end_time"], '%Y-%m-%d %H:%M:%S'
                    ) if rule["end_time"] else now
                    if special_end_time < now:
                        special_end_time = now
                    groupbuy_countdown = rule["groupbuy_countdown"] if rule["groupbuy_countdown"] > 0 else 0

                    countdown_endtime = now + datetime.timedelta(seconds=groupbuy_countdown)

                    groupbuy_start_time = now
                    groupbuy_end_time = min(countdown_endtime, special_end_time)

                    groupbuy_nums = rule["groupbuy_nums"]
                    groupbuy_type = rule["groupbuy_type"]

                    groupbuy_team = groupbuy_manager.create_groupbuy(
                        user_id=order.user_id,
                        order_id=order.id,
                        groupbuy_type=groupbuy_type,
                        groupbuy_nums=groupbuy_nums,
                        service_item_id=order.service_item_id,
                        price_id=order.service_item_price_id,
                        groupbuy_start_time=groupbuy_start_time,
                        groupbuy_end_time=groupbuy_end_time,
                    )

                    order.groupbuy_status = GROUPBUY_STATUS.GROUPBUY_STARTED
                    order.join_groupbuy_team_id = groupbuy_team.id

                    order.save(update_fields=["groupbuy_status", "join_groupbuy_team_id"])


def _do_settlement_paid(settlement, payment_channel, pay_time):
    all_order_ids = []
    service_item_id_to_orders = {}

    auto_create_diary = settlement.auto_create_diary  # 是否自动创建日记本，默认True

    for item in settlement.items.all():
        order = item.order
        all_order_ids.append(order.id)
        order_paid(
            order=order,
            payment_channel=payment_channel,
            pay_time=pay_time,
            auto_create_diary=auto_create_diary
        )
        if order.service_item_id:
            if order.service_item_id not in service_item_id_to_orders:
                service_item_id_to_orders[order.service_item_id] = []
            service_item_id_to_orders[order.service_item_id].append(order)

    for siid, orders in service_item_id_to_orders.items():
        first_order = orders[0]
        sender_user_id = first_order.user_id
        target_user_id = first_order.service.doctor.user_id
        service_name = first_order.servicesnapshot.name
        number = len(orders)
        total_service_pre_payment_price = first_order.servicesnapshot.pre_payment_price * number
        total_service_price = sum([o.service_price for o in orders])
        total_hospital_payment = sum([o.hospital_payment for o in orders])
        content_text = u'医生您好，我购买了{}个{}，预付款为{}元，更美总价为{}元，到医还需付{}元。到时见'.format(
            number,
            service_name,
            total_service_pre_payment_price,
            total_service_price,
            total_hospital_payment,
        )
        _send_order_message_to_doctor(
            sender_user_id=sender_user_id,
            target_user_id=target_user_id,
            content_text=content_text,
        )

    coupon_info_ids = settlement.get_coupon_info_ids()
    if coupon_info_ids:
        coupon_infos = CouponInfo.objects.filter(id__in=coupon_info_ids)
        for ci in coupon_infos:
            ci.consume_coupon()

    if settlement.pay_mode == SETTLEMENT_PAY_MODE.INSTALLMENT and payment_channel not in (PAYMENT_CHANNEL.XIAOYING):
        # 支付结束发现不是采用分期方式, 则改结算单mode 状态
        # try:
        #     from rpc.context import create_fake_context
        #     ctx = create_fake_context()
        #     order_id = settlement.items.first().order_id
        #     ctx.rpc['pay/installment/refund'](partner=1, order_id=order_id).unwrap()
        # except:
        #     logging_exception()

        settlement.pay_mode = SETTLEMENT_PAY_MODE.COMMON

    settlement.status = SETTLEMENT_STATUS.PAID
    settlement.save()

    from hippo.views.doctormessage import add_order_message
    from gm_types.doctor import DOCTOR_ORDER_MESSAGE_TYPE

    add_order_message(DOCTOR_ORDER_MESSAGE_TYPE.ORDER, all_order_ids)


def settlement_paid_with_log(ctx, settlement, payment_channel, pay_time):
    _do_settlement_paid(
        settlement=settlement, payment_channel=payment_channel, pay_time=pay_time
    )
    ctx.logger.app(settlement_paid={
        'settlement_id': settlement.id,
    })


def after_settlement_paid(settlement, un_lock=False):
    doctor_ids = set()
    user_id = None

    for item in settlement.items.all():
        order = item.order
        user_id = order.user_id

        doctor_id = order.service.doctor_id
        doctor_ids.add(doctor_id)

        # 2016双11大促添加 特殊商品支付送美分逻辑
        if getattr(order, 'orderextra', None) and order.orderextra.get_points:
            point.add(
                user_id=order.user.id,
                reason=POINTS_TYPE.PAY,
                point=order.orderextra.get_points,
                order_id=order.id
            )

            send_notification(
                uid=order.user.id,
                title=u'美分返还',
                content=u'美人儿，您的美分到账啦，再次消费美分直抵现金哦（100美分抵扣1元）~',
                url=gm_protocol.point_detail
            )

        order_task.order_increase_event(order.id)
        _send_to_doctor(order)
        _send_to_customer(order)
        _delete_shopcart_item(order)
        # 通知医生有新订单了
        notify('order/add', doctor_id=doctor_id, data={
            'order_id': order.id,
            'order_status': order.status,
        })

    notification_content_format = (
        u'您的【{}】美购订单付款成功！请在确定手术后，才将验证密码交给医生/医院。已验证的订单将无法退款哦～'
    )

    doctor_id_to_data = {d[0]: (d[1], d[2]) for d in Doctor.objects.filter(
        id__in=doctor_ids
    ).select_related('hospital__city').values_list('id', 'name', 'hospital__city__name')}

    all_notification_contents = []

    for doctor_id, d in doctor_id_to_data.items():
        name, city_name = d
        if name:
            city_and_doctor_name = u"{}@{}".format(city_name, name) if city_name else name
            content = notification_content_format.format(city_and_doctor_name)
            all_notification_contents.append((content, city_and_doctor_name))

    # _send_to_customer(order)
    # 短信要等模板配置
    pushurl = (
        settlement.service_payment_type == SERVICE_PAYMENT_TYPE.EXCHANGE_GIFT and
        PushUrlProtocol.SETTLEMENT_DETAIL_GIFT_EXCHANGE or
        PushUrlProtocol.SETTLEMENT_DETAIL
    )

    for notification_content, city_and_doctor_name in all_notification_contents:
        send_notification(
            user_id,
            u'付款成功',
            notification_content,
            pushurl.format(id=settlement.id)
        )
    #结算成功后修改拼团信息
    _do_groupbuy(settlement, un_lock=un_lock)

    order_manager.send_settlement_paid_event(settlement)


def after_settlement_paid_commit(settlement):
    from gm_types.gaia import MOMO_STAT_LOG_TYPE

    from api.tool.order_tool import get_momo_stat_log_info
    from api.tool.order_tool import get_live_order_stat_log
    from api.tasks.period_task import momo_stat_log
    from api.tasks.period_task import send_live_order_stats

    momo_info_list = []
    live_info_list = []

    for item in settlement.items.all():
        order = item.order
        momo_param_info = get_momo_stat_log_info(order)
        live_order_info = get_live_order_stat_log(order)
        if momo_param_info:
            momo_info_list.append(momo_param_info)
        live_info_list.append(live_order_info)

    if live_info_list:
        send_live_order_stats.delay({"orders": live_info_list})

    if momo_info_list:
        # 异步打点前，落日志
        _data = {
            "source": "momo_pre_stat_log",
            "momo_stat_log_type": MOMO_STAT_LOG_TYPE.PRE_PAY_SUCCESS,
            "info_list": momo_info_list,
            "pre_stat_log_time": time.time(),
        }
        momo_stat_logger.info("momo_pre_stat_log_info:{}".format(json.dumps(_data)))
        momo_stat_log.delay(
            info_list=momo_info_list,
            stat_log_type=MOMO_STAT_LOG_TYPE.PRE_PAY_SUCCESS
        )

def send_order_message_to_doctor(settlement):
    all_order_ids = []
    service_item_id_to_orders = {}

    for item in settlement.items.all():
        order = item.order
        all_order_ids.append(order.id)
        if order.service_item_id:
            if order.service_item_id not in service_item_id_to_orders:
                service_item_id_to_orders[order.service_item_id] = []
            service_item_id_to_orders[order.service_item_id].append(order)

    for siid, orders in service_item_id_to_orders.items():
        first_order = orders[0]
        sender_user_id = first_order.user_id
        target_user_id = first_order.service.doctor.user_id
        service_name = first_order.servicesnapshot.name
        number = len(orders)

        if not first_order.servicesnapshot.items:
            service_item_name = ""
        else:
            try:
                service_item_name = "".join(json.loads(first_order.servicesnapshot.items))
            except:
                logging_exception()
                service_item_name = ""
        content_text = u"医生您好，我下单了{number}个{service_name}-{service_item_name}，还请知悉。".format(
            number=number,
            service_name=service_name,
            service_item_name=service_item_name,
        )
        _send_order_message_to_doctor(
            sender_user_id=sender_user_id,
            target_user_id=target_user_id,
            content_text=content_text,
        )


def make_decrease_celery_app():
    from celery import Celery
    from kombu import Exchange, Queue

    app = Celery(
        "decrease",
        broker=settings.DECREASE_INVENTORY_BROKER_URL,
    )

    CELERY_TASK_SERIALIZER = "json"
    inventory_exchange = Exchange('promotion_decrease_inventory', type='direct')
    cancel_inventory_exchange = Exchange('promotion_cancel_decrease_inventory', type='direct')

    CELERY_QUEUES = (
        Queue('inventory', inventory_exchange, routing_key='decrease_inventory'),
        Queue('cancel_inventory', cancel_inventory_exchange, routing_key='cancel_decrease_inventory'),

    )

    CELERY_ROUTES = {
        'promotion.tasks.decrease_inventory.decrease_inventory': {
            'queue': 'promotion_decrease_inventory',
            'routing_key': 'decrease_inventory'
        },
        'promotion.tasks.decrease_inventory.cancel_order_promotion_relation': {
            'queue': 'promotion_cancel_decrease_inventory',
            'routing_key': 'cancel_decrease_inventory'
        },
    }
    app.conf.update({"CELERY_QUEUES": CELERY_QUEUES})
    app.conf.update({"CELERY_ROUTES": CELERY_ROUTES})
    app.conf.update({"CELERY_TASK_SERIALIZER": CELERY_TASK_SERIALIZER})
    return app


def decrease_promotion_inventory(settlement_id, commodity_promotion_number, user_id, is_join_group_buy):

    # app = make_decrease_celery_app()
    # app.send_task(
    #     "promotion.tasks.decrease_inventory.decrease_inventory",
    #     kwargs=dict(
    #         settlement_id=settlement_id,
    #         commodity_promotion_number=commodity_promotion_number,
    #         user_id=user_id
    #     ),
    #     result_cls=app.AsyncResult,
    #     queue="promotion_decrease_inventory"
    # )
    # app.close()

    try:
        rpc_invoker = get_rpc_remote_invoker()
        rpc_invoker['poseidon/promotion/commodity/inventory/decrease_inventory'](
            settlement_id=settlement_id,
            commodity_promotion_number=commodity_promotion_number,
            user_id=user_id,
            is_join_group_buy=is_join_group_buy
        ).unwrap()
    except Exception:
        logging_exception()


def cancel_decrease_promotion_inventory(submission2expend_inventory, is_join_group_buy):
    # app = make_decrease_celery_app()
    # app.send_task(
    #     "promotion.tasks.decrease_inventory.cancel_order_promotion_relation",
    #     kwargs=dict(
    #         submission2expend_inventory=submission2expend_inventory
    #     ),
    #     result_cls=app.AsyncResult,
    #     queue="promotion_cancel_decrease_inventory"
    # )
    # app.close()
    try:
        rpc_invoker = get_rpc_remote_invoker()
        rpc_invoker['poseidon/promotion/commodity/inventory/cancel_inventory'](
            submission2expend_inventory=submission2expend_inventory,
            is_join_group_buy=is_join_group_buy
        ).unwrap()
    except Exception:
        logging_exception()
