package com.fizz.business.service.client;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.fizz.business.dto.*;
import com.fizz.business.service.impl.BeanFactory;
import com.fizz.business.utils.CoilMeasUtil;
import com.fizz.business.utils.MatmapUtil;
import com.fizz.business.utils.RedisUtil;
import com.fizz.business.vo.PdiPlanVO;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

import static com.fizz.business.constants.CommonConstants.Redis.*;


/**
 * redis缓存管理
 *
 * @author chenhao
 * @date 2023/05/15
 */
@Slf4j
@Component
public class RedisCacheManager {
    @Autowired
    RedisTemplate<String, SegmentDTO> segmentRedisTemplate;

    @Autowired
    @Qualifier("redisTemplateOfMatmap")
    RedisTemplate<String, MatmapDTO> matmapRedisTemplate;

    @Autowired
    @Qualifier("redisTemplateOfHead")
    RedisTemplate<String, CoilHeadDTO> headRedisTemplate;

    @Autowired
    @Qualifier("redisTemplateOfCoilPos")
    RedisTemplate<String, CoilPositionDTO> coilPosRedisTemplate;

    @Autowired
    RedisTemplate<String, JSONObject> jsonObjectRedisTemplate;

    public double getStripLocation() {
        return Optional.ofNullable((Double) RedisUtil.getValue(COIL_STRIP_LOCATION_KEY)).orElse(0d);
    }

    public void setStripLocation(double value) {
        RedisUtil.setValue(COIL_STRIP_LOCATION_KEY, value);
    }

    public Integer getPrevPorIdx() {
        return Optional.ofNullable((Integer) RedisUtil.getValue(COIL_POR_IDX_KEY)).orElse(0);
    }

    public void setPrevPorIdx(Integer value) {
        RedisUtil.setValue(COIL_POR_IDX_KEY, value);
    }

    public CoilPositionDTO getCoilPosition() {

        return Optional.ofNullable(coilPosRedisTemplate.opsForValue().get(COIL_POSITION_KEY)).orElse(new CoilPositionDTO());
    }

    public void setCoilPosition(CoilPositionDTO coilPos) {
        coilPosRedisTemplate.opsForValue().set(COIL_POSITION_KEY, coilPos);
    }

    public int getSegNum() {
        return Optional.ofNullable((Integer) RedisUtil.getValue(COIL_SEG_NUM_KEY)).orElse(1);
    }

    public void setSegNum(int value) {
        RedisUtil.setValue(COIL_SEG_NUM_KEY, value);
    }

    public void incSegNum() {
        RedisUtil.increment(COIL_SEG_NUM_KEY);
    }

    public void resetSegNum() {
        RedisUtil.setValue(COIL_SEG_NUM_KEY, 1);
    }

    public double getHeadCutLength(int porIdx) {
        return Optional.ofNullable((Double) RedisUtil.getValue(COIL_HEAD_CUT_LEN_KEY + porIdx)).orElse(0d);
    }

    public void setHeadCutLength(int porIdx, double value) {
        RedisUtil.setValue(COIL_HEAD_CUT_LEN_KEY + porIdx, value);
    }

    public List<SegmentDTO> getSegmentList() {
        return segmentRedisTemplate.opsForList().range(COIL_SEG_LIST_KEY, 0, -1);
    }

    public void setSegmentList(List<SegmentDTO> segmentList) {
        segmentRedisTemplate.delete(COIL_SEG_LIST_KEY);
        if (CollUtil.isNotEmpty(segmentList)) {
            segmentRedisTemplate.opsForList().rightPushAll(COIL_SEG_LIST_KEY, segmentList);
        }
    }

