package com.izouma.nineth.service;

import cn.hutool.core.collection.CollUtil;
import com.izouma.nineth.config.RedisKeys;
import com.izouma.nineth.domain.Asset;
import com.izouma.nineth.domain.AuctionActivity;
import com.izouma.nineth.domain.User;
import com.izouma.nineth.dto.PageQuery;
import com.izouma.nineth.dto.auction.AuctionInputDTO;
import com.izouma.nineth.enums.*;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.repo.*;
import com.izouma.nineth.utils.JpaUtils;
import com.izouma.nineth.utils.SecurityUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.Page;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@AllArgsConstructor
public class AuctionActivityService {

    private final AuctionActivityRepo           auctionActivityRepo;
    private final AssetRepo                     assetRepo;
    private final UserRepo                      userRepo;
    private final PasswordEncoder               passwordEncoder;
    private final RedisTemplate<String, Object> redisTemplate;
    private final CacheService                  cacheService;
    private final TaskScheduler                 taskScheduler;
    private final Environment                   env;
    private final SysConfigService              sysConfigService;
    private final RockRecordService             rockRecordService;

    private final Map<Long, ScheduledFuture<?>> tasks = new HashMap<>();

    @PostConstruct
    public void init() {
        if (Arrays.asList(env.getActiveProfiles()).contains("dev")) {
            return;
        }
        List<AuctionActivity> activities = auctionActivityRepo.findByStartTimeBeforeAndStatus(LocalDateTime.now(),
                AuctionStatus.NOTSTARTED);
        for (AuctionActivity activity : activities) {
            onShelfTask(activity);
        }
    }

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

    public AuctionActivity createFromAsset(AuctionInputDTO dto) {
        Asset asset = assetRepo.findById(dto.getAssetId()).orElseThrow(new BusinessException("暂无"));

        //拍卖周期
        BigDecimal auctionCycle = sysConfigService.getBigDecimal("auction_cycle");

        AuctionActivity auctionActivity = new AuctionActivity();
        auctionActivity.setAuctionType(AuctionType.NFT);
        auctionActivity.setAssetId(dto.getAssetId());
        auctionActivity.setStatus(AuctionStatus.ONGOING);
        auctionActivity.setBids(0);
        auctionActivity.setCategory(asset.getCategory());
        auctionActivity.setEndTime(LocalDateTime.now()
                .plusHours(auctionCycle.multiply(new BigDecimal("24")).intValue()));
        auctionActivity.setDeposit(dto.getDeposit());
        if (Arrays.asList(env.getActiveProfiles()).contains("staging")) {
            auctionActivity.setEndTime(LocalDateTime.now().plusMinutes(8));
        }
        auctionActivity.setDetail(asset.getDetail());
        auctionActivity.setFixedPrice(dto.getFixedPrice());
        auctionActivity.setIncrement(dto.getIncrement());
        auctionActivity.setMinter(asset.getMinter());
        auctionActivity.setPic(asset.getPic());
        auctionActivity.setModel3d(asset.getModel3d());
        auctionActivity.setName(asset.getName());
        auctionActivity.setSeller(asset.getOwner());
        auctionActivity.setSellerId(asset.getOwnerId());
        auctionActivity.setStartTime(LocalDateTime.now());
        auctionActivity.setSource(AuctionSource.TRANSFER);
        //固定值 or 资产值
        auctionActivity.setServiceCharge(asset.getServiceCharge());
        auctionActivity.setRoyalties(asset.getRoyalties());

        auctionActivity.setStartingPrice(dto.getStartingPrice());
        auctionActivity.setHasFixedPrice(auctionActivity.getFixedPrice() != null);
        return save(auctionActivity, dto.getTradeCode());
    }


