package com.sonic.lion.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sonic.common.exception.BizException;
import com.sonic.common.exception.SysException;
import com.sonic.lion.channel.config.StripeConfig;
import com.sonic.lion.domain.bo.SubscriptionBo;
import com.sonic.lion.domain.bo.WebhookBo;
import com.sonic.lion.domain.entity.PayAccountFundThird;
import com.sonic.lion.domain.entity.PayTrade;
import com.sonic.lion.domain.enums.CallChannelStatus;
import com.sonic.lion.domain.enums.TradeEvent;
import com.sonic.lion.domain.input.*;
import com.sonic.lion.domain.output.*;
import com.sonic.lion.enums.PayChannel;
import com.sonic.lion.enums.ThirdAccountType;
import com.sonic.lion.enums.ToastResultCode;
import com.sonic.lion.service.PayAccountFundThirdService;
import com.sonic.lion.service.PayChannelService;
import com.sonic.lion.utils.MoneyUtils;
import com.stripe.Stripe;
import com.stripe.exception.SignatureVerificationException;
import com.stripe.exception.StripeException;
import com.stripe.model.*;
import com.stripe.model.checkout.Session;
import com.stripe.net.Webhook;
import com.stripe.param.CustomerCreateParams;
import com.stripe.param.RefundCreateParams;
import com.stripe.param.SubscriptionListParams;
import com.stripe.param.TransferCreateParams;
import com.stripe.param.checkout.SessionCreateParams;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import java.util.Map;

/**
 * @author: code
 * @date: 2025/05/19
 * @Description:
 * @version: 1.0.0
 */
@Slf4j
@Service("stripeService")
public class StripeServiceImpl implements PayChannelService, InitializingBean {

    @Autowired
    private PayAccountFundThirdService payAccountFundThirdService;

    @Autowired
    private StripeConfig stripeConfig;