    public void addSegment(SegmentDTO segment) {
        List<SegmentDTO> segmentList = this.getSegmentList();
        if (segmentList.isEmpty()) {
            segmentRedisTemplate.opsForList().rightPush(COIL_SEG_LIST_KEY, segment);
            log.info("create new segment, coilId=[{}], segNo={}, startPos={}, endPos={}, segLen={}",
                    segment.getEnCoilID(), segment.getSegNo(), segment.getStartPos(), segment.getEndPos(), segment.getSegLen());
        } else {
            Map<String, String> exists = segmentList.stream().collect(Collectors.toMap(s -> s.getEnCoilID() + "-" + s.getSegNo(), s -> s.getEnCoilID() + s.getSegNo() , (k1, k2) -> k1));
            String key = segment.getEnCoilID() + "-" + segment.getSegNo() ;
            if (!exists.containsKey(key)) {
                segmentRedisTemplate.opsForList().rightPush(COIL_SEG_LIST_KEY, segment);
                log.info("create new segment, coilId=[{}], segNo={}, startPos={}, endPos={}, segLen={}",
                        segment.getEnCoilID(), segment.getSegNo(), segment.getStartPos(), segment.getEndPos(), segment.getSegLen());
            }
        }
    }

    public SegmentDTO getSegment(long index) {
        return segmentRedisTemplate.opsForList().index(COIL_SEG_LIST_KEY, index);
    }

    public long getSegmentListSize() {
        return Optional.ofNullable(segmentRedisTemplate.opsForList().size(COIL_SEG_LIST_KEY)).orElse(0L);
    }

    public void setMatmapList(List<MatmapDTO> matmaps) {
        matmapRedisTemplate.delete(COIL_MATMAP_LIST_KEY);
        if (CollUtil.isNotEmpty(matmaps)) {
            matmapRedisTemplate.opsForList().rightPushAll(COIL_MATMAP_LIST_KEY, matmaps);
        }
    }

    public List<MatmapDTO> getMatmapList() {
        List<MatmapDTO> rawList = matmapRedisTemplate.opsForList().range(COIL_MATMAP_LIST_KEY, 0, -1);
        if (rawList == null) {
            return null;
        }
        
        List<MatmapDTO> result = new ArrayList<>();
        for (Object item : rawList) {
            if (item == null) {
                result.add(null);
                continue;
            }
            
            // 如果是LinkedHashMap，转换为MatmapDTO
            if (item instanceof java.util.LinkedHashMap) {
                try {
                    MatmapDTO dto = BeanUtil.toBean((java.util.LinkedHashMap<?, ?>) item, MatmapDTO.class);
                    result.add(dto);
                } catch (Exception e) {
                    log.error("Failed to convert LinkedHashMap to MatmapDTO: {}", e.getMessage());
                    result.add(null);
                }
            }
            // 如果已经是MatmapDTO，直接添加
            else if (item instanceof MatmapDTO) {
                result.add((MatmapDTO) item);
            }
            else {
                log.warn("Unexpected type in Redis list: {}", item.getClass().getName());
                result.add(null);
            }
        }
        
        return result;
    }

    public void setMatmap(int index, MatmapDTO matmap) {
        matmapRedisTemplate.opsForList().set(COIL_MATMAP_LIST_KEY, index, matmap);
    }

    public MatmapDTO getMatmap(int index) {
        Object result = matmapRedisTemplate.opsForList().index(COIL_MATMAP_LIST_KEY, index);
        if (result == null) {
            return null;
        }
        
        // 如果是LinkedHashMap，转换为MatmapDTO
        if (result instanceof java.util.LinkedHashMap) {
            try {
                return BeanUtil.toBean((java.util.LinkedHashMap<?, ?>) result, MatmapDTO.class);
            } catch (Exception e) {
                log.error("Failed to convert LinkedHashMap to MatmapDTO: {}", e.getMessage());
                return null;
            }
        }
        
        // 如果已经是MatmapDTO，直接返回
        if (result instanceof MatmapDTO) {
            return (MatmapDTO) result;
        }
        
        log.warn("Unexpected type in Redis: {}", result.getClass().getName());
        return null;
    }

    public List<CoilHeadDTO> getHeadList() {
        return Optional.ofNullable(headRedisTemplate.opsForList().range(COIL_HEAD_LIST_KEY, 0, -1)).orElse(Collections.emptyList());
    }

