package com.yeejoin.amos.boot.module.ymt.biz.service.impl;

import com.yeejoin.amos.boot.module.ymt.api.common.DateUtils;
import com.yeejoin.amos.boot.module.ymt.api.enums.ApplicationFormTypeEnum;
import com.yeejoin.amos.boot.module.ymt.api.enums.EquipmentCategoryEnum;
import com.yeejoin.amos.boot.module.ymt.api.mapper.CategoryOtherInfoMapper;
import com.yeejoin.amos.boot.module.ymt.api.service.IGenerateCodeService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author LiuLin
 * @date 2023-12-14
 */
@Slf4j
@Service
public class GenerateCodeServiceImpl implements IGenerateCodeService {

    private static final String LOCK_VALUE = "locked";
    private static final String LOCK_KEY_AF = "sequence_lock_af";
    private static final String SEQUENCE_TYPE_AF = "%03d";
    private static final String LOCK_KEY_DR = "sequence_lock_dr";
    private static final String SEQUENCE_TYPE_DR = "%04d";
    private static final String LOCK_KEY_UR = "sequence_lock_ur";
    private static final String LOCK_KEY_ELEVATOR = "sequence_lock_elevator";
    private static final String LOCK_KEY_SUPERVISORY = "sequence_lock_supervisory";
    private static final String SEQUENCE_TYPE_UR = "%05d";
    private static final String SEQUENCE_TYPE = "%07d";
    private final RedisTemplate<String, String> redisTemplate;
    private final StringRedisTemplate stringRedisTemplate;
    private final CategoryOtherInfoMapper categoryOtherInfoMapper;
    private final RedissonClient redissonClient;
    private String rulePrefix = "";

    public GenerateCodeServiceImpl(RedisTemplate<String, String> redisTemplate, StringRedisTemplate stringRedisTemplate, CategoryOtherInfoMapper categoryOtherInfoMapper, RedissonClient redissonClient) {
        this.redisTemplate = redisTemplate;
        this.redissonClient = redissonClient;
        this.stringRedisTemplate = stringRedisTemplate;
        this.categoryOtherInfoMapper = categoryOtherInfoMapper;
    }

    /**
     * 生成申请单编号(13位，GZ20231214000)
     *
     * @param type      枚举类型
     * @param batchSize 生成个数
     * @return List
     */
    @Override
    public List<String> createApplicationFormCode(String type, int batchSize) {
        if (!isValueInEnum(type)) {
            return Collections.emptyList();
        }
        rulePrefix = type + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        return generateBatchSequence(type, batchSize);
    }

    /**
     * 生成设备注册编码(20位)
     *
     * @param key key
     * @return 顺序编号
     */
    @Override
    public String createDeviceRegistrationCode(String key) {
        return (key.length() == 16) ? generateSequence(key, SEQUENCE_TYPE_DR, LOCK_KEY_DR) : "生成码规则不对！";
    }

    /**
     * 生成使用登记证编号(13位，起11陕C00001(23))
     *
     * @param key key
     * @return 顺序编号
     */
    @Override
    public String createUseRegistrationCode(String key) {
        rulePrefix = "(" + LocalDate.now().format(DateTimeFormatter.ofPattern("yy")) + ")";
        return (key.length() == 5) ? generateSequence(key, SEQUENCE_TYPE_UR, LOCK_KEY_UR) : "生成码规则不对！";
    }

    @Override
    public String createElevatorCode(String key) {
        return (key.length() == 8) ? generateElevatorSequence(key, SEQUENCE_TYPE, LOCK_KEY_ELEVATOR) : "生成码规则不对！";
    }

    @Override
    public boolean reduceElevatorCode(String key) {
        return reduceSequence(key, SEQUENCE_TYPE, LOCK_KEY_ELEVATOR);
    }

    @Override
    public String createSupervisoryCode(String key) {
        return (key.length() == 5) ? generateSupervisorySequence(key) : "生成码规则不对！";
    }

    @Override
    public String initCode() {
        categoryOtherInfoMapper.selectSupervisorCodeMaxValue().forEach(vo ->
                stringRedisTemplate.opsForValue().set(vo.getName(), String.valueOf(vo.getValue())));
        categoryOtherInfoMapper.selectElevatorCodeMaxValue().forEach(vo ->
                stringRedisTemplate.opsForValue().set(vo.getName(), String.valueOf(vo.getValue())));
        return "ok";
    }