    public AuctionActivity save(AuctionActivity record, String tradeCode) {
        if (record.getSource().equals(AuctionSource.OFFICIAL)) {
            record.setStatus(AuctionStatus.NOTSTARTED);
        }
        if (record.getSource().equals(AuctionSource.TRANSFER) && !record.getSellerId().equals(9859L)) {
            User user = userRepo.findById(record.getSellerId()).orElseThrow(new BusinessException("无用户信息"));
            if (!passwordEncoder.matches(tradeCode, user.getTradeCode())) {
                throw new BusinessException("交易密码错误");
            }
            if (!AuthStatus.SUCCESS.equals(user.getAuthStatus())) {
                throw new BusinessException("未实名或实名未通过");
            }
            BigDecimal userBuy = rockRecordService.getRock(user.getId()).getRecord();
            BigDecimal num = sysConfigService.getBigDecimal("auction_lvzhoushi_num");
            if (userBuy.compareTo(num) < 0) {
                throw new BusinessException("绿洲石不足");
            }

            Asset asset = assetRepo.findById(record.getAssetId()).orElseThrow(new BusinessException("未找到该藏品"));
            if (!asset.getOwnerId().equals(SecurityUtils.getAuthenticatedUser().getId())) {
                throw new BusinessException("非本人藏品，无法操作.");
            }

            int holdDays;
            if (ObjectUtils.isEmpty(asset.getHoldDays())) {
                holdDays = sysConfigService.getInt("hold_days");
            } else {
                holdDays = asset.getHoldDays();
            }
            if (holdDays == 0 && AssetSource.OFFICIAL.equals(asset.getSource())) {
                BigDecimal officialConsignment = sysConfigService.getBigDecimal("OFFICIAL_CONSIGNMENT");
                //天转小时
                int hour = officialConsignment.multiply(new BigDecimal("24")).intValue();
                if (ChronoUnit.HOURS.between(asset.getCreatedAt(), LocalDateTime.now()) < hour) {
                    throw new BusinessException("需持有满" + hour + "小时后才能进行拍卖");
                }
            }
            if (ChronoUnit.DAYS.between(asset.getCreatedAt(), LocalDateTime.now()) < holdDays) {
                throw new BusinessException("需持有满" + holdDays + "天才能进行拍卖");
            }


            if (!asset.getStatus().equals(AssetStatus.NORMAL)) {
                throw new BusinessException("藏品状态异常，无法操作.");
            }
            if (asset.isPublicShow() || asset.isConsignment()) {
//                throw new BusinessException("藏品已寄售，取消寄售后再申请拍卖。");
                throw new BusinessException("请先下架藏品");
            }

            //是否二次拍卖
            List<AuctionActivity> activity = auctionActivityRepo.findByAssetId(asset.getId());
            if (CollUtil.isNotEmpty(activity)) {
                if (activity.stream().anyMatch(ac -> !AuctionStatus.PASS.equals(ac.getStatus()))) {
                    throw new BusinessException("已有拍卖");
                }
                log.info("下架流拍拍卖：assetId-{}", asset.getId());
                auctionActivityRepo.updateOnShelf(asset.getId(), false);
            }

            asset.setStatus(AssetStatus.AUCTIONING);
            assetRepo.save(asset);
        }

        //上架
        record.setOnShelf(true);
        AuctionActivity saved = auctionActivityRepo.save(record);
        if (saved.getStatus().equals(AuctionStatus.NOTSTARTED)) {
            onShelfTask(saved);
        }
        return saved;
    }