    public void setHeadList(List<CoilHeadDTO> headList) {
        headRedisTemplate.delete(COIL_HEAD_LIST_KEY);
        if (CollUtil.isNotEmpty(headList)) {
            headRedisTemplate.opsForList().rightPushAll(COIL_HEAD_LIST_KEY, headList);
        }
    }

    public void addHead(CoilHeadDTO head) {
        List<CoilHeadDTO> headList = getHeadList();
        if (CollUtil.isNotEmpty(headList)) {
            Map<String, CoilHeadDTO> exists = headList.stream().collect(Collectors.toMap(CoilHeadDTO::getMatId, h -> h, (k1, k2) -> k1));
            if (!exists.containsKey(head.getMatId())) {
                headRedisTemplate.opsForList().rightPush(COIL_HEAD_LIST_KEY, head);
                log.info("add coil head: {}", JSON.toJSONString(head));
            }
        } else {
            headRedisTemplate.opsForList().rightPush(COIL_HEAD_LIST_KEY, head);
            log.info("add coil head: {}", JSON.toJSONString(head));
        }
    }

    public void updateMergeHead(CoilHeadDTO head) {
        headRedisTemplate.opsForValue().set(COIL_MERGE_HEAD_KEY, head);
    }

    public void removeHead(String matId) {
        List<CoilHeadDTO> headList = getHeadList();
        if (CollUtil.isEmpty(headList)) {
            return;
        }
        // 倒序处理
        ListIterator<CoilHeadDTO> iterator = headList.listIterator(headList.size());
        while (iterator.hasPrevious()) {
            CoilHeadDTO exists = iterator.previous();
            if (Objects.equals(matId, exists.getMatId())) {
                iterator.remove();
            }
        }
        setHeadList(headList);
    }

    public void setMergeHead(CoilHeadDTO head) {
        headRedisTemplate.opsForValue().set(COIL_MERGE_HEAD_KEY, head);
        log.info("add merge head: {}", JSON.toJSONString(head));
    }

    public CoilHeadDTO getMergeHead() {
        return headRedisTemplate.opsForValue().get(COIL_MERGE_HEAD_KEY);
    }

    public void removeMergeHead() {
        CoilHeadDTO head = this.getMergeHead();
        if (Objects.nonNull(head)) {
            headRedisTemplate.delete(COIL_MERGE_HEAD_KEY);
            log.info("remove merge head: {}", JSON.toJSONString(head));
        }
    }

    public void setPrevAndMergeHead(CoilHeadDTO head, Integer workTr) {
        this.setPrevHead(head);
        // 卷取机上有卷时才设置mergeHead
        Integer trIdx = CoilMeasUtil.getTrIdx(workTr);
        if (Objects.nonNull(trIdx) && MatmapUtil.already(MatmapUtil.getMatmap(trIdx))) {
            this.setMergeHead(head);
        }
    }

    public void setPrevHead(CoilHeadDTO head) {
        headRedisTemplate.opsForValue().set(COIL_PREV_HEAD_KEY, head);
        log.info("add prev head: {}", JSON.toJSONString(head));
    }

    public CoilHeadDTO getPrevHead() {
        return headRedisTemplate.opsForValue().get(COIL_PREV_HEAD_KEY);
    }

    private void removePrevHead() {
        headRedisTemplate.delete(COIL_PREV_HEAD_KEY);
    }

    public double getMergeEndPos() {
        return Optional.ofNullable((Double) RedisUtil.getValue(COIL_MERGE_END_POS_KEY)).orElse(0d);
    }

    public void setMergeEndPos(double value) {
        RedisUtil.setValue(COIL_MERGE_END_POS_KEY, value);
    }

    public double getLastEndPos() {
        return Optional.ofNullable((Double) RedisUtil.getValue(EXIT_CUT_LAST_END_POS_KEY)).orElse(0d);
    }