    private String generateSupervisorySequence(String sequenceKey) {
        // 使用分布式锁，确保在并发情况下只有一个线程能够生成顺序码
        RLock lock = redissonClient.getLock(LOCK_KEY_SUPERVISORY);
        try {
            log.info("尝试获取锁: {}", lock.tryLock(10, TimeUnit.SECONDS));

            if (lock.isLocked()) {
                // 获取当前顺序码
                ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
                String currentSequenceStr = valueOps.get(sequenceKey);
                // 如果为空，则初始化为0
                Long currentSequence = (currentSequenceStr != null) ? Long.parseLong(currentSequenceStr) : this.initRedis(sequenceKey);

                log.info("===================>获取《{}》当前顺序码：{}<===================", sequenceKey, currentSequenceStr);
                currentSequence++;
                // 生成顺序码
                String formattedSequence = String.format(GenerateCodeServiceImpl.SEQUENCE_TYPE, currentSequence);
                log.info("===================>更新《{}》顺序码：{}<===================", sequenceKey, formattedSequence);
                // 更新顺序码
                valueOps.set(sequenceKey, formattedSequence);
                return sequenceKey + "-" + formattedSequence;
            } else {
                throw new RuntimeException("Failed to acquire lock for sequence generation");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保持中断状态
            throw new RuntimeException("Thread interrupted while acquiring lock", e);
        } finally {
            lock.unlock(); // 释放锁
            log.info("释放锁");
        }
    }

    /**
     * 监管码为空的话，初始化Redis
     *
     * @param sequenceKey key
     * @return 顺序码
     */
    private Long initRedis(String sequenceKey) {
        categoryOtherInfoMapper.selectSupervisorCodeMaxValue().forEach(vo ->
                stringRedisTemplate.opsForValue().set(vo.getName(), String.valueOf(vo.getValue())));

        return Long.parseLong(redisTemplate.opsForValue().get(sequenceKey) == null ? "0" : redisTemplate.opsForValue().get(sequenceKey));
    }

    /**
     * 校验枚举合法性
     *
     * @param value value
     * @return bool
     */
    public boolean isValueInEnum(String value) {
        return EnumSet.allOf(ApplicationFormTypeEnum.class)
                .stream()
                .anyMatch(enumValue -> enumValue.name().equals(value));
    }

    /**
     * 批量生成顺序码
     *
     * @param sequenceKey sequenceKey
     * @param batchSize   批量生成个数
     * @return List
     */
    public List<String> generateBatchSequence(String sequenceKey, int batchSize) {
        RLock lock = redissonClient.getLock(LOCK_KEY_AF);

        try {
            log.info("尝试获取锁: {}", lock.tryLock(10, TimeUnit.SECONDS));

            if (lock.isLocked()) {
                // 获取当前顺序码
                ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
                String currentSequenceStr = valueOps.get(sequenceKey);

                // 如果为空，则初始化为0
                Long currentSequence = (currentSequenceStr != null) ? Long.parseLong(currentSequenceStr) : 0L;
                log.info("===================>获取《{}》当前顺序码：{}<===================", sequenceKey, currentSequenceStr);

                // 生成批量顺序码
                List<String> sequenceList = new ArrayList<>();
                log.info("===================>批量生成{}个《{}》顺序码<===================", batchSize, sequenceKey);
                for (int i = 0; i < batchSize; i++) {
                    currentSequence++;
                    // 生成3位顺序码
                    String formattedSequence = String.format(SEQUENCE_TYPE_AF, currentSequence);
                    sequenceList.add(rulePrefix + formattedSequence);
                    // 更新顺序码
                    log.info("===================>更新《{}》顺序码：{}<===================", sequenceKey, formattedSequence);
                    setValueWithDailyExpiration(sequenceKey, String.valueOf(formattedSequence));
                }

                return sequenceList;
            } else {
                // 获取锁失败，可以选择重试或采取其他策略
                throw new RuntimeException("Failed to acquire lock for sequence generation");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保持中断状态
            throw new RuntimeException("Thread interrupted while acquiring lock", e);
        } finally {
            lock.unlock(); // 释放锁
            log.info("释放锁");
        }
    }

    /**
     * 生成顺序码
     *
     * @param sequenceKey  redisKey
     * @param sequenceType 生成码类型
     * @param lockKey      redis锁
     * @return s
     */
    public String generateSequence(String sequenceKey, String sequenceType, String lockKey) {
        // 使用分布式锁，确保在并发情况下只有一个线程能够生成顺序码
        RLock lock = redissonClient.getLock(lockKey);

        try {
            log.info("尝试获取锁: {}", lock.tryLock(10, TimeUnit.SECONDS));

            if (lock.isLocked()) {

                // 获取当前顺序码
                ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
                String currentSequenceStr = valueOps.get(sequenceKey);
                // 如果为空，则初始化为0
                Long currentSequence = (currentSequenceStr != null) ? Long.parseLong(currentSequenceStr) : 0L;
                log.info("===================>获取《{}》当前顺序码：{}<===================", sequenceKey, currentSequenceStr);
                currentSequence++;
                // 生成顺序码
                String formattedSequence = String.format(sequenceType, currentSequence);
                log.info("===================>更新《{}》顺序码：{}<===================", sequenceKey, formattedSequence);
                // 更新顺序码
                if (lockKey.equals(LOCK_KEY_DR)) {
                    setValueWithMonthlyExpiration(sequenceKey, String.valueOf(formattedSequence));
                } else {
                    setValueWithYearlyExpiration(sequenceKey, String.valueOf(formattedSequence));
                }

                String generatedSequence = sequenceKey + formattedSequence;
                String result = generatedSequence + (lockKey.equals(LOCK_KEY_DR) ? "" : rulePrefix);

                log.info("===================>返回《{}》顺序码：{}<===================", sequenceKey, result);
                return result;
            } else {
                throw new RuntimeException("Failed to acquire lock for sequence generation");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保持中断状态
            throw new RuntimeException("Thread interrupted while acquiring lock", e);
        } finally {
            lock.unlock(); // 释放锁
            log.info("释放锁");
        }
    }

    /**
     * 生成顺序码
     *
     * @param sequenceKey  Redis Key
     * @param sequenceType 生成码类型
     * @param lockKey      Redis锁Key
     * @return 生成的顺序码
     */
    public String generateElevatorSequence(String sequenceKey, String sequenceType, String lockKey) {
        // 使用分布式锁，确保在并发情况下只有一个线程能够生成顺序码
        RLock lock = redissonClient.getLock(lockKey);

        try {
            log.info("尝试获取锁: {}", lock.tryLock(10, TimeUnit.SECONDS));
            if (lock.isLocked()) {
                // 获取当前顺序码
                ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
                String currentSequenceStr = valueOps.get(sequenceKey);

                // 如果为空，则初始化为0
                if (currentSequenceStr == null) {
                    String initialCode = EquipmentCategoryEnum.getCodeByValue(sequenceKey);
                    log.info("===================>获取《{}》初始码：{}<===================", sequenceKey, initialCode);
                    return initialCode;
                }

                Long currentSequence = Long.parseLong(currentSequenceStr);
                log.info("===================>获取《{}》当前顺序码：{}<===================", sequenceKey, currentSequenceStr);

                currentSequence++;
                // 生成顺序码
                String formattedSequence = String.format(sequenceType, currentSequence);
                log.info("===================>更新《{}》顺序码：{}<===================", sequenceKey, formattedSequence);
                // 更新顺序码
                valueOps.set(sequenceKey, formattedSequence);
                return formattedSequence;
            } else {
                throw new RuntimeException("Failed to acquire lock for sequence generation");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保持中断状态
            throw new RuntimeException("Thread interrupted while acquiring lock", e);
        } finally {
            lock.unlock(); // 释放锁
            log.info("释放锁");
        }
    }

    /**
     * 回退顺序码
     *
     * @param sequenceKey  redisKey
     * @param sequenceType 码类型
     * @param lockKey      分布锁
     * @return 操作是否成功
     */
    public boolean reduceSequence(String sequenceKey, String sequenceType, String lockKey) {
        // 使用分布式锁，确保在并发情况下只有一个线程能够生成顺序码
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock(); // 获取锁
            log.info("尝试获取锁: {}", lock.tryLock(10, TimeUnit.SECONDS));
            if (lock.isLocked()) {
                // 获取当前顺序码
                ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
                String currentSequenceStr = valueOps.get(sequenceKey);
                // 如果为空，则初始化为0
                long currentSequence = (currentSequenceStr != null) ? Long.parseLong(currentSequenceStr) : 0L;
                log.info("===================>获取《{}》当前顺序码：{}<===================", sequenceKey, currentSequenceStr);

                // 判断是否有可回退的操作
                if (currentSequence > 0) {
                    currentSequence--;

                    // 生成顺序码
                    String formattedSequence = String.format(sequenceType, currentSequence);
                    log.info("===================>回退《{}》顺序码：{}<===================", sequenceKey, formattedSequence);

                    // 更新顺序码
                    valueOps.set(sequenceKey, String.valueOf(formattedSequence));
                    return true; // 回退成功
                } else {
                    log.warn("===================>无法回退《{}》顺序码，当前顺序码已为0<===================", sequenceKey);
                    return false; // 无法回退，当前顺序码已为0
                }
            } else {
                throw new RuntimeException("Failed to acquire lock for sequence generation");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保持中断状态
            throw new RuntimeException("Thread interrupted while acquiring lock", e);
        } finally {
            lock.unlock(); // 释放锁
            log.info("释放锁");
        }
    }

    /**
     * redis 根据自然日过期
     *
     * @param key   key
     * @param value value
     */
    public void setValueWithDailyExpiration(String key, String value) {
        Date endOfDay = DateUtils.calculateEndOfDay(new Date());
        setValueWithExpiration(key, value, endOfDay);
    }

    /**
     * redis 根据自然月过期
     *
     * @param key   key
     * @param value value
     */
    public void setValueWithMonthlyExpiration(String key, String value) {
        Date endOfMonth = DateUtils.calculateEndOfMonth(new Date());
        setValueWithExpiration(key, value, endOfMonth);
    }

    /**
     * redis 根据自然年过期
     *
     * @param key   key
     * @param value value
     */
    public void setValueWithYearlyExpiration(String key, String value) {
        Date endOfYear = DateUtils.calculateEndOfYear(new Date());
        setValueWithExpiration(key, value, endOfYear);
    }

    /**
     * redis设置key
     *
     * @param key            key
     * @param value          value
     * @param expirationDate 过期时间
     */
    public void setValueWithExpiration(String key, String value, Date expirationDate) {
        ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
        valueOps.set(key, value);

        long expirationTimeInSeconds = expirationDate.getTime() - System.currentTimeMillis();
        redisTemplate.expire(key, expirationTimeInSeconds, TimeUnit.MILLISECONDS);
    }
}