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

import com.yeejoin.amos.boot.module.tcm.api.enums.ApplicationFormTypeEnum;
import com.yeejoin.amos.boot.module.tcm.api.service.ICreateCodeService;
import org.springframework.data.redis.core.RedisTemplate;
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
 */
@Service
public class CreateCodeServiceImpl implements ICreateCodeService {

    private static final String LOCK_VALUE = "locked";
    private static final String LOCK_KEY_AF = "sequence_lock_af";
    private static final String LOCK_KEY_DR = "sequence_lock_dr";
    private final RedisTemplate<String, String> redisTemplate;
    private String rulePrefix;

    public CreateCodeServiceImpl(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @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);
    }

    @Override
    public String createDeviceRegistrationCode(String key) {
        return generateSequence(key);
    }

    public boolean isValueInEnum(String value) {
        return EnumSet.allOf(ApplicationFormTypeEnum.class)
                .stream()
                .anyMatch(enumValue -> enumValue.name().equals(value));
    }

    public List<String> generateBatchSequence(String sequenceKey, int batchSize) {
        // 使用分布式锁，确保在并发情况下只有一个线程能够生成顺序码
        Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY_AF, LOCK_VALUE);
        if (Boolean.TRUE.equals(lockAcquired)) {
            try {
                // 获取当前顺序码
                ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
                String currentSequenceStr = valueOps.get(sequenceKey);

                // 如果为空，则初始化为0
                if (currentSequenceStr == null) {
                    currentSequenceStr = "0";
                }

                // 将当前顺序码加1
                Long currentSequence = Long.parseLong(currentSequenceStr);

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

                return sequenceList;
            } finally {
                redisTemplate.delete(LOCK_KEY_AF);
            }
        } else {
            // 获取锁失败，可以选择重试或采取其他策略
            throw new RuntimeException("Failed to acquire lock for sequence generation");
        }
    }

    public String generateSequence(String sequenceKey) {
        // 使用分布式锁，确保在并发情况下只有一个线程能够生成顺序码
        Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY_DR, LOCK_VALUE);
        if (Boolean.TRUE.equals(lockAcquired)) {
            try {
                // 获取当前顺序码
                ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
                String currentSequenceStr = valueOps.get(sequenceKey);

                // 如果为空，则初始化为0
                if (currentSequenceStr == null) {
                    currentSequenceStr = "0";
                }

                // 将当前顺序码加1
                Long currentSequence = Long.parseLong(currentSequenceStr);
                currentSequence++;

                // 生成10位顺序码
                String formattedSequence = String.format("%04d", currentSequence);

                // 更新顺序码
                setValueWithMonthlyExpiration(sequenceKey, String.valueOf(formattedSequence));

                return sequenceKey + formattedSequence;
            } finally {
                redisTemplate.delete(LOCK_KEY_DR);
            }
        } else {
            // 获取锁失败，可以选择重试或采取其他策略
            throw new RuntimeException("Failed to acquire lock for sequence generation");
        }
    }

    /**
     * redis 根据自然月过期
     *
     * @param key   key
     * @param value value
     */
    public void setValueWithMonthlyExpiration(String key, String value) {
        ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
        valueOps.set(key, value);

        Date currentDate = new Date();
        Date endOfMonth = calculateEndOfMonth(currentDate);
        long expirationTimeInSeconds = endOfMonth.getTime() - currentDate.getTime();

        redisTemplate.expire(key, expirationTimeInSeconds, TimeUnit.MILLISECONDS);
    }

    public void setValueWithDailyExpiration(String key, String value) {
        ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
        valueOps.set(key, value);

        Date currentDate = new Date();
        Date endOfDay = calculateEndOfDay(currentDate);
        long expirationTimeInSeconds = endOfDay.getTime() - currentDate.getTime();
        redisTemplate.expire(key, expirationTimeInSeconds, TimeUnit.MILLISECONDS);
    }

    private Date calculateEndOfMonth(Date currentDate) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(currentDate);
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 999);
        return calendar.getTime();
    }

    private Date calculateEndOfDay(Date currentDate) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(currentDate);
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 999);
        return calendar.getTime();
    }
}