    public void setLastEndPos(double value) {
        RedisUtil.setValue(EXIT_CUT_LAST_END_POS_KEY, value);
    }

    public double getDefectStartPos(int posIdx) {
        Double value;
        if (CoilMeasUtil.getPorIdxList().contains(posIdx)) {
            value = RedisUtil.getValue(HEAD_DEFECT_START_POS);
        } else {
            value = RedisUtil.getValue(TAIL_DEFECT_START_POS);
        }
        return Optional.ofNullable(value).orElse(0d);
    }

    public void setHeadDefectStartPos(Double value) {
        RedisUtil.setValue(HEAD_DEFECT_START_POS, value);
    }

    public void setTailDefectStartPos(Double value) {
        RedisUtil.setValue(TAIL_DEFECT_START_POS, value);
    }



    public void delCoilSetup(String matId) {
        RedisUtil.delete(Lists.newArrayList(COIL_SETUP_KEY + matId));
    }

    private boolean isValidSignal(Integer counter, String redisKey) {
        if (counter == null) return false;
        Integer cnt = RedisUtil.getValue(redisKey);
        // 第一次信号不处理
        if (Objects.isNull(cnt)) {
            // 更新缓存计数器
            RedisUtil.setValue(redisKey, counter);
            return false;
        }
        if (!Objects.equals(cnt, counter)) {
            // 更新缓存计数器
            RedisUtil.setValue(redisKey, counter);
            return true;
        }
        return false;
    }

    public boolean isExitCut(Integer exitCutCounter) {
        return isValidSignal(exitCutCounter, COUNTER_EXIT_CUT_KEY);
    }

    public boolean isHeadCut(Integer headCutCounter) {
        return isValidSignal(headCutCounter, COUNTER_HEAD_CUT_KEY);
    }

    public boolean isPorOnline(Integer porOnlineCounter) {
        return isValidSignal(porOnlineCounter, COUNTER_POR_ONLINE_KEY);
    }

    public boolean isPorPayOver(Integer porPayOverCounter) {
        return isValidSignal(porPayOverCounter, COUNTER_POR_PAY_OVER_KEY);
    }

    public boolean isTrUnload(Integer trUnloadCounter) {
        return isValidSignal(trUnloadCounter, COUNTER_TR_UNLOAD_KEY);
    }

    public void setPointAlarm(String point, String type, double value) {
        String key = ALARM_KEY_PREFIX + point + "-" + type;
        RedisUtil.setValue(key, value, 5 * 1000);
    }

    public boolean hasPointAlarm(String point, String type) {
        String key = ALARM_KEY_PREFIX + point + "-" + type;
        return RedisUtil.getValue(key) != null;
    }

    /**
     * 获取虚拟计划
     *
     * @param planId 计划ID
     * @return 虚拟计划
     */
    public PdiPlanVO getVirtualPlan(Long planId) {
        Object value = jsonObjectRedisTemplate.opsForHash().get(VIRTUAL_PLAN_KEY, planId.toString());
        return BeanUtil.toBean(value, PdiPlanVO.class);
    }

    public void deleteExpireVirtualPlan() {
        Set<Object> keys = jsonObjectRedisTemplate.opsForHash().keys(VIRTUAL_PLAN_KEY);
        if (CollUtil.isEmpty(keys)) return;

        long curr = DateUtil.yesterday().getTime();
        String[] expireKeys = keys.stream().map(Object::toString).filter(string -> Long.parseLong(string) < curr).toArray(String[]::new);
        if (expireKeys.length > 0) {
            jsonObjectRedisTemplate.opsForHash().delete(VIRTUAL_PLAN_KEY, expireKeys);
        }
    }

    /**
     * 缓存新增虚拟计划
     *
     * @param plan 虚拟计划
     */
    public void putVirtualPlan(PdiPlanVO plan) {
        JSONObject value = JSONUtil.parseObj(plan);
        String planId = plan.getId().toString();
        jsonObjectRedisTemplate.opsForHash().put(VIRTUAL_PLAN_KEY, planId, value);
    }
}
