package com.wuban.tron.explore.service.impl;

import com.wuban.tron.explore.constant.Constant;
import com.wuban.tron.explore.dao.LastBlockRepository;
import com.wuban.tron.explore.domain.TronResponseArrayData;
import com.wuban.tron.explore.domain.TronResponseData;
import com.wuban.tron.explore.entity.LastBlock;
import com.wuban.tron.explore.entity.example.LastBlockExample;
import com.wuban.tron.explore.service.LastBlockService;
import com.wuban.tron.explore.service.TronService;
import com.wuban.tron.explore.util.BigDecimalUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;

/**
 *  <core>区块高度服务接口实现类</core>
 *
 * @author sky
 * @date 2020/11/02
 */
@Service
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class LastBlockServiceImpl implements LastBlockService {

    private final LastBlockRepository lastBlockRepository;

    private final TronService tronService;

    private final StringRedisTemplate stringRedisTemplate;

    @Override
    public LastBlock getByIdForUpdate(Long id) {
        return lastBlockRepository.selectByIdForUpdate(id);
    }

    /**
     * 获取区块高度信息:并且更新此高度+1
     *
     * @param id 唯一标识
     * @return Long
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long getForUpdate(Long id) {
        LastBlock lastBlock = lastBlockRepository.selectByIdForUpdate(id);
        long curBlockNum = lastBlock.getCurBlockNum() + 1;
        LastBlock obj = new LastBlock();
        obj.setId(lastBlock.getId());
        obj.setCurBlockNum(curBlockNum);
        this.lastBlockRepository.updateCurBlockNumById(obj);
        return curBlockNum;
    }

    /**
     * 获取区块高度信息
     *
     * @return LastBlock
     */
    @Override
    public LastBlock getOneByExample() {
        LastBlockExample example = new LastBlockExample();
        return this.lastBlockRepository.selectOneByExample(example);
    }

    @Override
    public void refresh() {
        Integer latestNum = 1;

        try {
            TronResponseArrayData ret = this.tronService.getBlockByLatestNum(latestNum);
            if (ret != null && !CollectionUtils.isEmpty(ret.getBlock())) {
                TronResponseData data = ret.getBlock().get(0);
                LastBlock record = new LastBlock();
                record.setId(1L);
                record.setLastBlockNum(data.getBlock_header().getRaw_data().getNumber());
                this.lastBlockRepository.updateById(record);
                log.info("定时任务-已更新区块高度num:{}",data.getBlock_header().getRaw_data().getNumber());
            }
        } catch (Exception e) {
            log.error("定时任务-抓取区块高度失败");
        }

    }


    @Override
    public void sync() {
        LastBlock lastBlock = this.lastBlockRepository.selectOneByExample(null);
        if (lastBlock == null) {
            return;
        }

        BigDecimal bg;
        BigDecimal diff;
        long startNum;
        long endNum;

        /*
            Redis获取已同步区块高度block_num
            如果已存在，根据此值计算：（最新区块高度-Redis:block_num）将区间值push到队列中，已被后续抓取数据做准备
            如果不存在，从DB中提取已抓取的数据值（最新区块高度-DB:block_num）将区间值push到队列中，已被后续抓取数据做准备
         */
        String redisBlockNum = this.stringRedisTemplate.opsForValue().get(Constant.BLOCK_NUM_KEY);
        if (StringUtils.isEmpty(redisBlockNum)) {
            startNum = lastBlock.getCurBlockNum();
            bg = BigDecimalUtil.getAdd(new BigDecimal(lastBlock.getCurBlockNum()), new BigDecimal(Constant.FIVE_THOUSAND));
        } else {
            startNum = Long.parseLong(redisBlockNum);
            long size = this.stringRedisTemplate.opsForList().size(Constant.BLOCK_NUM_LIST_KEY);
            if (size - Constant.THRESHOLD > 0) {
                return;
            }
            bg = BigDecimalUtil.getAdd(new BigDecimal(redisBlockNum), new BigDecimal(Constant.FIVE_THOUSAND));
        }
        diff = BigDecimalUtil.getSubtract(bg, new BigDecimal(lastBlock.getLastBlockNum()));

        // 判断已缓存 + BLOCK_NUM_KEY 是否超过区块的最大高度值
        if (diff.longValue() < 0) {
            endNum = bg.longValue();
        } else {
            // 替换掉"-"号，相当于去绝对值
            endNum = lastBlock.getLastBlockNum() - 1;
        }

        if (startNum == endNum) {
            return;
        }

        if (diff.longValue() < 0) {
            this.stringRedisTemplate.opsForValue().set(Constant.BLOCK_NUM_KEY, bg.toPlainString());
        } else {
            this.stringRedisTemplate.opsForValue().set(Constant.BLOCK_NUM_KEY, lastBlock.getLastBlockNum().toString());
        }

        // 将区块高度同步到redis 队列中
        this.stringRedisTemplate.executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                StringRedisConnection conn = (StringRedisConnection)redisConnection;
                for (long i = startNum; i <= endNum; i++) {
                    conn.lPush(Constant.BLOCK_NUM_LIST_KEY, Long.toString(i));
                }
                return null;
            }
        });
    }

}