    @Override
    public ChannelPaymentOutput payment(@NonNull ChannelPaymentInput input) {
        ChannelPaymentOutput output = new ChannelPaymentOutput();
        PayTrade payTrade = input.getPayTrade();
        String returnUrl = input.getReturnUrl();
        if (returnUrl.contains("?")) {
            returnUrl += "&submitId=";
        } else {
            returnUrl += "?submitId=";
        }
        returnUrl += input.getSubmitId();
        //查询三方表中绑定的stripe的客户id
        PayAccountFundThird payAccountFundThird = payAccountFundThirdService.getByAccountIdAndAppType(payTrade.getSrcAccountId(), ThirdAccountType.STRIPE_PAYMENT);
        if(payAccountFundThird == null || StringUtils.isEmpty(payAccountFundThird.getOpenId())) {
            //在stripe创建客户号，并保存到数据库中
            ChannelCreateCustomerInput createCustomerInput = ChannelCreateCustomerInput.builder()
                    .userId(payTrade.getSrcAccountId())
                    .payChannel(PayChannel.STRIPE).build();
            ChannelCreateCustomerOutput channelCreateCustomerOutput = createCustomer(createCustomerInput);
            payAccountFundThird = payAccountFundThirdService.bindStripe(payTrade.getSrcAccountId(), channelCreateCustomerOutput.getOpenId());
        }
        log.info("===> payment payAccountFundThird : {}", payAccountFundThird);
        String customerId = payAccountFundThird.getOpenId();
        try {
            SessionCreateParams params = SessionCreateParams.builder()
                    .addLineItem(
                            SessionCreateParams.LineItem.builder()
                                    .setPriceData(
                                            SessionCreateParams.LineItem.PriceData.builder()
                                                    .setCurrency("usd")
                                                    .setUnitAmount(payTrade.getAmount()) // 动态传入价格
                                                    .setProductData(
                                                            SessionCreateParams.LineItem.PriceData.ProductData.builder()
                                                                    .setName("购买服务") // 动态商品名称
                                                                    .putMetadata("submitId", input.getPayTrade().getTradeNo()) // 可选：元数据
                                                                    .build()
                                                    )
                                                    .build()
                                    )
                                    .setQuantity(1L)
                                    .build())
                    // 手续费 Line Item
                    .addLineItem(
                            SessionCreateParams.LineItem.builder()
                                    .setPriceData(
                                            SessionCreateParams.LineItem.PriceData.builder()
                                                    .setCurrency("usd")
                                                    .setUnitAmount(payTrade.getOccurAmount() - payTrade.getAmount()) // 手续费金额
                                                    .setProductData(
                                                            SessionCreateParams.LineItem.PriceData.ProductData.builder()
                                                                    .setName("手续费") // 显示为手续费
//                                                                    .setDescription("3% 平台服务费") // 可选：详细描述
                                                                    .putMetadata("type", "platform_fee")
                                                                    .putMetadata("submitId", input.getPayTrade().getTradeNo())
                                                                    .build()
                                                    )
                                                    .build()
                                    )
                                    .setQuantity(1L)
                                    .build()
                    )
                    .setMode(SessionCreateParams.Mode.PAYMENT)
                    .setCustomer(customerId)
                    .setSuccessUrl(returnUrl)
                    .setCancelUrl(input.getCancelUrl())
                    .setPaymentIntentData(
                            SessionCreateParams.PaymentIntentData.builder()
                                    .setCaptureMethod(SessionCreateParams.PaymentIntentData.CaptureMethod.AUTOMATIC)
                                    //保存支付方式
                                    .setSetupFutureUsage(SessionCreateParams.PaymentIntentData.SetupFutureUsage.OFF_SESSION)
                                    .putMetadata("submitId", input.getPayTrade().getTradeNo())
                                    .build()
                    )
                    //设置30分钟后过期（不设置，默认为24小时）
                    .setExpiresAt(Instant.now().plusSeconds(30 * 60).getEpochSecond())
                    .build();
            Session session = Session.create(params);
            log.info("Stripe payment result: {}", session);
            output.setResult(session == null ? null : JSONObject.toJSONString(session));
            if (session != null) {
                output.setBatchId(session.getPaymentIntent());
                output.setTransactionId(session.getPaymentIntent());
                output.setStatus(CallChannelStatus.PROCESSING);
                output.setPaymentUrl(session.getUrl());
            } else {
                output.setStatus(CallChannelStatus.FAIL);
            }
        } catch (Exception e) {
            log.error("创建Stripe交易失败", e);
            throw new BizException("", e.getMessage(), e);
        }
        return output;
    }

    @Override
    public ChannelPayoutOutput payout(ChannelPayoutInput input) {
        ChannelPayoutOutput output = new ChannelPayoutOutput();

        TransferCreateParams transferParam = TransferCreateParams.builder()
                .setAmount(input.getAmount())
                .setCurrency("USD")
                .setDestination(input.getDesAccountNo())
                .build();
        try {
            Transfer result = Transfer.create(transferParam);
            String str = JSON.toJSONString(result);
            log.info("Stripe payout result: {}", str);
            output.setStatus(CallChannelStatus.PROCESSING);
            output.setBatchId(result.getId());
            output.setTransactionId(result.getId());
            output.setResult(str);
        } catch (StripeException e) {
            log.error("stripe transfer error", e);
            output.setStatus(CallChannelStatus.FAIL);
            output.setResult(e.getMessage());
        }
        return output;
    }

    @Override
    public ChannelCheckOutput checkPayout(String channelSn) {
        ChannelCheckOutput output = new ChannelCheckOutput();
        try {
            Transfer result = Transfer.retrieve(channelSn);
            //TODO 处理查询的交易状态
            String str = JSON.toJSONString(result);
            log.info("Stripe check payout result: {}", str);
            output.setStatus(CallChannelStatus.SUCC);
            output.setTransactionId(result.getId());
            output.setResult(str);
        } catch (StripeException e) {
            log.error("Stripe check payout error", e);
            output.setStatus(CallChannelStatus.UNKNOW);
            output.setResult(e.getMessage());
        }
        return output;
    }

