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

import com.yeejoin.amos.boot.module.jg.api.dto.FlowingEquipRedisKeyDTO;
import com.yeejoin.amos.boot.module.jg.api.service.IEquipUsedCheck;
import com.yeejoin.amos.boot.module.jg.biz.context.FlowingEquipRedisContext;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.typroject.tyboot.core.restful.exception.instance.BadRequest;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
@Slf4j
public abstract class BaseEquipUsedCheckService implements IEquipUsedCheck {


    /**
     * 并发校验（页面同时提交或者页面同时打开多个时，某些设备都是为使用状态）校验设备流程在用状态及更新不在用为再使用状态
     *
     * @param record      设备唯一标识
     * @param companyCode 登录人的公司代码
     */
    @Override
    public void equipRepeatUsedCheck(String record, String companyCode) {
        RLock lock = getRedisClient().getLock(this.getRepeatUsedCheckLockKey(companyCode, getApplyBizType(), record));
        try {
            boolean isLocked = lock.tryLock(0, 180, TimeUnit.SECONDS);
            if (!isLocked) {
                log.error("设备：{}在被其他业务使用，加锁失败", record);
                throw new BadRequest("设备已被其他流程使用，请刷新后重试！");
            }
            RBucket<Set<String>> RBucket = getRedisClient().getBucket(this.getFlowingEquipRedisKey(companyCode, getApplyBizType()));
            Set<String> equipListOfUsed = RBucket.get();
            // 查询当前设备在流程中的申请单号列表
            if (equipListOfUsed != null && equipListOfUsed.contains(record)) {
                log.error("设备已被其他流程使用，不允许重复提交，设备ID:{}", record);
                throw new BadRequest("该设备已在当前业务流程中使用，不能重复提交，请刷新页面查看单据状态！");
            }
            setUsingData2Redis(record, companyCode, equipListOfUsed);
        } catch (InterruptedException e) {
            log.error( e.getMessage(), e);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    private void setUsingData2Redis(String record, String companyCode, Set<String> equipListOfUsed) {
        String redisKey = this.getFlowingEquipRedisKey(companyCode, getApplyBizType());
        log.info("【setUsingData2Redis】start, record={}, companyCode={}, redisKey={}, thread={}",
                record, companyCode, redisKey, Thread.currentThread().getName());
        log.info("【setUsingData2Redis】before handle, incoming equipListOfUsed={}", equipListOfUsed);
        try {
            if (equipListOfUsed == null || equipListOfUsed.isEmpty()) {
                log.info("【setUsingData2Redis】equipListOfUsed is null or empty, init new TreeSet");
                equipListOfUsed = new TreeSet<>();
            }
            boolean added = equipListOfUsed.add(record);
            log.info("【setUsingData2Redis】after add, added={}, currentSet={}", added, equipListOfUsed);
            getRedisClient()
                    .getBucket(redisKey)
                    .set(equipListOfUsed);
            log.info("【setUsingData2Redis】redis set success, redisKey={}, value={}", redisKey, equipListOfUsed);
            FlowingEquipRedisContext.setRedisKeyInfo(
                    new FlowingEquipRedisKeyDTO(redisKey, Collections.singletonList(record))
            );
            log.info("【setUsingData2Redis】FlowingEquipRedisContext set success, redisKey={}, record={}",
                    redisKey, record);
        } catch (Exception e) {
            log.error("【setUsingData2Redis】Exception, record={}, redisKey={}", record, redisKey, e);
            throw e;
        }
        log.info("【setUsingData2Redis】end, record={}, redisKey={}", record, redisKey);
    }

    @Override
    public void registrationRepeatUsedCheck(String record, String companyCode) {

        String lockKey = this.getRepeatUsedCheckLockKey(companyCode, getApplyBizType(), record);
        String redisKey = this.getFlowingEquipRedisKey(companyCode, getApplyBizType());

        log.info("【registrationRepeatUsedCheck】start, record={}, companyCode={}, lockKey={}, redisKey={}",
                record, companyCode, lockKey, redisKey);

        RLock lock = getRedisClient().getLock(lockKey);

        try {

            boolean isLocked = lock.tryLock(0, 180, TimeUnit.SECONDS);
            log.info("【registrationRepeatUsedCheck】tryLock result={}, record={}, thread={}",
                    isLocked, record, Thread.currentThread().getName());

            if (!isLocked) {
                log.error("使用登记证：{}在被其他业务使用，加锁失败，lockKey={}", record, lockKey);
                throw new BadRequest("使用登记证已被其他流程使用，请刷新后重试！");
            }

            RBucket<Set<String>> rBucket = getRedisClient().getBucket(redisKey);
            Set<String> equipListOfUsed = rBucket.get();

            log.info("【registrationRepeatUsedCheck】redis before check, redisKey={}, currentValue={}",
                    redisKey, equipListOfUsed);

            // 查询当前设备在用的申请单号列表
            if (equipListOfUsed != null && equipListOfUsed.contains(record)) {
                log.error("使用登记证已被其他流程使用，不允许重复提交，使用登记证ID={}, redisKey={}, redisValue={}",
                        record, redisKey, equipListOfUsed);
                throw new BadRequest("使用登记证已被其他流程使用，不允许重复提交，请联系管理员!");
            }

            log.info("【registrationRepeatUsedCheck】准备写入redis, record={}, redisKey={}", record, redisKey);

            setUsingData2Redis(record, companyCode, equipListOfUsed);

            Set<String> afterSet = rBucket.get();
            log.info("【registrationRepeatUsedCheck】redis after write, redisKey={}, value={}",
                    redisKey, afterSet);

        } catch (InterruptedException e) {
            log.error("【registrationRepeatUsedCheck】InterruptedException, record={}", record, e);
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            log.error("【registrationRepeatUsedCheck】Exception, record={}", record, e);
            throw e;
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.info("【registrationRepeatUsedCheck】unlock success, record={}, lockKey={}", record, lockKey);
            } else {
                log.warn("【registrationRepeatUsedCheck】current thread not holding lock, record={}, lockKey={}", record, lockKey);
            }
        }

        log.info("【registrationRepeatUsedCheck】end, record={}", record);
    }

    private String getRepeatUsedCheckLockKey(String companyCode, String bizType, String record) {
        return String.format("%s-%s-%s-%s", "JG_REPEAT_CHECK", companyCode, bizType, record);
    }

    String getFlowingEquipRedisKey(String companyCode, String type) {
        return String.format("%s:%s:%s", "EQUIP_IN_FLOWING", companyCode, type);
    }

    /**
     * 删除流程中的数据，释放redis空间（异常处理及驳回到发起节点、撤回到发起节点、完成时时进行）
     *
     * @param records  设备唯一标识
     * @param redisKey key
     */
    @Override
    public void delDataForCheckWithKey(List<String> records, String redisKey) {
        delRedisData(records, redisKey, getRedisClient());
    }

    @Override
    public Set<String> getEquipInFlow(String companyCode) {
        RBucket<Set<String>> rBucket = getRedisClient().getBucket(this.getFlowingEquipRedisKey(companyCode, getApplyBizType()));
        return rBucket.get();
    }

    /**
     * 删除流程中的数据，释放redis空间（异常处理及驳回到发起节点、撤回到发起节点、完成时时进行）
     *
     * @param records     设备唯一标识
     * @param companyCode 登录人的公司代码
     */
    @Override
    public void delDataForCheckEquipRepeatUsed(List<String> records, String companyCode) {
        if (records == null || records.isEmpty()) {
            log.info("delData: records is null or empty");
            return;
        }

        String lockKey = "lock:" + getFlowingEquipRedisKey(companyCode, getApplyBizType());

        RLock lock = getRedisClient().getLock(lockKey);
        boolean locked = false;

        try {
            locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
            if (!locked) {
                log.warn("delData: cannot acquire lock, key={}", lockKey);
                return;
            }

            RBucket<Set<String>> rBucket = getRedisClient()
                    .getBucket(getFlowingEquipRedisKey(companyCode, getApplyBizType()));

            Set<String> equipListOfUsed = rBucket.get();
            log.info("delData: redis original set={}", equipListOfUsed);

            if (equipListOfUsed == null) {
                return;
            }

            Set<String> after = equipListOfUsed.stream()
                    .filter(record -> !records.contains(record))
                    .collect(Collectors.toSet());

            log.info("delData: after filter set={}", after);

            rBucket.set(after);

            Set<String> verify = rBucket.get();
            log.info("delData: after set verify redis set={}", verify);

        } catch (Exception e) {
            log.error("delData error", e);
        } finally {
            if (locked) {
                try {
                    lock.unlock();
                } catch (Exception e) {
                    log.error("unlock error", e);
                }
            }
        }
    }


    private void delRedisData(List<String> records, String redisKey, RedissonClient redissonClient) {
        RBucket<Set<String>> rBucket = redissonClient.getBucket(redisKey);
        Set<String> equipListOfUsed = rBucket.get();
        if(equipListOfUsed != null){
            equipListOfUsed = equipListOfUsed.stream().filter(record -> !records.contains(record)).collect(Collectors.toSet());
            rBucket.set(equipListOfUsed);
        }
    }

    public abstract RedissonClient getRedisClient();

    public abstract String getApplyBizType();

    @Override
    public String applyBizType() {
        return getApplyBizType();
    }

}