    public synchronized String changeStatus(Long id, AuctionStatus status) {
        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.AUCTION_STATUS + id);
        if (ops.get() == null) {
            Boolean success = ops.setIfAbsent(Optional.ofNullable(auctionActivityRepo.getStatus(id))
                    .orElse(AuctionStatus.NOTSTARTED.toString()), 7, TimeUnit.DAYS);
            log.info("创建redis拍卖活动状态：{}", success);
        }
        String stock = (String) ops.getAndSet(status.toString());
        syncStatus(id);
        return stock;
    }

    //    @Debounce(key = "#id", delay = 500)
    public void syncStatus(Long id) {
        String stock = (String) redisTemplate.opsForValue().get(RedisKeys.AUCTION_STATUS + id);
        if (stock != null) {
            log.info("同步拍卖活动状态信息{},{}", id, stock);
            auctionActivityRepo.updateStatus(id, AuctionStatus.valueOf(stock));
            cacheService.clearAuction(id);
        }
    }

    public synchronized String getStatus(Long id) {
        BoundValueOperations<String, Object> ops = redisTemplate.boundValueOps(RedisKeys.AUCTION_STATUS + id);
        String stock = (String) ops.get();
        if (stock == null) {
            Boolean success = ops.setIfAbsent(Optional.ofNullable(auctionActivityRepo.getStatus(id))
                    .orElse(AuctionStatus.NOTSTARTED.toString()), 7, TimeUnit.DAYS);
            log.info("创建redis拍卖活动状态：{}", success);
            return (String) ops.get();
        }
        return stock;
    }

    private void onShelfTask(AuctionActivity record) {
        ScheduledFuture<?> task = tasks.get(record.getId());
        if (task != null) {
            if (!task.cancel(true)) {
                return;
            }
        }
        if (record.getStatus().equals(AuctionStatus.NOTSTARTED)) {
            if (record.getStartTime().minusSeconds(2).isAfter(LocalDateTime.now())) {
                Date date = Date.from(record.getStartTime().atZone(ZoneId.systemDefault()).toInstant());
                ScheduledFuture<?> future = taskScheduler.schedule(() -> {

//                    this.changeStatus(record.getId(), AuctionStatus.ONGOING);
                    auctionActivityRepo.updateStatus(record.getId(), AuctionStatus.ONGOING);
                    tasks.remove(record.getId());
                }, date);
                tasks.put(record.getId(), future);
            } else {
//                this.changeStatus(record.getId(), AuctionStatus.ONGOING);
                auctionActivityRepo.updateStatus(record.getId(), AuctionStatus.ONGOING);
            }
        }
    }

    public void offShelfTask(AuctionActivity record) {
        Long id = record.getId();
        ScheduledFuture<?> task = tasks.get(id);
        if (task != null) {
            if (!task.cancel(true)) {
                return;
            }
        }
//        AuctionActivity recordNew = auctionActivityRepo.findById(id)
//                .orElseThrow(new BusinessException("无数据"));
        if (record.getStatus().equals(AuctionStatus.ONGOING)) {
            if (record.getEndTime().minusSeconds(2).isAfter(LocalDateTime.now())) {
                Date date = Date.from(record.getEndTime().atZone(ZoneId.systemDefault()).toInstant());
                ScheduledFuture<?> future = taskScheduler.schedule(() -> {
                    AuctionActivity recordNew1 = auctionActivityRepo.findById(id)
                            .orElseThrow(new BusinessException("无数据"));

                    if (ObjectUtils.isNotEmpty(recordNew1.getPurchasePrice())) {
                        log.info("拍卖成交{}", recordNew1.getId());
//                        this.changeStatus(recordNew1.getId(), AuctionStatus.PURCHASED);
                        auctionActivityRepo.updateStatus(recordNew1.getId(), AuctionStatus.PURCHASED);
                    } else {
                        //没有成交价，无人出价过
                        log.info("拍卖流拍Task-else{}", recordNew1.getId());
                        auctionActivityRepo.scheduleOffShelf(recordNew1.getId(), AuctionStatus.PASS);

                        if (AuctionSource.TRANSFER.equals(recordNew1.getSource())) {
                            Asset asset = assetRepo.findById(recordNew1.getAssetId())
                                    .orElseThrow(new BusinessException("暂无"));
                            asset.setStatus(AssetStatus.NORMAL);
                            asset.setConsignment(false);
                            asset.setPublicShow(false);
                            assetRepo.save(asset);
                        }
                    }
                    tasks.remove(id);
                }, date);
                tasks.put(id, future);
            } else {
                AuctionActivity recordNew1 = auctionActivityRepo.findById(id)
                        .orElseThrow(new BusinessException("无数据"));
                if (ObjectUtils.isNotEmpty(recordNew1.getPurchasePrice())) {
                    log.info("拍卖成交{}", id);
                    auctionActivityRepo.scheduleOffShelf(id, AuctionStatus.PURCHASED);
                } else {
                    log.info("拍卖流拍Task-else-else{}", id);
                    auctionActivityRepo.scheduleOffShelf(id, AuctionStatus.PASS);

                    if (AuctionSource.TRANSFER.equals(recordNew1.getSource())) {
                        Asset asset = assetRepo.findById(recordNew1.getAssetId())
                                .orElseThrow(new BusinessException("暂无"));
                        asset.setStatus(AssetStatus.NORMAL);
                        asset.setConsignment(false);
                        asset.setPublicShow(false);
                        assetRepo.save(asset);
                    }
                }
            }
        }
    }

    /**
     * 定时下架拍卖
     * （每隔1分钟执行一次）
     */
    @Scheduled(cron = "0 */1 * * * ?")
    public void passAuction() {
        List<AuctionActivity> activities = auctionActivityRepo.findAllByStatus(AuctionStatus.ONGOING);
        activities.forEach(activity -> {
            if (activity.getEndTime().isBefore(LocalDateTime.now())) {
                if (ObjectUtils.isNotEmpty(activity.getPurchasePrice())) {
                    log.info("拍卖成交{}", activity.getId());
//                    this.changeStatus(activity.getId(), AuctionStatus.PURCHASED);
                    auctionActivityRepo.updateStatus(activity.getId(), AuctionStatus.PURCHASED);
                } else {
                    //没有成交价，无人出价过
                    log.info("拍卖流拍Task-else-else{}", activity.getId());
//                    this.changeStatus(activity.getId(), AuctionStatus.PASS);
                    auctionActivityRepo.updateStatus(activity.getId(), AuctionStatus.PASS);

                    if (AuctionSource.TRANSFER.equals(activity.getSource())) {
                        Asset asset = assetRepo.findById(activity.getAssetId())
                                .orElseThrow(new BusinessException("暂无"));
                        asset.setStatus(AssetStatus.NORMAL);
                        asset.setConsignment(false);
                        asset.setPublicShow(false);
                        assetRepo.save(asset);
                    }
                }
            }
        });
    }
}