    @Override
    public ChannelCheckOutput checkPayment(@NonNull ChannelCheckPaymentInput input) {
        ChannelCheckOutput output = new ChannelCheckOutput();
        PaymentIntent result = null;
        try {
            result = paymentIntentRetrieve(input.getBatchId());
            if ("succeeded".equals(result.getStatus())) {
                output.setBatchId(result.getId());
                output.setTransactionId(result.getId());
                output.setChannelEvent(TradeEvent.PAYMENT);
                output.setStatus(CallChannelStatus.SUCC);
            }
        } catch (Exception e) {
            log.error("Stripe check payment error", e);
        }
        String str = JSON.toJSONString(result);
        log.info("Stripe check payment result: {}", str);
        output.setResult(str);
        return output;
    }

    @Override
    public ChannelCheckOutput checkRefund(ChannelCheckRefundInput input) {
        return null;
    }

    @Override
    public ChannelPaymentOutput createSubPayment(String priceId, String customerId, String successUrl, String cancelUrl, Long trialPeriodDays) {
        SessionCreateParams params = SessionCreateParams.builder()
                .addLineItem(
                        SessionCreateParams.LineItem.builder()
                                .setPrice(priceId)
                                .setQuantity(1L)
                                .build())
                .setMode(SessionCreateParams.Mode.SUBSCRIPTION)
                .setCustomer(customerId)
                .setSuccessUrl(successUrl)
                .setCancelUrl(cancelUrl)
                //设置30分钟后过期（不设置，默认为24小时）
                .setExpiresAt(Instant.now().plusSeconds(30 * 60).getEpochSecond())
                .build();
        if(trialPeriodDays > 0) {
            params = SessionCreateParams.builder()
                    .addLineItem(
                            SessionCreateParams.LineItem.builder()
                                    .setPrice(priceId)
                                    .setQuantity(1L)
                                    .build())
                    //设置试用天数
                    .setSubscriptionData(SessionCreateParams.SubscriptionData.builder().setTrialPeriodDays(trialPeriodDays).build())
                    .setMode(SessionCreateParams.Mode.SUBSCRIPTION)
                    .setCustomer(customerId)
                    .setSuccessUrl(successUrl)
                    .setCancelUrl(cancelUrl)
                    //设置30分钟后过期（不设置，默认为24小时）
                    .setExpiresAt(Instant.now().plusSeconds(30 * 60).getEpochSecond())
                    .build();
        }
        Session session = null;
        try {
            session = Session.create(params);
        } catch (StripeException e) {
            log.error("===> createSubPayment error : ", e);
            ToastResultCode.SYSTEM_EXCEPTION.check(true);
        }
        log.info("===> createSubPayment session : {}", session);
        ChannelPaymentOutput output = new ChannelPaymentOutput();
        output.setPaymentUrl(session.getUrl());
        output.setTransactionId(session.getId());
        output.setBatchId(session.getId());
        return output;
    }


    @Override
    public String retrieveSessionUrl(String sessionId) {
        try {
            if(StringUtils.isEmpty(sessionId)) {
                return null;
            }
            // 检索 Session
            Session retrievedSession = Session.retrieve("cs_test_a1jbDwOV6REEPkfJF2MndM9IYdZ9DSrcGfQwqz9ELYoOiEpOIWXoV9aBfU");
            // 检查状态
            String status = retrievedSession.getStatus();
            if ("open".equals(retrievedSession.getStatus())) {
                return retrievedSession.getUrl();
            }
        } catch (StripeException e) {
            System.err.println("检索失败: " + e.getMessage());
            // 如果 Session 不存在或其他错误，URL 也无效
        }
        return null;
    }

