package com.ai.da.service.impl;

import com.ai.da.common.context.UserContext;
import com.ai.da.common.enums.PayTypeEnum;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.mapper.primary.PaymentInfoMapper;
import com.ai.da.mapper.primary.entity.OrderInfo;
import com.ai.da.mapper.primary.entity.PaymentInfo;
import com.ai.da.mapper.primary.entity.ProductCoupons;
import com.ai.da.model.dto.AlipayHKCallbackDTO;
import com.ai.da.model.dto.QueryPageByTimeDTO;
import com.ai.da.model.vo.OrderListVO;
import com.ai.da.service.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.paypal.orders.Order;
import com.stripe.Stripe;
import com.stripe.exception.StripeException;
import com.stripe.model.Charge;
import com.stripe.model.Invoice;
import com.stripe.model.Subscription;
import com.stripe.model.checkout.Session;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Service
@Slf4j
public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, PaymentInfo> implements PaymentInfoService {

    @Resource
    private StripeService stripeService;

    @Resource
    private OrderInfoService orderInfoService;

    /**
     * 记录支付日志：微信支付
     * @param plainText
     */
    @Override
    public void createPaymentInfo(String plainText) {

        log.info("记录支付日志");

        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);

        //订单号
        String orderNo = (String)plainTextMap.get("out_trade_no");
        //业务编号
        String transactionId = (String)plainTextMap.get("transaction_id");
        //支付类型
        String tradeType = (String)plainTextMap.get("trade_type");
        //交易状态
        String tradeState = (String)plainTextMap.get("trade_state");
        //用户实际支付金额
        Map<String, Object> amount = (Map)plainTextMap.get("amount");
        Integer payerTotal = ((Double) amount.get("payer_total")).intValue();

        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(orderNo);
        paymentInfo.setPaymentType(PayTypeEnum.WXPAY.getType());
        paymentInfo.setTransactionId(transactionId);
        paymentInfo.setTradeState(tradeState);
        // 原来的单位是:分 Int 现改为:元 Float
        paymentInfo.setPayerTotal(payerTotal / 100.0F);
        paymentInfo.setContent(plainText);

        baseMapper.insert(paymentInfo);
    }

    /**
     * 记录支付日志：支付宝
     * @param params
     */
    @Override
    public void createPaymentInfoForAliPay(Map<String, String> params, String type) {

        log.info("记录支付日志");

        //获取订单号
        String orderNo = params.get("out_trade_no");
        //业务编号
        String transactionId = params.get("trade_no");
        //交易状态
        String tradeStatus = params.get("trade_status");
        //交易金额
        String totalAmount = params.get("total_amount");
//        int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
        int totalAmountInt = new BigDecimal(totalAmount).intValue();

        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(orderNo);
        paymentInfo.setPaymentType(PayTypeEnum.ALIPAY.getType());
        paymentInfo.setTransactionId(transactionId);
        paymentInfo.setTradeState(tradeStatus);
        // 原来的单位是分 Int 现改为元 Long
        paymentInfo.setPayerTotal(totalAmountInt / 100.0F);
        paymentInfo.setType(type);

        Gson gson = new Gson();
        String json = gson.toJson(params, HashMap.class);
        paymentInfo.setContent(json);
        // 获取订单信息
        OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo);
        if (!Objects.isNull(orderByOrderNo)){
            paymentInfo.setCountry(orderByOrderNo.getCountry());
            paymentInfo.setCity(orderByOrderNo.getCity());
            paymentInfo.setIpAddress(orderByOrderNo.getIpAddress());
        }
        baseMapper.insert(paymentInfo);
    }

    @Override
    public void createPaymentInfoForPayPal(Order order, String type) {

        log.info("记录支付日志");

//        int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
        Float totalAmountFloat = new BigDecimal(order.purchaseUnits().get(0).payments().captures().get(0).amount().value()).floatValue();

        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(order.id());
        paymentInfo.setPaymentType(PayTypeEnum.PAYPAL.getType());
        paymentInfo.setTransactionId(order.id());
        paymentInfo.setTradeState(order.status());
        // 确认这里的数据单位是不是元
        paymentInfo.setPayerTotal(totalAmountFloat);
        paymentInfo.setType(type);

        Gson gson = new Gson();
        String json = gson.toJson(order, Order.class);
        paymentInfo.setContent(json);
        // 获取订单信息
        OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(order.id());
        if (!Objects.isNull(orderByOrderNo)){
            paymentInfo.setCountry(orderByOrderNo.getCountry());
            paymentInfo.setCity(orderByOrderNo.getCity());
            paymentInfo.setIpAddress(orderByOrderNo.getIpAddress());
        }
        baseMapper.insert(paymentInfo);
    }

    @Override
    public void createPaymentInfoForAliPayHK(AlipayHKCallbackDTO alipayHKCallbackDTO, String type) {

        log.info("记录支付日志");

        //获取订单号
        String orderNo = alipayHKCallbackDTO.getOut_trade_no();
        //业务编号
        String transactionId = alipayHKCallbackDTO.getTransaction_id();
        //交易状态
        String tradeStatus = alipayHKCallbackDTO.getStatus();
        //交易金额
        String totalAmount = alipayHKCallbackDTO.getAmount().toString();
//        int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
        Float totalAmountFloat = new BigDecimal(totalAmount).floatValue();

        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(orderNo);
        paymentInfo.setPaymentType(PayTypeEnum.ALIPAY_HK.getType());
        paymentInfo.setTransactionId(transactionId);
        paymentInfo.setTradeState(tradeStatus);
        paymentInfo.setPayerTotal(totalAmountFloat);
        paymentInfo.setType(type);

        Gson gson = new Gson();
        String json = gson.toJson(alipayHKCallbackDTO);
        paymentInfo.setContent(json);
        // 获取订单信息
        OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo);
        if (!Objects.isNull(orderByOrderNo)){
            paymentInfo.setCountry(orderByOrderNo.getCountry());
            paymentInfo.setCity(orderByOrderNo.getCity());
            paymentInfo.setIpAddress(orderByOrderNo.getIpAddress());
        }
        baseMapper.insert(paymentInfo);
    }

    public void createOrUpdatePaymentInfoForStripe(Session session){
        String orderId = session.getMetadata().get("orderId");
        String status = session.getStatus();
        // 获取transactionId,从sessionId更改为invoiceId
        String sessionId = session.getId();
        Long amountTotal = session.getAmountTotal();
        // stripe 的支付金额单位是分
        Float divide = new BigDecimal(amountTotal).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).floatValue();

        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(orderId);
        paymentInfo.setPaymentType(PayTypeEnum.STRIPE.getType());
        paymentInfo.setTransactionId(sessionId);
        paymentInfo.setTradeState(status);
        paymentInfo.setPayerTotal(divide);
        Gson gson = new Gson();
        String json = gson.toJson(session);
        paymentInfo.setContent(json);
        // 获取订单信息
        OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderId);
        if (!Objects.isNull(orderByOrderNo)){
            paymentInfo.setCountry(orderByOrderNo.getCountry());
            paymentInfo.setCity(orderByOrderNo.getCity());
            paymentInfo.setIpAddress(orderByOrderNo.getIpAddress());
        }
        baseMapper.insert(paymentInfo);
    }

    @Value("${stripe.private-key}")
    private String privateKey;
    @Transactional(rollbackFor = Exception.class)
    public PaymentInfo createOrUpdatePaymentInfoForStripe(Invoice invoice){
        Stripe.apiKey = privateKey;
        // 获取transactionId,从sessionId更改为invoiceId
        String invoiceId = invoice.getId();

        QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
        qw.eq("transaction_id", invoiceId);
        PaymentInfo paymentInfo = baseMapper.selectOne(qw);
        String status = invoice.getStatus();
        // 判断是否有优惠码
        String promotionCode = null;
        if (Objects.nonNull(invoice.getDiscount()) && !StringUtil.isNullOrEmpty(invoice.getDiscount().getPromotionCode())){
            ProductCoupons productCoupon = stripeService.getProductCoupon(null, invoice.getDiscount().getPromotionCode());
            promotionCode = productCoupon.getPromotionCode();
        }
        // 判断当前支付是否已经被记录，确保同一个支付不会被重复记录
        if (Objects.isNull(paymentInfo)){
            String orderNo;
            try {
                if (invoice.getBillingReason().equals("manual")){
                    // 手动创建的发票，针对one-time支付
//                    orderNo = invoice.getLines().getData().get(0).getPrice().getMetadata().get("orderId");
                    // 当支付失败时，chargeId为空
                    String chargeId = invoice.getCharge();
                    orderNo = Charge.retrieve(chargeId).getDescription().replace("AiDA - ", "");
                }else {
                    String subscriptionId = invoice.getSubscription();
                    // 从subscription中获取orderNo
                    orderNo = Subscription.retrieve(subscriptionId).getDescription().replace("AiDA - ", "");
                }
            } catch (StripeException e) {
                throw new RuntimeException(e);
            }
            Long amountTotal;
            if (status.equals("paid")){
                amountTotal = invoice.getAmountPaid();
            }else {
                amountTotal = invoice.getAmountDue();
            }

            // stripe 的支付金额单位是分,在我们数据库中金额单位为 元
            Float divide = new BigDecimal(amountTotal).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).floatValue();
            String type = invoice.getBillingReason().equals("subscription_create") ? "new" :
                    invoice.getBillingReason().equals("subscription_cycle") ? "renewal" : invoice.getBillingReason();

            // 获取支付方式
            Map<String, String> paymentMethod = stripeService.getPaymentMethodByInvoiceId(invoiceId);
            // 获取订单信息
            OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo);

            paymentInfo = new PaymentInfo();
            paymentInfo.setOrderNo(orderNo);
            paymentInfo.setPaymentType(PayTypeEnum.STRIPE.getType());
            paymentInfo.setTransactionId(invoiceId);
            // 使用invoice的状态
            paymentInfo.setTradeState(status);
            paymentInfo.setPayerTotal(divide);
            Gson gson = new Gson();
            String json = gson.toJson(invoice);
            paymentInfo.setContent(json);
            paymentInfo.setType(type);
            paymentInfo.setNotified(0);
            paymentInfo.setPaymentMethod(paymentMethod.get("paymentMethod"));
            paymentInfo.setLast4(paymentMethod.get("last4"));
            paymentInfo.setHostedInvoiceUrl(invoice.getHostedInvoiceUrl());
            paymentInfo.setPromotionCode(promotionCode);
            paymentInfo.setCreateTime(LocalDateTime.now());
            if (!Objects.isNull(orderByOrderNo)){
                paymentInfo.setCountry(orderByOrderNo.getCountry());
                paymentInfo.setCity(orderByOrderNo.getCity());
                paymentInfo.setIpAddress(orderByOrderNo.getIpAddress());
            }
            int row = baseMapper.insertIgnore(paymentInfo);
            log.info("Payment Info insert affect rows:{}", row);
        }else {
            paymentInfo.setTradeState(status);
            paymentInfo.setPromotionCode(promotionCode);
            paymentInfo.setUpdateTime(LocalDateTime.now());
            baseMapper.updateById(paymentInfo);
        }
        return paymentInfo;
    }

    public PaymentInfo createOrUpdatePaymentInfoForStripe(Charge charge){
        Stripe.apiKey = privateKey;
        QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
        // todo 首次支付失败，没有invoiceId，所以如果这个order之后成功支付后，会有多条paymentInfo 是否需要优化？？
        qw.eq("transaction_id", charge.getInvoice());
        PaymentInfo paymentInfo = baseMapper.selectOne(qw);
        Charge.PaymentMethodDetails paymentMethodDetails = charge.getPaymentMethodDetails();
        String paymentMethod;
        String last4 = "N/A";
        switch (paymentMethodDetails.getType()){
            case "alipay":
                paymentMethod = "Alipay";
                break;
            case "bancontact":
                paymentMethod = "BanContact";
                break;
            case "card":
                Charge.PaymentMethodDetails.Card card = paymentMethodDetails.getCard();
                String brand = card.getBrand();
                brand = brand.substring(0, 1).toUpperCase() + brand.substring(1);
                paymentMethod = brand + " " + card.getFunding() + "card";
                last4 = card.getLast4();
                break;
            case "eps":
                Charge.PaymentMethodDetails.Eps eps = paymentMethodDetails.getEps();
                paymentMethod = eps.getBank();
                break;
            case "giropay":
                paymentMethod = "GiroPay";
                break;
            case "ideal":
                Charge.PaymentMethodDetails.Ideal ideal = paymentMethodDetails.getIdeal();
                paymentMethod = ideal.getBank();
                break;
            case "link":
                paymentMethod = "Link";
                break;
            default:
                paymentMethod = "N/A";
        }
        if (Objects.isNull(paymentInfo)){
            Stripe.apiKey = privateKey;

            String orderNo = charge.getDescription().replace("AiDA - ", "");
            OrderInfo orderByOrderNo = orderInfoService.getOrderByOrderNo(orderNo);
            Float divide = new BigDecimal(charge.getAmount()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP).floatValue();
            paymentInfo = new PaymentInfo();
            paymentInfo.setOrderNo(orderNo);
            paymentInfo.setTransactionId(charge.getInvoice());
            paymentInfo.setPaymentType(PayTypeEnum.STRIPE.getType());
            paymentInfo.setTradeState(charge.getStatus());
            paymentInfo.setPayerTotal(divide);
            paymentInfo.setNotified(0);
            paymentInfo.setPaymentMethod(paymentMethod);
            paymentInfo.setLast4(last4);
            paymentInfo.setCreateTime(LocalDateTime.now());
            if (!Objects.isNull(orderByOrderNo)){
                paymentInfo.setCountry(orderByOrderNo.getCountry());
                paymentInfo.setCity(orderByOrderNo.getCity());
                paymentInfo.setIpAddress(orderByOrderNo.getIpAddress());
            }
            int row = baseMapper.insertIgnore(paymentInfo);
            log.info("Payment Info insert affect rows:{}", row);
        }else {
            paymentInfo.setTradeState(charge.getStatus());
            paymentInfo.setPaymentMethod(paymentMethod);
            paymentInfo.setLast4(last4);
            paymentInfo.setUpdateTime(LocalDateTime.now());
            baseMapper.updateById(paymentInfo);
        }

        return paymentInfo;
    }


    @Override
    public List<PaymentInfo> getPaymentInfoByOrderNo(String orderId, String order){
        QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
        qw.eq("order_no", orderId).orderByDesc(order.equals("DESC"),"id");

        return baseMapper.selectList(qw);
    }

    @Override
    public void updatePaymentStatusById(Long id, String status, String content){
        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setId(id);
        paymentInfo.setTradeState(status);
        paymentInfo.setContent(content);

        baseMapper.updateById(paymentInfo);
    }

    public PageBaseResponse<OrderListVO> getPaymentInfo(QueryPageByTimeDTO queryPageByTimeDTO){
        Long accountId = UserContext.getUserHolder().getId();

        String startTime = queryPageByTimeDTO.getStartTime();
        String endTime = queryPageByTimeDTO.getEndTime();
        if (StringUtil.isNullOrEmpty(startTime)) {
            startTime = "2024-02-01 00:00:00";
        }
        if (StringUtil.isNullOrEmpty(endTime)) {
            LocalDateTime now = LocalDateTime.now();
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            endTime = now.format(dateTimeFormatter);
        }

        int offset = (queryPageByTimeDTO.getPage() - 1) * queryPageByTimeDTO.getSize();
        List<OrderListVO> orderListVOS = baseMapper.selectPageOrderList(accountId, startTime, endTime, offset,
                queryPageByTimeDTO.getSize(), queryPageByTimeDTO.getId());

        if (CollectionUtils.isEmpty(orderListVOS)) {
            return PageBaseResponse.success(new Page<>());
        }else {
            int totalCount = baseMapper.queryOrderListTotalCount(accountId, startTime, endTime, queryPageByTimeDTO.getId());
            IPage<OrderListVO> orderListVOIPage = new Page<>();
            Integer size = queryPageByTimeDTO.getSize();
            orderListVOIPage.setSize(size);
            orderListVOIPage.setRecords(orderListVOS);
            orderListVOIPage.setCurrent(queryPageByTimeDTO.getPage());
            orderListVOIPage.setPages((long)Math.ceil((double) totalCount / size));
            orderListVOIPage.setTotal(totalCount);
            return PageBaseResponse.success(orderListVOIPage);
        }
    }

    public List<PaymentInfo> getPaymentInfoByPromCode(Long accountId, String promCode){
        return baseMapper.selectPaidPaymentsByAccountAndPromotion(accountId, promCode);
    }

    public PaymentInfo updatePaymentRefundStatus(Charge charge){
        // 判断当前退款是部分退款还是全部退款
        QueryWrapper<PaymentInfo> qw = new QueryWrapper<>();
        qw.eq("transaction_id", charge.getInvoice());
        PaymentInfo paymentInfo = baseMapper.selectOne(qw);
        if (Objects.nonNull(paymentInfo)){
            String status ;
            if (Objects.equals(charge.getAmount(), charge.getAmountRefunded())){
                status = "Refunded";
            }else if (charge.getAmount() > charge.getAmountRefunded()){
                status = "Partial refund";
            }else {
                status = "Refund Exception";
                log.warn("{}, 退款金额高于付款金额， ChargeId为：{}", status, charge.getId());
            }
            if (!paymentInfo.getTradeState().equals(status)){
                paymentInfo.setTradeState(status);
                paymentInfo.setUpdateTime(LocalDateTime.now());
                baseMapper.updateById(paymentInfo);
            }
        }
        return null;
    }
}
