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 = "%08d";

    private static final String SUPERVISOR_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 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) ? generateUseRegistrationSequence(key, LOCK_KEY_UR) : "生成码规则不对！";
    }

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

    @Override
    @Deprecated
    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.SUPERVISOR_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 {
                    setValueWithoutExpiration(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;
                }
                // currentSequenceStr截取前两位作为prefix
                String prefix = currentSequenceStr.substring(0, 2);
                // 截取剩余的数字作为顺序码
                currentSequenceStr = currentSequenceStr.substring(2);
                long currentSequence = Long.parseLong(currentSequenceStr);
                log.info("===================>获取《{}》当前顺序码：{}<===================", sequenceKey, currentSequenceStr);

                currentSequence++;
                currentSequence = Long.parseLong(prefix + currentSequence);
                // 生成顺序码
                String formattedSequence = String.format(sequenceType, currentSequence);
                // 去除先导0字符
                formattedSequence = formattedSequence.replaceFirst("^0+", "");
                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("释放锁");
        }
    }

    /**
     * 生成使用登记证编号(13位，起11陕C00001(23))
     *
     * @param sequenceKey key(起11陕C)
     * @return 顺序编号(value(00001,或者A0001,B0001))
     * rulePrefix (年份24)
     */
    public String generateUseRegistrationSequence(String sequenceKey, String lockKey) {
        // 使用分布式锁，确保在并发情况下只有一个线程能够生成顺序码
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 尝试获取锁，最多等待 10 秒，锁自动释放时间为 30 秒
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                log.info("成功获取锁: {}", lockKey);

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

                // 判断当前顺序码是字母+数字形式还是纯数字形式
                if (isAlphaNumericSequence(currentSequenceStr)) {
                    // 如果是字母+数字形式
                    formattedSequence = generateAlphaNumericSequence(currentSequenceStr);
                } else {
                    // 如果是纯数字形式
                    formattedSequence = generateNumericToAlphaSequence(currentSequenceStr);
                }

                log.info("===================>更新《{}》顺序码：{}<===================", sequenceKey, formattedSequence);

                // 更新顺序码
                setValueWithoutExpiration(sequenceKey, formattedSequence);

                // 返回生成的顺序码
                String generatedSequence = sequenceKey + formattedSequence;
                String result = generatedSequence +  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("释放锁");
        }
    }

    /**
     * 判断当前顺序码是否为字母+数字形式
     */
    private boolean isAlphaNumericSequence(String sequence) {
        return sequence != null && sequence.matches("^[A-Za-z]{1}\\d{4}$");
    }

    /**
     * 生成字母+数字形式的顺序码（如 A0001）
     */
    private String generateAlphaNumericSequence(String sequenceKey) {
        char letter = sequenceKey.charAt(0);  // 提取字母部分
        int numberPart = Integer.parseInt(sequenceKey.substring(1));  // 提取数字部分

        numberPart++;  // 数字部分递增

        // 检查是否超过了 Z9999
        if (letter == 'Z' && numberPart > 9999) {
            throw new RuntimeException("顺序码已用尽，请联系管理员");
        }

        if (numberPart > 9999) {
            // 如果数字部分大于 9999，字母部分递增，数字部分重置为 0000
            numberPart = 0;
            letter = (char) (letter + 1);
        }

        return String.format("%c%04d", letter, numberPart);  // 格式化字母+数字顺序码
    }

    /**
     * 生成纯数字顺序码到字母+数字的转换（仅从99999到A0001）
     */
    private String generateNumericToAlphaSequence(String sequenceStr) {
        // 如果是纯数字顺序码
        Long currentSequence = (sequenceStr != null) ? Long.parseLong(sequenceStr) : 0L;
        currentSequence++;  // 数字部分递增
        // 如果递增后的值大于 99999，转换为字母+数字形式
        if (currentSequence > 99999) {
            // 从数字转换到字母+数字形式，假设从 100000 转到 A0001
            char letter = 'A';  // 从 A 开始
            int numericPart = 0; // 数字部分为 0000
            return String.format("%c%04d", letter, numericPart);
        } else {
            // 继续保持纯数字形式
            return String.format("%05d", currentSequence);
        }
    }

    /**
     * 回退顺序码
     *
     * @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);
    }

    /**
     * redis 设置不过期的 key
     *
     * @param key   key
     * @param value value
     */
    public void setValueWithoutExpiration(String key, String value) {
        ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
        valueOps.set(key, value);
    }
}