package com.sonic.lion.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sonic.lion.dao.AppStoreProductDao;
import com.sonic.lion.dao.PayCallChannelRecordDao;
import com.sonic.lion.dao.UserSubscriptionDao;
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.UserSubscription;
import com.sonic.lion.domain.input.ChannelCreateCustomerInput;
import com.sonic.lion.domain.input.ChannelSubPayoutInput;
import com.sonic.lion.domain.input.CreateSubscribeCheckSessionInput;
import com.sonic.lion.domain.output.ChannelCreateCustomerOutput;
import com.sonic.lion.domain.output.ChannelPaymentOutput;
import com.sonic.lion.domain.output.CreateSubscribeCheckoutSessionOutput;
import com.sonic.lion.domain.resp.AppProductInfoOutput;
import com.sonic.lion.enums.PayChannel;
import com.sonic.lion.enums.ThirdAccountType;
import com.sonic.lion.enums.ToastResultCode;
import com.sonic.lion.service.*;
import com.sonic.lion.utils.RedisKeyUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class StripeSubscribeServiceImpl implements StripeSubscribeService {

    @Autowired
    private AppStoreProductDao appStoreProductDao;
    @Autowired
    private PayAccountFundThirdService payAccountFundThirdService;
    @Autowired
    @Qualifier("stripeService")
    private PayChannelService stripeService;
    @Autowired
    private PayCallChannelRecordDao payCallChannelRecordDao;
    @Autowired
    private UserSubscriptionService userSubscriptionService;
    @Autowired
    private UserSubscriptionDao userSubscriptionDao;
    @Autowired
    private RedisKeyUtils redisKeyUtils;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private CommonMessageService commonMessageService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public CreateSubscribeCheckoutSessionOutput createSubscribeCheckoutSession(Long userId, CreateSubscribeCheckSessionInput input) {
        //判断当前用户是否已经在订阅中了，如果已经订阅了则不能获取订阅链接
        int count = userSubscriptionDao.selectCount(Wrappers.<UserSubscription>lambdaQuery()
                .eq(UserSubscription::getUserId, userId)
                .eq(UserSubscription::getPlatform, PayChannel.STRIPE.name())
                .gt(UserSubscription::getExpTime, LocalDateTime.now()));
        ToastResultCode.SUBSCRIBED_NO_DUPLICATE.check(count > 0);

        //查询三方表中绑定的stripe的客户id
        PayAccountFundThird payAccountFundThird = payAccountFundThirdService.getByAccountIdAndAppType(userId, ThirdAccountType.STRIPE_PAYMENT);
        if(payAccountFundThird == null || StringUtils.isEmpty(payAccountFundThird.getOpenId())) {
            //在stripe创建客户号，并保存到数据库中
            ChannelCreateCustomerInput createCustomerInput = ChannelCreateCustomerInput.builder()
                    .userId(userId)
                    .payChannel(PayChannel.STRIPE).build();
            ChannelCreateCustomerOutput channelCreateCustomerOutput = stripeService.createCustomer(createCustomerInput);
            payAccountFundThird = payAccountFundThirdService.bindStripe(userId, channelCreateCustomerOutput.getOpenId());
        }
        String customerId = payAccountFundThird.getOpenId();
        //不生成多个支付连接，查询连接是否存在、链接是否失效。然后再返回给前端
        String sessionId = stringRedisTemplate.opsForValue().get(redisKeyUtils.subSessionIdCacheKey(userId));
        String url = stripeService.retrieveSessionUrl(sessionId);
        if(StringUtils.isNotEmpty(sessionId) && StringUtils.isEmpty(url)) {
            //删除缓存
            stringRedisTemplate.delete(redisKeyUtils.subSessionIdCacheKey(userId));
        }
        if(StringUtils.isNotEmpty(url)) {
            return CreateSubscribeCheckoutSessionOutput.builder().payUrl(url).build();
        }
        //查询数据库获得 订阅价格
        AppProductInfoOutput appProduct = appStoreProductDao.queryAppProductInfo(input.getSubProductId());
        //商品不存在，直接抛出异常
        ToastResultCode.SUB_PRODUCT_NOT_FOUND.check(appProduct == null);
        //设置免费试用天数（当用户首次订阅时才享受3天免费）
        Long trialPeriodDays = userSubscriptionDao.getByUserId(userId) == null ? appProduct.getFreeDays() : 0L;
        //创建订阅的结帐会话并生成支付url返回
        ChannelPaymentOutput paymentOutput = stripeService.createSubPayment(input.getSubProductId(), customerId, input.getReturnUrl(), input.getCancelUrl(), trialPeriodDays);
        //存储到redis缓存中，30分钟后过期
        stringRedisTemplate.opsForValue().set(redisKeyUtils.subSessionIdCacheKey(userId), paymentOutput.getBatchId(), 30 * 60, TimeUnit.SECONDS);
        return CreateSubscribeCheckoutSessionOutput.builder().payUrl(paymentOutput.getPaymentUrl()).build();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void subscriptionHandler(Long notifyId, WebhookBo webhookBo) {
        //查询关系数据，没有绑定关系的话直接抛出异常
        Long userId = payAccountFundThirdService.getAccountIdByOpenId(webhookBo.getCustomerId(), ThirdAccountType.STRIPE_PAYMENT);
        if(userId == null) {
            log.error("===> subscriptionHandler userId is null");
            return;
        }
        //处理用户订阅成功事件
        doWhenPay(notifyId, userId, webhookBo);
        //处理用户取消订阅事件
        doWhenCancel(notifyId, userId, webhookBo);
    }

    /**
     * 处理用户订阅成功事件
     * @param notifyId
     * @param userId
     * @param webhookBo
     */
    private void doWhenPay(Long notifyId, Long userId, WebhookBo webhookBo) {
        //非创建、更新状态不进行处理
        if (!"customer.subscription.created".equals(webhookBo.getEventType()) &&
                !"customer.subscription.updated".equals(webhookBo.getEventType())) {
            return;
        }
        try {
            //从stripe中查询当前用户的订阅到期时间
            SubscriptionBo subscriptionBo = stripeService.checkSubPayment(ChannelSubPayoutInput.builder().customerId(webhookBo.getCustomerId()).build());
            log.info("===> webhook subscriptionBo : {}", subscriptionBo);
            if(subscriptionBo == null || subscriptionBo.getCurrentPeriodEnd() == null
                    || System.currentTimeMillis() / 1000 > subscriptionBo.getCurrentPeriodEnd().intValue()) {
                //没有过期时间，说明用户没有订阅，不做任何处理，直接关闭掉当前的回调
                userSubscriptionService.completeNotify(notifyId);
                return;
            }
            //处理扣费失败的场景
            if("past_due".equalsIgnoreCase(subscriptionBo.getStatus())) {
                //修改自动续订状态 为关闭
                userSubscriptionDao.updateAutoRenew(userId, false);
                //完成数据处理
                userSubscriptionService.completeNotify(notifyId);;
                //订阅扣费失败
                commonMessageService.memberRenewFail(userId);
                return;
            }
            //判断状态不为订阅、活跃状态的话不进行后续处理
            if(!"active".equalsIgnoreCase(subscriptionBo.getStatus()) && !"trialing".equalsIgnoreCase(subscriptionBo.getStatus())) {
                //完成数据处理
                userSubscriptionService.completeNotify(notifyId);;
                return;
            }
            //更新用户订阅相关的基础信息
            userSubscriptionService.bindStripe(subscriptionBo);
            //完成
            userSubscriptionService.completeNotify(notifyId);
            log.info("===========> subscriptionHandler success");
        } catch (Exception e) {
            log.error("===> webhook exception : ", e);
            userSubscriptionService.failNotify(notifyId);
            //抛出异常
            ToastResultCode.SYSTEM_EXCEPTION.check(true);
        }
    }

    private void doWhenCancel(Long notifyId, Long userId, WebhookBo webhookBo) {
        //订阅被取消或到期时触发，自动续订停止
        if(!webhookBo.getEventType().equalsIgnoreCase("customer.subscription.deleted")) {
            return;
        }
        //修改自动续订状态 为关闭
        userSubscriptionDao.updateAutoRenew(userId, false);
        //更新到期时间为当前时间
        userSubscriptionDao.updateExpTimeByUserId(userId, LocalDateTime.now());
        //完成数据
        userSubscriptionService.completeNotify(notifyId);
    }

}
