package com.haha.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.haha.common.enums.CommonStatus;
import com.haha.common.constant.ResponseEnum;
import com.haha.common.constant.RedisConstants;
import com.haha.common.exception.BusinessException;
import com.haha.common.utils.PageUtil;
import com.haha.entity.dto.DictDataQueryDTO;
import com.haha.entity.dto.DictTypeQueryDTO;
import com.haha.entity.DictData;
import com.haha.entity.DictLog;
import com.haha.entity.DictType;
import com.haha.mapper.DictDataMapper;
import com.haha.mapper.DictLogMapper;
import com.haha.mapper.DictTypeMapper;
import com.haha.service.DictService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@RequiredArgsConstructor
@Service
public class DictServiceImpl extends ServiceImpl<DictTypeMapper, DictType> implements DictService {

    private static final String DICT_CACHE_KEY = "dict:data:";
    private static final String DICT_ALL_CACHE_KEY = "dict:data:all";
    private static final long CACHE_EXPIRE_HOURS = 24;

    private final DictTypeMapper dictTypeMapper;
    private final DictDataMapper dictDataMapper;
    private final DictLogMapper dictLogMapper;
    private final StringRedisTemplate redisTemplate;

    @Override
    public Map<String, Object> getDictTypeList(DictTypeQueryDTO queryDTO) {
        int page = queryDTO.getPage();
        int pageSize = queryDTO.getPageSize();
        String dictCode = queryDTO.getDictCode();
        String dictName = queryDTO.getDictName();
        Integer status = queryDTO.getStatus();

        LambdaQueryWrapper<DictType> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.hasText(dictCode)) {
            wrapper.like(DictType::getDictCode, dictCode);
        }
        if (StringUtils.hasText(dictName)) {
            wrapper.like(DictType::getDictName, dictName);
        }
        if (status != null) {
            wrapper.eq(DictType::getStatus, status);
        }
        wrapper.orderByAsc(DictType::getSortOrder);
        wrapper.orderByDesc(DictType::getCreateTime);

        Page<DictType> pageObj = new Page<>(page, pageSize);
        Page<DictType> result = dictTypeMapper.selectPage(pageObj, wrapper);

