package com.izouma.nineth.service;

import com.izouma.nineth.annotations.RedisLock;
import com.izouma.nineth.config.Constants;
import com.izouma.nineth.config.RedisKeys;
import com.izouma.nineth.domain.*;
import com.izouma.nineth.dto.PageQuery;
import com.izouma.nineth.enums.*;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.repo.*;
import com.izouma.nineth.service.sms.SmsService;
import com.izouma.nineth.utils.JpaUtils;
import com.izouma.nineth.utils.SecurityUtils;
import com.izouma.nineth.utils.SnowflakeIdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Page;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.persistence.Transient;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class AuctionOrderService {

    @Autowired
    private AuctionOrderRepo              auctionOrderRepo;
    @Autowired
    private SysConfigService              sysConfigService;
    @Autowired
    private UserRepo                      userRepo;
    @Autowired
    private AssetService                  assetService;
    @Autowired
    private AuctionActivityRepo           auctionActivityRepo;
    @Autowired
    private AuctionRecordRepo             auctionRecordRepo;
    @Autowired
    private AssetRepo                     assetRepo;
    @Autowired
    private UserAddressRepo               userAddressRepo;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private SnowflakeIdWorker             snowflakeIdWorker;
    @Autowired
    private AuctionPassRecordRepo         auctionPassRecordRepo;
    @Lazy
    @Autowired
    private OrderPayService               orderPayService;
    @Autowired
    private SmsService                    smsService;
    @Autowired
    private UserBalanceService            userBalanceService;
    @Autowired
    private ShowroomService               showroomService;
    @Autowired
    private CollectionRepo                collectionRepo;
    @Autowired
    private ShowroomRepo                  showroomRepo;
    @Autowired
    private UserBalanceRepo               userBalanceRepo;

    public Page<AuctionOrder> all(PageQuery pageQuery) {
        return auctionOrderRepo
                .findAll(JpaUtils.toSpecification(pageQuery, AuctionOrder.class), JpaUtils.toPageRequest(pageQuery));
    }

    @RedisLock("'createAuctionOrder::'+#auctionId")
    public AuctionOrder create(Long userId, Long auctionId, Long addressId, Long auctionRecordId, AuctionPaymentType type) {
        User user = userRepo.findById(userId).orElseThrow(new BusinessException("无用户"));

        AuctionActivity auction = auctionActivityRepo.findById(auctionId)
                .orElseThrow(new BusinessException("无拍卖信息"));

        if (!auction.isOnShelf()) {
            throw new BusinessException("拍卖已结束");
        }

        String status = auctionActivityRepo.getStatus(auctionId);
        switch (AuctionStatus.valueOf(status)) {
            case NOTSTARTED:
                throw new BusinessException("拍卖还未开始");
//            case PURCHASED:
//                throw new BusinessException("拍卖成交中");
            case PASS:
                throw new BusinessException("已经流拍");
            case FINISH:
                throw new BusinessException("拍卖已结束");
            case FIXED_PRICE_PURCHASED:
                if (AuctionPaymentType.FIXED_PRICE.equals(type)) {
                    throw new BusinessException("一口价成交中");
                }
        }

        if (user.getId().equals(auction.getSellerId())) {
            throw new BusinessException("不可自己出价自己");
        }

        if (AuctionPaymentType.PURCHASE_PRICE.equals(type)) {
            if (auction.getEndTime().isAfter(LocalDateTime.now())) {
                throw new BusinessException("拍卖还未结束");
            }
            AuctionRecord record = auctionRecordRepo.findTopByAuctionIdAndUserIdOrderByIdDesc(auctionId, userId);
            if (ObjectUtils.isEmpty(record) || !record.isPayDeposit()) {
                throw new BusinessException("未支付保证金");
            }
            if (record.getBidderPrice().compareTo(auction.getPurchasePrice()) != 0) {
                throw new BusinessException("与成交价不否");
            }
            int time = sysConfigService.getInt("auction_cancel_time");
            if (LocalDateTime.now().isAfter(auction.getEndTime().plusMinutes(time))) {
                throw new BusinessException("超过支付时长");
            }

//            AuctionOrder order = auctionOrderRepo
//                    .findByUserIdAndAuctionIdAndPaymentTypeAndStatusIn(user.getId(), auction.getId(),
//                            AuctionPaymentType.PURCHASE_PRICE, Arrays
//                                    .asList(AuctionOrderStatus.NOT_PAID, AuctionOrderStatus.FINISH));
//            if (ObjectUtils.isNotEmpty(order)) {
//                if (AuctionOrderStatus.FINISH.equals(order.getStatus())) {
//                    throw new BusinessException("成交金已交过，无需再交");
//                }
//                throw new BusinessException("成交金未支付，取消后再支付");
//            }

        } else {
            if (auction.getEndTime().isBefore(LocalDateTime.now())) {
                throw new BusinessException("拍卖已结束");
            }
            if (AuctionPaymentType.DEPOSIT.equals(type)) {
                return this.createDeposit(user, auction);
            }
        }

        UserAddress userAddress = null;
        if (addressId != null) {
            userAddress = userAddressRepo.findById(addressId).orElseThrow(new BusinessException("地址信息不存在"));
        }

        try {

            auctionActivityRepo.updateStatus(auctionId, AuctionPaymentType.FIXED_PRICE
                    .equals(type) ? AuctionStatus.FIXED_PRICE_PURCHASED : AuctionStatus.PURCHASED);

            if (AuctionSource.TRANSFER.equals(auction.getSource())) {
                Asset asset = assetRepo.findById(auction.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
                asset.setStatus(AssetStatus.AUCTION_TRADING);
                assetRepo.save(asset);
            }

            BigDecimal price = AuctionPaymentType.FIXED_PRICE.equals(type) ? auction.getFixedPrice() : auction
                    .getPurchasePrice();

            AuctionOrder order = AuctionOrder.builder()
                    .id(snowflakeIdWorker.nextId())
                    .auctionId(auction.getId())
                    .userId(user.getId())
                    .nickname(user.getNickname())
                    .paymentType(type)
                    .name(auction.getName())
                    .pic(auction.getPic())
                    .serviceCharge(auction.getServiceCharge())
                    .royalties(auction.getRoyalties())
                    .source(auction.getSource())
                    .price(price)
                    .totalPrice(price)
                    .auctionRecordId(auctionRecordId)
                    .status(AuctionOrderStatus.NOT_PAID)
                    .contactName(Optional.ofNullable(userAddress).map(UserAddress::getName).orElse(null))
                    .contactPhone(Optional.ofNullable(userAddress).map(UserAddress::getPhone).orElse(null))
                    .address(Optional.ofNullable(userAddress).map(UserAddress::getDetail).orElse(null))
                    .build();

            return auctionOrderRepo.save(order);
        } catch (Exception e) {
            auctionActivityRepo.updateStatus(auctionId, AuctionStatus.ONGOING);
            throw e;
        }
    }


    public AuctionOrder createDeposit(User user, AuctionActivity auction) {
        if (user.getId().equals(auction.getSellerId())) {
            throw new BusinessException("不可自己出价自己的");
        }

        //竞拍人绿魔卡余额限制
        BigDecimal minAmount = sysConfigService.getBigDecimal("auction_min_amount");
        if (!user.isWalletEnabled()) {
            throw new BusinessException("请开通绿魔卡，并充值" + minAmount + "元");
        } else {
            UserBalance userBalance = userBalanceRepo.findByUserId(user.getId()).orElse(new UserBalance(user.getId()));
            if (minAmount.compareTo(userBalance.getBalance()) > 0) {
                throw new BusinessException("绿魔卡余额不足" + minAmount + "元，请先充值");
            }
        }

        //保证金
//        BigDecimal deposit = sysConfigService.getBigDecimal("deposit");
        AuctionOrder order = auctionOrderRepo
                .findByUserIdAndAuctionIdAndPaymentTypeAndStatusIn(user.getId(), auction.getId(),
                        AuctionPaymentType.DEPOSIT, Arrays
                                .asList(AuctionOrderStatus.NOT_PAID, AuctionOrderStatus.FINISH));
        if (ObjectUtils.isNotEmpty(order)) {
            if (AuctionOrderStatus.FINISH.equals(order.getStatus())) {
                throw new BusinessException("保证金已交过，无需再交");
            }
            throw new BusinessException("保证金未支付，取消后再重新出价");
        }

        AuctionRecord auctionRecord = AuctionRecord.builder()
                .auctionId(auction.getId())
                .type(AuctionRecordType.DEPOSIT)
                .bidderPrice(auction.getDeposit())
                .auctionPic(null)
                .userId(SecurityUtils.getAuthenticatedUser().getId())
                .avatar(SecurityUtils.getAuthenticatedUser().getAvatar())
                .name(auction.getName())
                .purchased(false)
                .auctionType(auction.getAuctionType())
                .build();
        AuctionRecord record = auctionRecordRepo.save(auctionRecord);

        order = AuctionOrder.builder()
                .id(snowflakeIdWorker.nextId())
                .auctionId(auction.getId())
                .userId(user.getId())
                .nickname(user.getNickname())
                .paymentType(AuctionPaymentType.DEPOSIT)
                .name(auction.getName())
                .pic(auction.getPic())
                .serviceCharge(auction.getServiceCharge())
                .royalties(auction.getRoyalties())
                .source(auction.getSource())
                .price(auction.getDeposit())
                .totalPrice(auction.getDeposit())
                .auctionRecordId(record.getId())
                .status(AuctionOrderStatus.NOT_PAID)
                .build();


        return auctionOrderRepo.save(order);

    }

    @Transient
    public void notify(Long id, PayMethod payMethod, String transactionId) {
        AuctionOrder order = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("无记录"));
        if (!order.getStatus().equals(AuctionOrderStatus.NOT_PAID)) {
            throw new BusinessException("订单已处理");
        }
        AuctionActivity auction = auctionActivityRepo.findById(order.getAuctionId())
                .orElseThrow(new BusinessException("无拍卖活动"));

        if (auction.getAuctionType().equals(AuctionType.ENTITY)) {
            order.setStatus(AuctionOrderStatus.DELIVERY);
        } else {
            order.setStatus(AuctionOrderStatus.FINISH);
        }
        order.setPayMethod(payMethod);
        order.setTransactionId(transactionId);
        order.setPayTime(LocalDateTime.now());
        //存订单
        auctionOrderRepo.save(order);

        if (AuctionPaymentType.DEPOSIT.equals(order.getPaymentType())) {
            //改出价记录表
            AuctionRecord record = auctionRecordRepo.findById(order.getAuctionRecordId())
                    .orElseThrow(new BusinessException("无出价记录"));
            record.setPayDeposit(true);
            auctionRecordRepo.save(record);
            return;
        }

        //此拍卖结束
        //修改买家和成交价
        auction.setStatus(AuctionStatus.FINISH);
        auction.setPurchaserId(order.getUserId());
        auction.setPurchasePrice(order.getTotalPrice());
        auctionActivityRepo.save(auction);
        log.info("拍卖结束：{}", order.getAuctionId());

        if (AuctionSource.TRANSFER.equals(order.getSource())) {
            Asset asset = assetRepo.findById(auction.getAssetId()).orElseThrow(new BusinessException("资产不存在"));
            if (asset.isPublicShow()) {
                //取消公开展示
                assetService.cancelPublic(asset);
            }
            User user = userRepo.findById(order.getUserId()).orElseThrow(new BusinessException("无用户"));
            //转让流程
            assetService.transfer(asset, order.getTotalPrice(), user, TransferReason.AUCTION, order.getId());

            // 发送短信提醒用户转让成功
            if (asset.getUserId() != null) {
                smsService.sellOut(userRepo.findPhoneById(asset.getUserId()));
            }

            //用户冲余额
            BigDecimal amount = order.getTotalPrice()
                    .multiply(BigDecimal.valueOf(100 - order.getRoyalties() - order.getServiceCharge()))
                    .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);

            userBalanceService.addBalance(asset.getUserId(), amount, id, BalanceType.AUCTION);

        }

        //改出价记录表为竞得（一口价无出价表）
        if (ObjectUtils.isNotEmpty(order.getAuctionRecordId())) {
            AuctionRecord record = auctionRecordRepo.findById(order.getAuctionRecordId())
                    .orElseThrow(new BusinessException("无出价记录"));
            record.setPurchased(true);
            auctionRecordRepo.save(record);
        }


        //退保证金
        List<AuctionOrder> orders = auctionOrderRepo.findAllByAuctionIdAndPaymentTypeAndStatus(order.getAuctionId(),
                AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH);
        //退款
        orders.forEach(this::refund);
    }

    public void cancel(AuctionOrder order) {
        if (!getOrderLock(order.getId())) {
            log.error("订单取消失败 {}, redis锁了", order.getId());
            return;
        }

        boolean isRefund = false;
        try {
            AuctionActivity auction = auctionActivityRepo.findById(order.getAuctionId())
                    .orElseThrow(new BusinessException("无记录"));

            if (AuctionPaymentType.PURCHASE_PRICE.equals(order.getPaymentType())) {
                //如果是拍卖，需获取取消订单的时长
                int time = sysConfigService.getInt("auction_cancel_time");
                if (LocalDateTime.now().isAfter(auction.getEndTime().plusMinutes(time))) {
                    //超过支付时长
                    log.info("取消订单流拍：{}", auction.getId());
                    auctionActivityRepo.updateStatus(order.getAuctionId(), AuctionStatus.PASS);
                    //添加到流拍记录表里
                    auctionPassRecordRepo.save(AuctionPassRecord.builder()
                            .auctionId(auction.getId())
                            .userId(auction.getPurchaserId())
                            .purchasePrice(auction.getPurchasePrice())
                            .build());
                    //流拍不退自己的保证金
                    isRefund = true;

                    if (AuctionSource.TRANSFER.equals(order.getSource())) {
                        //改回资产状态
                        Asset asset = assetRepo.findById(auction.getAssetId())
                                .orElseThrow(new BusinessException("资产不存在"));
                        asset.setStatus(AssetStatus.NORMAL);
                        assetRepo.save(asset);
                    }

                }
            } else if (AuctionPaymentType.DEPOSIT.equals(order.getPaymentType())) {
                //删除出价记录
                auctionRecordRepo.softDelete(order.getAuctionRecordId());
            } else {
                //拍卖是否结束
                if (LocalDateTime.now().isBefore(auction.getEndTime())) {
                    //返回拍卖状态
                    auctionActivityRepo.updateStatus(order.getAuctionId(), AuctionStatus.ONGOING);
                } else {
                    //最后一个出价的人得
                    auctionActivityRepo.updateStatus(order.getAuctionId(), AuctionStatus.PURCHASED);
                }
            }

            order.setStatus(AuctionOrderStatus.CANCELLED);
            order.setCancelTime(LocalDateTime.now());
            auctionOrderRepo.save(order);
            log.info("取消订单{}", order.getId());

        } catch (Exception e) {
            log.error("订单取消错误 orderId: " + order.getId(), e);
        }

        if (isRefund) {
            //退其余保证金
            List<AuctionOrder> orders = auctionOrderRepo
                    .findAllByAuctionIdAndPaymentTypeAndStatus(order.getAuctionId(),
                            AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH);
            //退款
            orders.stream()
                    .filter(o -> !order.getUserId().equals(o.getUserId()))
                    .forEach(this::refund);
        }
        releaseOrderLock(order.getId());
    }

    /**
     * 退款方法
     *
     * @param order 订单
     */
    private void refund(AuctionOrder order) {
        log.info("退款拍卖保证金订单{}", order.getId());
        PayMethod payMethod = order.getPayMethod();
        if (PayMethod.ALIPAY == payMethod) {
            if (StringUtils.length(order.getTransactionId()) == 28) {
                payMethod = PayMethod.HMPAY;
            } else if (StringUtils.length(order.getTransactionId()) == 30) {
                payMethod = PayMethod.SANDPAY;
            }
        }
        try {
            switch (payMethod) {
                case HMPAY:
                    orderPayService.refund(order.getId()
                            .toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.HM);
                    log.info("退款成功{}", order.getId());
                    break;
                case SANDPAY:
                    orderPayService.refund(order.getId()
                            .toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.SAND);
                    log.info("退款成功{}", order.getId());
                    break;
                case PAYEASE:
                    orderPayService.refund(order.getId()
                            .toString(), order.getTransactionId(), order.getTotalPrice(), Constants.PayChannel.PE);
                    log.info("退款成功{}", order.getId());
                    break;
                case BALANCE:
                    userBalanceService.addBalance(order.getUserId(), order.getTotalPrice(), order.getId(), BalanceType.AUCTION_RETURN);
                    log.info("退款成功{}", order.getId());
                    break;
            }
            order.setRefundTime(LocalDateTime.now());
            order.setStatus(AuctionOrderStatus.REFUNDED);
            auctionOrderRepo.save(order);
        } catch (Exception e) {
            log.error("拍卖保证金订单退款失败 {} ", order.getId(), e);
            order.setRefundTime(LocalDateTime.now());
            order.setStatus(AuctionOrderStatus.REFUNDING);
            auctionOrderRepo.save(order);
        }
    }

    public boolean getOrderLock(Long orderId) {
        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.AUCTION_ORDER_LOCK + orderId);
        Boolean flag = ops.setIfAbsent(1, 1, TimeUnit.DAYS);
        return Boolean.TRUE.equals(flag);
    }

    public void releaseOrderLock(Long orderId) {
        redisTemplate.delete(RedisKeys.AUCTION_ORDER_LOCK + orderId);
    }

    public void passCancel(AuctionOrder order) {
        if (!getOrderLock(order.getId())) {
            log.error("订单取消失败 {}, redis锁了", order.getId());
            return;
        }

        try {
            order.setStatus(AuctionOrderStatus.CANCELLED);
            order.setCancelTime(LocalDateTime.now());
            auctionOrderRepo.save(order);
            log.info("取消订单{}", order.getId());

        } catch (Exception e) {
            log.error("订单取消错误 orderId: " + order.getId(), e);
        }
        releaseOrderLock(order.getId());
    }

    @Scheduled(cron = "0 0/1 * * * ?")
    public void passOverTimeAuction() {
        List<AuctionActivity> purchased = auctionActivityRepo.findAllByStatus(AuctionStatus.PURCHASED);
        if (purchased != null) {
            int time = sysConfigService.getInt("auction_cancel_time");
            purchased.forEach(act -> {
                if (LocalDateTime.now().isAfter(act.getEndTime().plusMinutes(time))) {
                    List<AuctionOrder> auctionOrders = auctionOrderRepo.findAllByAuctionIdAndPaymentTypeAndStatus(act
                            .getId(), AuctionPaymentType.PURCHASE_PRICE, AuctionOrderStatus.NOT_PAID);
                    //创建了订单
                    auctionOrders.forEach(this::passCancel);

                    act.setStatus(AuctionStatus.PASS);
                    auctionActivityRepo.save(act);
                    log.info("拍卖定时任务流拍{}", act.getId());

                    if (AuctionSource.TRANSFER.equals(act.getSource())) {
                        //改回资产状态
                        Asset asset = assetRepo.findById(act.getAssetId())
                                .orElseThrow(new BusinessException("资产不存在"));
                        asset.setStatus(AssetStatus.NORMAL);
                        assetRepo.save(asset);
                    }

                    //退其余保证金
                    List<AuctionOrder> orders = auctionOrderRepo
                            .findAllByAuctionIdAndPaymentTypeAndStatus(act.getId(),
                                    AuctionPaymentType.DEPOSIT, AuctionOrderStatus.FINISH);
                    //退款
                    orders.stream()
                            .filter(o -> !act.getPurchaserId().equals(o.getUserId()))
                            .forEach(this::refund);

                    //添加到流拍记录表里
                    auctionPassRecordRepo.save(AuctionPassRecord.builder()
                            .auctionId(act.getId())
                            .userId(act.getPurchaserId())
                            .purchasePrice(act.getPurchasePrice())
                            .build());
                }
            });
        }
    }

    /**
     * 发货
     *
     * @param id        编号
     * @param courierId 快递单号
     */
    public void dispatch(Long id, String courierId) {
        AuctionOrder auctionOrder = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在"));
        auctionOrder.setStatus(AuctionOrderStatus.RECEIVE);
        auctionOrder.setCourierId(courierId);
        auctionOrderRepo.save(auctionOrder);
    }

    /**
     * 订单
     *
     * @param id 编号
     */
    public void finish(Long id) {
        AuctionOrder auctionOrder = auctionOrderRepo.findById(id).orElseThrow(new BusinessException("铸造订单不存在"));
        auctionOrder.setStatus(AuctionOrderStatus.FINISH);
        auctionOrderRepo.save(auctionOrder);
    }

    public void privilege(AuctionOrder order, User user) {
        if (showroomRepo.findByUserIdAndType(order.getUserId(), "AUCTION").isEmpty()) {
            //Bidder特殊拍卖展厅服务 创建一个bidder展厅藏品
            Long collectionId = (long) sysConfigService.getInt("bidder_collection_id");
            List<Asset> assets = assetRepo.findAllByUserIdAndCollectionIdAndStatus(order.getUserId(), collectionId, AssetStatus.NORMAL);
            Asset asset;
            if (assets.isEmpty()) {
                Collection collection = collectionRepo.findById(collectionId).orElseThrow(new BusinessException("无藏品"));
                if (!CollectionType.SHOWROOM.equals(collection.getType())) {
                    throw new BusinessException("不是展厅藏品");
                }
                //创建资产
                asset = assetService.createAsset(collection, user, order.getId(), BigDecimal.ZERO, "拍卖赠送",
                        null, false);
            } else {
                asset = assets.get(0);
            }
            //创建展厅
            showroomService.save(asset, "AUCTION");
        }

        //一个月的优先拍卖权
        PurchaserPrivilege.builder()
                .userId(order.getUserId())
                .title("元宇宙Bidder")
                .priority(true)
                .priorityExpireAt(LocalDateTime.now().plusMonths(1))
                .build();


        //前20名分钱
        BigDecimal totalPrice = order.getTotalPrice();
        //手续费
        int auctionServiceCharge = sysConfigService.getInt("auction_service_charge");
        BigDecimal serviceCharge = totalPrice.multiply(new BigDecimal(auctionServiceCharge))
                .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);

        //奖励费用
        BigDecimal subtract = totalPrice.subtract(serviceCharge);
        int auctionReward = sysConfigService.getInt("auction_reward");
        BigDecimal reward = subtract.multiply(new BigDecimal(auctionReward))
                .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
        List<Long> records = auctionRecordRepo.findByAuctionId(order.getAuctionId(), 20);
        BigDecimal everyReward = reward.divide(new BigDecimal(records.size()), 2, RoundingMode.HALF_UP);
        //分奖励
        records.forEach(userId -> userBalanceService.addBalance(userId, everyReward, order.getId(), BalanceType.REWARD));


        //拍卖者所得
        BigDecimal amount = totalPrice.subtract(serviceCharge).subtract(reward);
        userBalanceService.addBalance(order.getUserId(), amount, order.getId(), BalanceType.AUCTION);

    }

}