    @Override
    public SubscriptionBo checkSubPayment(ChannelSubPayoutInput input) {
        try {
            //查询最新的一条活跃数据
            SubscriptionListParams params = SubscriptionListParams.builder()
                    .setCustomer(input.getCustomerId())
                    .setLimit(1L)
                    .setStatus(SubscriptionListParams.Status.ACTIVE)
                    .build();
            List<Subscription> list = Subscription.list(params).getData();
            //查询最新一条试用的数据
            SubscriptionListParams params1 = SubscriptionListParams.builder()
                    .setCustomer(input.getCustomerId())
                    .setLimit(1L)
                    .setStatus(SubscriptionListParams.Status.TRIALING)
                    .build();
            List<Subscription> list1 = Subscription.list(params1).getData();
            list.addAll(list1);
            if(CollectionUtils.isEmpty(list)) {
                return null;
            }
            log.info("===> checkSubPayment list : {}", list);
            List<SubscriptionBo> boList = Lists.newArrayList();
            for (Subscription subscription : list) {
                SubscriptionBo bo = new SubscriptionBo();
                bo.setId(subscription.getId());
                bo.setCustomer(subscription.getCustomer());
                bo.setCurrentPeriodStart(subscription.getCurrentPeriodStart());
                bo.setCurrentPeriodEnd(subscription.getCurrentPeriodEnd());
                //获取是否自动续订
                bo.setCancelAtPeriodEnd(subscription.getCancelAtPeriodEnd());
                bo.setStatus(subscription.getStatus());
                //获取价格
                List<SubscriptionItem> itemList = subscription.getItems().getData();
                for (SubscriptionItem item : itemList) {
                    bo.setPriceId(item.getPrice().getId());
                    boList.add(bo);
                }
            }
            if(CollectionUtils.isNotEmpty(boList)) {
                //排序处理，按照时间的降序排，取过期时间最长的一条数据
                boList.sort((s2, s1) -> s1.getCurrentPeriodEnd().compareTo(s2.getCurrentPeriodEnd()));
            }
            log.info("===> checkSubPayment boList : {}", boList);
            return CollectionUtils.isEmpty(boList) ? null : boList.get(0);
        } catch (StripeException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Long calculatePaymentFee(@NonNull Long productAmount) {
        return new BigDecimal(productAmount).multiply(stripeConfig.getPaymentFeeRate()).setScale(0, BigDecimal.ROUND_UP).add(new BigDecimal(stripeConfig.getPaymentFeeBase())).longValue();
    }

    @Override
    public ChannelCreateCustomerOutput createCustomer(@NonNull ChannelCreateCustomerInput input) {
        Map<String, String> metadata = Maps.newHashMap();
        metadata.put("userId", input.getUserId().toString());
        CustomerCreateParams customerParams = CustomerCreateParams.builder()
                .setName(input.getName())
                .setEmail(input.getEmail())
                .setMetadata(metadata)
                .build();
        Customer customer = null;
        try {
            customer = Customer.create(customerParams);
        } catch (StripeException e) {
            log.error("==> Stripe createCustomer error", e);
            ToastResultCode.SYSTEM_EXCEPTION.check(true);
        }
        ChannelCreateCustomerOutput output = new ChannelCreateCustomerOutput();
        output.setOpenId(customer.getId());
        output.setResult(JSON.toJSONString(customer));
        return output;
    }

    @Override
    public ChannelCancelOutput cancel(@NonNull String batchId) {
        ChannelCancelOutput output = new ChannelCancelOutput();
        PaymentIntent result = null;
        try {
            PaymentIntent paymentIntent = paymentIntentRetrieve(batchId);
            result = paymentIntent.cancel();
            output.setStatus(CallChannelStatus.CANCEL);
        } catch (StripeException e) {
            log.error("Stripe retrieve error", e);
        }
        String str = JSON.toJSONString(result);
        log.info("Stripe cancel result: {}", str);
        output.setResult(str);
        return output;
    }

    @Override
    public ChannelRefundOutput refund(@NonNull ChannelRefundInput input) {
        ChannelRefundOutput output = new ChannelRefundOutput();
        try {
            Refund result = Refund.create(RefundCreateParams.builder()
                    .setPaymentIntent(input.getTransactionId())
                    .build());
            String str = JSON.toJSONString(result);
            output.setResult(str);
            log.info("Stripe refund result: {}", str);
            if ("succeeded".equals(result.getStatus())) {
                output.setBatchId(result.getId());
                output.setTransactionId(result.getId());
                output.setStatus(CallChannelStatus.SUCC);
            } else {
                output.setStatus(CallChannelStatus.FAIL);
            }
        } catch (Exception e) {
            log.error("Stripe refund error", e);
            throw new BizException("", e.getMessage(), e);
        }
        return output;
    }

    @Override
    public ChannelWebhookOutput webhookAfterHandler(@NonNull ChannelWebhookInput input) {
        String payload = (String) input.getPayload();
        String signature = (String) input.getSignature();
        ChannelWebhookOutput output = new ChannelWebhookOutput();
        try {
            Event event = Webhook.constructEvent(payload, signature, stripeConfig.getWebhookSecret());
            EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();
            StripeObject stripeObject = dataObjectDeserializer.getObject().get();
            String str = stripeObject.toJson();
            log.info("Stripe webhook result: {}", str);
            output.setResult(str);
            JSONObject jsonObject = JSONObject.parseObject(str);
            output.setBatchId(jsonObject.getString("id"));

            if ("charge.succeeded".equals(event.getType()) || "payment_intent.succeeded".equals(event.getType())) {
                output.setChannelEvent(TradeEvent.PAYMENT);
                output.setStatus(CallChannelStatus.SUCC);
            } else if ("transfer.paid".equals(event.getType())) {
                output.setChannelEvent(TradeEvent.WITHDRAW);
                output.setStatus(CallChannelStatus.SUCC);
            } else if ("payment_method.attached".equals(event.getType())) {
                PaymentMethod paymentMethod = (PaymentMethod) stripeObject;
                PaymentMethod.Card card = paymentMethod.getCard();
                String openId = paymentMethod.getCustomer();
                Long accountId = payAccountFundThirdService.getAccountIdByOpenId(openId, ThirdAccountType.STRIPE_PAYMENT);

            }
        } catch (Exception e) {
            log.error("Stripe webhook签名校验异常", e);
            throw new SysException("", e.getMessage(), e);
        }
        return output;
    }

    @Override
    public ChannelBindBankCardOutput bindBankCard(ChannelBindBankCardInput input) {
        //TODO 创建 三方账号

        return null;
    }

    @Override
    public ChannelBindPaypalOutput bindPayPal(ChannelBindPaypalInput input) {
        return null;
    }

    @Override
    public ChannelBindBankAccountOutput bindBankAccount(ChannelBindBankAccountInput input) {
        return null;
    }

    @Override
    public Long getPaymentFeeBase() {
        return null;
    }

    @Override
    public BigDecimal getPaymentFeeRate() {
        return null;
    }

    @Override
    public WebhookBo webhookBeforeHandler(@NonNull ChannelWebhookInput input, String webhookSecret) {
        String payload = (String) input.getPayload();
        String signature = (String) input.getSignature();
        Event event;
        try {
            event = Webhook.constructEvent(payload, signature, webhookSecret);
            log.info("===> webhookBeforeHandler event : {}", JSONObject.toJSONString(event));
        } catch (SignatureVerificationException e) {
            log.error("Stripe webhook签名校验异常", e);
            throw new SysException("", e.getMessage(), e);
        }
        JSONObject jsonObject = JSONObject.parseObject(payload);
        WebhookBo bo = new WebhookBo();
        bo.setId(jsonObject.getJSONObject("data").getJSONObject("object").getString("id"));
        String customer = jsonObject.getJSONObject("data").getJSONObject("object").getString("customer");
        bo.setCustomerId(customer);
        bo.setEventType(event.getType());
        return bo;
    }

    @Override
    public Long calculateWithdrawFee(@NonNull Long occurAmount) {
        return MoneyUtils.calculateFee(occurAmount, stripeConfig.getWithdrawFeeRate()) + 25L;
    }

    private PaymentIntent paymentIntentRetrieve(String batchId) {
        try {
            PaymentIntent paymentIntent = PaymentIntent.retrieve(batchId);
            log.info("Stripe retrieve result: {}", JSONObject.toJSONString(paymentIntent));
            return paymentIntent;
        } catch (StripeException e) {
            log.error("Stripe retrieve error", e);
            throw new BizException("", e.getMessage(), e);
        }
    }

    @Override
    public void afterPropertiesSet() {
        Stripe.apiKey = stripeConfig.getApiKey();
    }

}