        return PageUtil.buildPageResult(result, page, pageSize);
    }

    @Override
    public DictType getDictTypeById(Long id) {
        DictType dictType = dictTypeMapper.selectById(id);
        ResponseEnum.DICT_TYPE_NOT_FOUND.assertNotNull(dictType);
        return dictType;
    }

    @Override
    public DictType getDictTypeByCode(String dictCode) {
        LambdaQueryWrapper<DictType> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DictType::getDictCode, dictCode);
        DictType dictType = dictTypeMapper.selectOne(wrapper);
        ResponseEnum.DICT_TYPE_NOT_FOUND.assertNotNull(dictType);
        return dictType;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addDictType(DictType dictType) {
        LambdaQueryWrapper<DictType> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DictType::getDictCode, dictType.getDictCode());
        if (dictTypeMapper.selectCount(wrapper) > 0) {
            throw new BusinessException(ResponseEnum.DICT_CODE_DUPLICATE);
        }

        dictType.setCreateTime(LocalDateTime.now());
        dictType.setUpdateTime(LocalDateTime.now());
        dictTypeMapper.insert(dictType);

        saveDictLog(dictType, null, "insert", "新增字典类型");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateDictType(DictType dictType) {
        DictType oldDictType = dictTypeMapper.selectById(dictType.getId());
        ResponseEnum.DICT_TYPE_NOT_FOUND.assertNotNull(oldDictType);

        dictType.setUpdateTime(LocalDateTime.now());
        dictTypeMapper.updateById(dictType);

        saveDictLog(dictType, oldDictType, "update", "修改字典类型");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteDictType(Long id) {
        DictType dictType = dictTypeMapper.selectById(id);
        ResponseEnum.DICT_TYPE_NOT_FOUND.assertNotNull(dictType);

        if (dictType.getIsSystem() != null && dictType.getIsSystem() == 1) {
            throw new BusinessException(ResponseEnum.DICT_SYSTEM_NO_DELETE);
        }

        dictDataMapper.deleteByDictCode(dictType.getDictCode());
        dictTypeMapper.deleteById(id);

        clearDictCache(dictType.getDictCode());

        saveDictLog(dictType, null, "delete", "删除字典类型");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchDeleteDictType(List<Long> ids) {
        for (Long id : ids) {
            DictType dictType = dictTypeMapper.selectById(id);
            if (dictType != null && (dictType.getIsSystem() == null || dictType.getIsSystem() != 1)) {
                dictDataMapper.deleteByDictCode(dictType.getDictCode());
                dictTypeMapper.deleteById(id);
                clearDictCache(dictType.getDictCode());
            }
        }
    }

    @Override
    public List<DictData> getDictDataList(String dictCode) {
        return getDictDataByCode(dictCode);
    }

    @Override
    public Map<String, Object> getDictDataPage(DictDataQueryDTO queryDTO) {
        int page = queryDTO.getPage();
        int pageSize = queryDTO.getPageSize();
        String dictCode = queryDTO.getDictCode();
        String itemLabel = queryDTO.getItemLabel();
        Integer status = queryDTO.getStatus();

        LambdaQueryWrapper<DictData> wrapper = new LambdaQueryWrapper<>();
        if (StringUtils.hasText(dictCode)) {
            wrapper.eq(DictData::getDictCode, dictCode);
        }
        if (StringUtils.hasText(itemLabel)) {
            wrapper.like(DictData::getItemLabel, itemLabel);
        }
        if (status != null) {
            wrapper.eq(DictData::getStatus, status);
        }
        wrapper.orderByAsc(DictData::getSortOrder);
        wrapper.orderByDesc(DictData::getCreateTime);

        Page<DictData> pageObj = new Page<>(page, pageSize);
        Page<DictData> result = dictDataMapper.selectPage(pageObj, wrapper);

        return PageUtil.buildPageResult(result, page, pageSize);
    }

    @Override
    public DictData getDictDataById(Long id) {
        DictData dictData = dictDataMapper.selectById(id);
        ResponseEnum.DICT_DATA_NOT_FOUND.assertNotNull(dictData);
        return dictData;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addDictData(DictData dictData) {
        LambdaQueryWrapper<DictData> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DictData::getDictCode, dictData.getDictCode());
        wrapper.eq(DictData::getItemValue, dictData.getItemValue());
        if (dictDataMapper.selectCount(wrapper) > 0) {
            throw new BusinessException(ResponseEnum.DICT_ITEM_VALUE_DUPLICATE);
        }

        dictData.setCreateTime(LocalDateTime.now());
        dictData.setUpdateTime(LocalDateTime.now());
        dictDataMapper.insert(dictData);

        clearDictCache(dictData.getDictCode());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateDictData(DictData dictData) {
        DictData oldDictData = dictDataMapper.selectById(dictData.getId());
        ResponseEnum.DICT_DATA_NOT_FOUND.assertNotNull(oldDictData);

        if (!oldDictData.getItemValue().equals(dictData.getItemValue())) {
            LambdaQueryWrapper<DictData> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(DictData::getDictCode, oldDictData.getDictCode());
            wrapper.eq(DictData::getItemValue, dictData.getItemValue());
            wrapper.ne(DictData::getId, dictData.getId());
            if (dictDataMapper.selectCount(wrapper) > 0) {
                throw new BusinessException(ResponseEnum.DICT_ITEM_VALUE_DUPLICATE);
            }
        }

        dictData.setUpdateTime(LocalDateTime.now());
        dictDataMapper.updateById(dictData);

        clearDictCache(oldDictData.getDictCode());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteDictData(Long id) {
        DictData dictData = dictDataMapper.selectById(id);
        ResponseEnum.DICT_DATA_NOT_FOUND.assertNotNull(dictData);

        dictDataMapper.deleteById(id);

        clearDictCache(dictData.getDictCode());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchDeleteDictData(List<Long> ids) {
        Set<String> dictCodes = new HashSet<>();
        for (Long id : ids) {
            DictData dictData = dictDataMapper.selectById(id);
            if (dictData != null) {
                dictCodes.add(dictData.getDictCode());
                dictDataMapper.deleteById(id);
            }
        }

        for (String dictCode : dictCodes) {
            clearDictCache(dictCode);
        }
    }

    @Override
    public Map<String, List<DictData>> getAllDictData() {
        String cachedJson = redisTemplate.opsForValue().get(DICT_ALL_CACHE_KEY);
        if (cachedJson != null) {
            return JSON.parseObject(cachedJson, Map.class);
        }

        LambdaQueryWrapper<DictType> typeWrapper = new LambdaQueryWrapper<>();
        typeWrapper.eq(DictType::getStatus, CommonStatus.ENABLED.getCode());
        typeWrapper.orderByAsc(DictType::getSortOrder);
        List<DictType> dictTypes = dictTypeMapper.selectList(typeWrapper);

        Map<String, List<DictData>> cachedData = new LinkedHashMap<>();

        for (DictType dictType : dictTypes) {
            List<DictData> dataList = getDictDataByCode(dictType.getDictCode());
            cachedData.put(dictType.getDictCode(), dataList);
        }

        redisTemplate.opsForValue().set(DICT_ALL_CACHE_KEY, JSON.toJSONString(cachedData), CACHE_EXPIRE_HOURS, TimeUnit.HOURS);

        return cachedData;
    }

    @Override
    public List<DictData> getDictDataByCode(String dictCode) {
        String cacheKey = DICT_CACHE_KEY + dictCode;
        String cachedJson = redisTemplate.opsForValue().get(cacheKey);
        if (cachedJson != null) {
            return JSON.parseArray(cachedJson, DictData.class);
        }

        List<DictData> dataList = dictDataMapper.selectByDictCode(dictCode);

        redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(dataList), CACHE_EXPIRE_HOURS, TimeUnit.HOURS);

        return dataList;
    }

    @Override
    public void refreshCache() {
        Set<String> keys = redisTemplate.keys(DICT_CACHE_KEY + "*");
        if (keys != null && !keys.isEmpty()) {
            redisTemplate.delete(keys);
        }
        if (Boolean.TRUE.equals(redisTemplate.hasKey(DICT_ALL_CACHE_KEY))) {
            redisTemplate.delete(DICT_ALL_CACHE_KEY);
        }
        log.info("字典缓存已刷新");
    }

    @Override
    public String getDictLabel(String dictCode, String itemValue) {
        if (!StringUtils.hasText(dictCode) || !StringUtils.hasText(itemValue)) {
            return "";
        }
        List<DictData> dataList = getDictDataByCode(dictCode);
        for (DictData data : dataList) {
            if (itemValue.equals(data.getItemValue())) {
                return data.getItemLabel();
            }
        }
        return "";
    }

    @Override
    public String getDictValue(String dictCode, String itemLabel) {
        if (!StringUtils.hasText(dictCode) || !StringUtils.hasText(itemLabel)) {
            return "";
        }
        List<DictData> dataList = getDictDataByCode(dictCode);
        for (DictData data : dataList) {
            if (itemLabel.equals(data.getItemLabel())) {
                return data.getItemValue();
            }
        }
        return "";
    }

    @Override
    public void exportDictData(String dictCode) {
        // 导出功能暂未实现
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void importDictData(List<DictData> dataList) {
        if (dataList == null || dataList.isEmpty()) {
            throw new BusinessException(ResponseEnum.IMPORT_DATA_EMPTY);
        }

        Set<String> dictCodes = new HashSet<>();
        for (DictData data : dataList) {
            data.setCreateTime(LocalDateTime.now());
            data.setUpdateTime(LocalDateTime.now());
            dictDataMapper.insert(data);
            dictCodes.add(data.getDictCode());
        }

        for (String code : dictCodes) {
            clearDictCache(code);
        }
    }

    private void clearDictCache(String dictCode) {
        String cacheKey = DICT_CACHE_KEY + dictCode;
        redisTemplate.delete(cacheKey);
        redisTemplate.delete(DICT_ALL_CACHE_KEY);
    }

    private void saveDictLog(DictType dictType, DictType oldDictType, String operationType, String operationDesc) {
        try {
            DictLog logEntry = new DictLog();
            logEntry.setDictCode(dictType.getDictCode());
            logEntry.setDictName(dictType.getDictName());
            logEntry.setOperationType(operationType);
            logEntry.setOperationDesc(operationDesc);
            logEntry.setBeforeData(oldDictType != null ? JSON.toJSONString(oldDictType) : null);
            logEntry.setAfterData(JSON.toJSONString(dictType));
            logEntry.setCreateTime(LocalDateTime.now());
            dictLogMapper.insert(logEntry);
        } catch (Exception e) {
            log.error("保存字典日志失败", e);
        }
    }
}
