package com.qmth.exam.reserve.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.boot.core.collection.PageResult;
import com.qmth.boot.core.exception.StatusException;
import com.qmth.boot.tools.excel.ExcelReader;
import com.qmth.boot.tools.excel.enums.ExcelType;
import com.qmth.boot.tools.excel.model.DataMap;
import com.qmth.exam.reserve.bean.applytask.CurrentApplyTaskVO;
import com.qmth.exam.reserve.bean.category.CategoryCacheBean;
import com.qmth.exam.reserve.bean.category.CategoryInfo;
import com.qmth.exam.reserve.bean.login.LoginUser;
import com.qmth.exam.reserve.bean.stdapply.CategoryVO;
import com.qmth.exam.reserve.bean.teaching.TeachingReq;
import com.qmth.exam.reserve.bean.teaching.TeachingSaveReq;
import com.qmth.exam.reserve.bean.teaching.TeachingSingleVO;
import com.qmth.exam.reserve.bean.teaching.TeachingVO;
import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
import com.qmth.exam.reserve.cache.impl.CategoryCacheService;
import com.qmth.exam.reserve.dao.CategoryDao;
import com.qmth.exam.reserve.entity.CategoryEntity;
import com.qmth.exam.reserve.enums.CategoryLevel;
import com.qmth.exam.reserve.enums.Role;
import com.qmth.exam.reserve.service.CategoryService;
import com.qmth.exam.reserve.util.PageUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

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

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    private static final String[] EXCEL_HEADER = new String[]{"教学点代码", "教学点名称", "教学点所在城市"};

    @Autowired
    private ApplyTaskCacheService applyTaskCacheService;

    @Autowired
    private CategoryCacheService categoryCacheService;

    @Override
    public List<CategoryVO> listTeaching(LoginUser user, Boolean flag) {
        flag = flag != null && flag;
        QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
        LambdaQueryWrapper<CategoryEntity> lw = wrapper.lambda();
        List<CategoryEntity> categoryList = new ArrayList<>();
        // 学校管理员
        if (user.getRole().equals(Role.ADMIN)) {
            lw.eq(CategoryEntity::getLevel, CategoryLevel.TEACHING.getValue());
            lw.eq(flag, CategoryEntity::getEnable, Boolean.TRUE);
            categoryList = this.getBaseMapper().selectList(wrapper);
        } else if (user.getRole().equals(Role.TEACHING)) { // 教学点管理员
            lw.eq(CategoryEntity::getId, user.getCategoryId());
            categoryList = this.getBaseMapper().selectList(wrapper);
        } else {
            throw new StatusException("用户无权限");
        }
        List<CategoryVO> cgList = new ArrayList<>();
        for (CategoryEntity category : categoryList) {
            CategoryVO cg = new CategoryVO();
            cg.setId(category.getId());
            cg.setName(category.getName());
            cgList.add(cg);
        }

        return cgList;
    }

    /**
     * 获取数据分类列表（树结构）
     */
    @Override
    public List<CategoryInfo> getCategoryTreeForStudent(LoginUser student) {
        if (Role.STUDENT != student.getRole()) {
            throw new StatusException("请使用学生账号登录！");
        }

        List<CategoryInfo> list = new ArrayList<>();

        // 获取当前启用的预约任务
        CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(student.getOrgId());
        if (curApplyTask == null) {
            return list;
        }

        if (curApplyTask.getOpenApplyStartTime() > System.currentTimeMillis()) {
            // 自主预约截止时间 之前，只能预约本教学点下的考点

            CategoryCacheBean curCategory = categoryCacheService.getCategoryById(student.getCategoryId());
            if (curCategory == null) {
                return list;
            }

            CategoryInfo curInfo = new CategoryInfo();
            curInfo.setId(curCategory.getId());
            curInfo.setName(curCategory.getName());
            curInfo.setLevel(curCategory.getLevel());
            curInfo.setParentId(curCategory.getParentId());
            curInfo.setSubNodes(new ArrayList<>());

            // 根节点
            CategoryInfo root = this.fillParentCategory(curInfo);
            list.add(root);

            return list;
        }

        // 开放式预约时间，可以在不同教学点间预约
        LambdaQueryWrapper<CategoryEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CategoryEntity::getOrgId, student.getOrgId());
        wrapper.eq(CategoryEntity::getEnable, Boolean.TRUE);
        List<CategoryEntity> categoryList = baseMapper.selectList(wrapper);

        Map<Long, List<CategoryEntity>> categoryMaps = categoryList.stream()
                .collect(Collectors.groupingBy(CategoryEntity::getParentId));
        for (CategoryEntity curEntity : categoryList) {
            if (curEntity.getLevel() > 1) {
                continue;
            }

            // 从根节点开始封装
            CategoryInfo curInfo = new CategoryInfo();
            curInfo.setId(curEntity.getId());
            curInfo.setName(curEntity.getName());
            curInfo.setLevel(curEntity.getLevel());
            curInfo.setParentId(curEntity.getParentId());
            this.fillSubCategory(curInfo, categoryMaps);

            list.add(curInfo);
        }

        return list;
    }

    /**
     * 递归封装子级分类
     */
    private void fillSubCategory(CategoryInfo cur, Map<Long, List<CategoryEntity>> categoryMaps) {
        List<CategoryInfo> subNodes = new ArrayList<>();

        if (categoryMaps.containsKey(cur.getId())) {
            categoryMaps.get(cur.getId()).forEach(entity -> {
                CategoryInfo info = new CategoryInfo();
                info.setId(entity.getId());
                info.setName(entity.getName());
                info.setLevel(entity.getLevel());
                info.setParentId(entity.getParentId());
                this.fillSubCategory(info, categoryMaps);
                subNodes.add(info);
            });
        }

        cur.setSubNodes(subNodes);
    }

    /**
     * 递归封装父级分类
     */
    private CategoryInfo fillParentCategory(CategoryInfo cur) {
        if (cur.getLevel() <= 1) {
            // 根节点，直接返回
            return cur;
        }

        CategoryCacheBean parent = categoryCacheService.getCategoryById(cur.getParentId());
        if (parent == null) {
            return cur;
        }

        CategoryInfo info = new CategoryInfo();
        info.setId(parent.getId());
        info.setName(parent.getName());
        info.setLevel(parent.getLevel());
        info.setParentId(parent.getParentId());

        List<CategoryInfo> subNodes = new ArrayList<>();
        subNodes.add(cur);
        info.setSubNodes(subNodes);

        return fillParentCategory(info);
    }

    @Override
    public PageResult<TeachingVO> page(TeachingReq req) {
        if (req.getLevel() == null) {
            req.setLevel(CategoryLevel.TEACHING.getValue());
        }
        IPage<TeachingVO> iPage = baseMapper.page(new Page<>(req.getPageNumber(), req.getPageSize()),
                req);
        return PageUtil.of(iPage);
    }

    @Transactional
    @Override
    public void saveTeaching(LoginUser user, TeachingSaveReq req) {
        checkTeaching(user.getOrgId(), req);
        CategoryEntity category = new CategoryEntity();
        BeanUtils.copyProperties(req, category);
        category.setLevel(CategoryLevel.TEACHING.getValue());
        category.setOrgId(user.getOrgId());
        category.setParentId(getById(req.getCityId()).getId());
        if (req.getId() == null) {
            category.setEnable(Boolean.TRUE);
            save(category);
        } else {
            updateById(category);
            //清空教学点缓存
            categoryCacheService.clearCategoryByIdCache(category.getId());
        }
    }

    @Override
    public void enable(Long id, Boolean enable) {
        CategoryEntity category = getById(id);
        category.setEnable(enable);
        updateById(category);
    }

    private void checkTeaching(Long orgId, TeachingSaveReq req) {
        if (StringUtils.isBlank(req.getCode())) {
            throw new StatusException("教学点代码不能为空");
        }
        if (StringUtils.isBlank(req.getName())) {
            throw new StatusException("教学点名称不能为空");
        }
        if (req.getCityId() == null) {
            throw new StatusException("请选择教学点所在城市");
        }
        checkTeachingExist(orgId, req, req.getId() != null);
        checkTeachingNameExist(req.getId(), req.getName());
    }

    private void checkTeachingExist(Long orgId, TeachingSaveReq req, boolean idFlag) {
        LambdaQueryWrapper<CategoryEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CategoryEntity::getCode, req.getCode());
        wrapper.eq(CategoryEntity::getOrgId, orgId);
        wrapper.notIn(idFlag, CategoryEntity::getId, req.getId());
        CategoryEntity categoryEntity = baseMapper.selectOne(wrapper);
        if (categoryEntity != null) {
            throw new StatusException("教学点代码已经存在");
        }
    }

    private void checkTeachingNameExist(Long id, String teachingName) {
        LambdaQueryWrapper<CategoryEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CategoryEntity::getName, teachingName);
        wrapper.notIn(id != null, CategoryEntity::getId, id);
        List<CategoryEntity> categoryEntities = baseMapper.selectList(wrapper);
        if (!categoryEntities.isEmpty()) {
            throw new StatusException(" 教学点名称已经存在");
        }
    }

    @Override
    public List<CategoryVO> listCity(LoginUser user) {
        LambdaQueryWrapper<CategoryEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CategoryEntity::getOrgId, user.getOrgId());
        wrapper.eq(CategoryEntity::getEnable, Boolean.TRUE);
        wrapper.eq(CategoryEntity::getLevel, CategoryLevel.CITY.getValue());
        wrapper.orderByAsc(CategoryEntity::getId);
        List<CategoryEntity> cityList = this.list(wrapper);
        List<CategoryVO> cityVOList = new ArrayList<>();
        for (CategoryEntity city : cityList) {
            CategoryVO cityVO = new CategoryVO();
            cityVO.setId(city.getId());
            cityVO.setName(city.getName());
            cityVOList.add(cityVO);
        }
        return cityVOList;
    }

    @Override
    public List<Map<String, Object>> importTeaching(LoginUser user, InputStream inputStream) {
        List<DataMap> lineList = null;
        ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
        try {
            lineList = reader.getDataMapList();
        } catch (Exception e) {
            throw new StatusException("Excel 解析失败");
        }
        if (CollectionUtils.isEmpty(lineList)) {
            throw new StatusException("Excel无内容");
        }
        List<Map<String, Object>> failRecords = new ArrayList<Map<String, Object>>();
        List<CategoryEntity> categoryList = new ArrayList<>();
        List<CategoryVO> cityList = listCity(user);
        for (int i = 0; i < lineList.size(); i++) {
            CategoryEntity category = new CategoryEntity();
            DataMap line = lineList.get(i);
            StringBuilder msg = new StringBuilder();
            String code = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
            if (StringUtils.isBlank(code)) {
                msg.append(" 教学点代码不能为空");
            } else if (code.length() > 20) {
                msg.append(" 教学点代码不能超过20个字符");
            } else {
                category.setCode(code);
            }
            String name = trimAndNullIfBlank(line.get(EXCEL_HEADER[1]));
            if (StringUtils.isBlank(name)) {
                msg.append(" 教学点名称不能为空");
            } else if (name.length() > 50) {
                msg.append(" 教学点名称不能超过50个字符");
            } else {
                List<CategoryEntity> categoryEntities = listTeachingByName(name);
                if(categoryEntities != null && !categoryEntities.isEmpty()) {
                    msg.append(" 教学点名称不能重复");
                } else {
                    category.setName(name);
                }
            }
            String cityName = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
            Long cityId = null;
            if (StringUtils.isBlank(cityName)) {
                msg.append(" 教学点所在城市不能为空");
            } else {
                cityId = getIdByName(cityName, cityList);
                if (cityId == null) {
                    msg.append(" 教学点所在城市不存在");
                } else {
                    category.setParentId(cityId);
                }
            }
            if (msg.length() > 0) {
                failRecords.add(newError(i + 1, msg.toString()));
            } else {
                category.setEnable(Boolean.TRUE);
                category.setLevel(CategoryLevel.TEACHING.getValue());
                category.setOrgId(user.getOrgId());
                categoryList.add(category);
            }
        }

        if (CollectionUtils.isNotEmpty(failRecords)) {
            return failRecords;
        }
        for (int i = 0; i < categoryList.size(); i++) {
            CategoryEntity category = categoryList.get(i);
            try {
                saveTeaching(category);
            } catch (Exception e) {
                failRecords.add(newError(i + 1, " 系统异常"));
                log.error("导入异常", e);
            }
        }
        if (CollectionUtils.isNotEmpty(failRecords)) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return failRecords;
        }
        return failRecords;
    }

    private List<CategoryEntity> listTeachingByName(String name) {
        LambdaQueryWrapper<CategoryEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CategoryEntity::getName, name);
        return list(wrapper);
    }

    @Override
    public void updateTeachingCapacity(Long teachingId) {
        baseMapper.updateTeachingCapacity(teachingId);
    }

    @Override
    public TeachingSingleVO getTeaching(Long id) {
        CategoryEntity category = getById(id);
        TeachingSingleVO vo = new TeachingSingleVO();
        vo.setId(category.getId());
        vo.setCode(category.getCode());
        vo.setName(category.getName());
        vo.setCityId(category.getParentId());
        vo.setEnable(category.getEnable());
        vo.setLevel(category.getLevel());
        return vo;
    }

    private void saveTeaching(CategoryEntity category) {
        LambdaQueryWrapper<CategoryEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CategoryEntity::getCode, category.getCode());
        wrapper.eq(CategoryEntity::getOrgId, category.getOrgId());
        CategoryEntity existCategory = baseMapper.selectOne(wrapper);
        if (existCategory != null) {
            existCategory.setParentId(category.getParentId());
            existCategory.setName(category.getName());
            existCategory.setEnable(Boolean.TRUE);
            baseMapper.updateById(existCategory);
        } else {
            baseMapper.insert(category);
        }
    }
    

    private Long getIdByName(String cityName, List<CategoryVO> cityList) {
        for (CategoryVO city : cityList) {
            if (city.getName().equals(cityName)) {
                return city.getId();
            }
        }
        return null;
    }

    private String trimAndNullIfBlank(String s) {
        if (StringUtils.isBlank(s)) {
            return null;
        }
        return s.trim();
    }

    private Map<String, Object> newError(int lineNum, String msg) {
        Map<String, Object> map = new HashMap<>();
        map.put("lineNum", lineNum);
        map.put("msg", msg);
        return map;
    }

}
