package org.dromara.app.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.app.domain.AppSchedule;
import org.dromara.app.domain.AppScheduleDetail;
import org.dromara.app.domain.AppSchedulingDevice;
import org.dromara.app.domain.bo.AppScheduleBo;
import org.dromara.app.domain.vo.AppScheduleVo;
import org.dromara.app.mapper.AppScheduleDetailMapper;
import org.dromara.app.mapper.AppScheduleMapper;
import org.dromara.app.mapper.AppSchedulingDeviceMapper;
import org.dromara.app.service.IAppScheduleService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

@Slf4j
@RequiredArgsConstructor
@Service
public class AppScheduleServiceImpl implements IAppScheduleService {

    private final AppScheduleMapper baseMapper;
    private final AppScheduleDetailMapper scheduleDetailMapper;
    private final AppSchedulingDeviceMapper schedulingDeviceMapper;

    @Override
    public AppScheduleVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    @Override
    public TableDataInfo<AppScheduleVo> queryPageList(AppScheduleBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<AppSchedule> lqw = buildQueryWrapper(bo);
        Page<AppScheduleVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    @Override
    public List<AppScheduleVo> queryList(AppScheduleBo bo) {
        LambdaQueryWrapper<AppSchedule> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<AppSchedule> buildQueryWrapper(AppScheduleBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<AppSchedule> lqw = Wrappers.lambdaQuery();
        lqw.orderByAsc(AppSchedule::getId);
        lqw.like(StringUtils.isNotBlank(bo.getName()), AppSchedule::getName, bo.getName());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), AppSchedule::getStatus, bo.getStatus());
        lqw.eq(bo.getUserId() != null, AppSchedule::getUserId, bo.getUserId());
        return lqw;
    }

    @Override
    @Transactional
    public AppScheduleVo insertByBo(AppScheduleBo bo) {
        AppSchedule add = MapstructUtils.convert(bo, AppSchedule.class);
        add.setUserId(LoginHelper.getUserId());
        validEntityBeforeSave(add);

        boolean flag = baseMapper.insert(add) > 0;
        if (!flag) {
            return new AppScheduleVo();
        }

        AppScheduleVo vo = MapstructUtils.convert(add, AppScheduleVo.class);
        vo.setDetails(saveDetails(add.getId(), bo.getDetails()));
        return vo;
    }

    @Override
    public Boolean updateStatusByBo(AppScheduleBo bo) {
        AppSchedule update = MapstructUtils.convert(bo, AppSchedule.class);
        Long userId = bo.getUserId() == null ? LoginHelper.getUserId() : bo.getUserId();
        return baseMapper.update(
            update,
            Wrappers.<AppSchedule>lambdaQuery()
                .eq(AppSchedule::getId, bo.getId())
                .eq(AppSchedule::getUserId, userId)
        ) > 0;
    }

    @Override
    @Transactional
    public Map<String, Object> updateByBo(AppScheduleBo bo) {
        AppSchedule update = MapstructUtils.convert(bo, AppSchedule.class);
        validEntityBeforeSave(update);

        Map<String, Object> repMap = new HashMap<>();
        Long userId = bo.getUserId() == null ? LoginHelper.getUserId() : bo.getUserId();
        Boolean flag = baseMapper.update(
            update,
            Wrappers.<AppSchedule>lambdaQuery()
                .eq(AppSchedule::getId, bo.getId())
                .eq(AppSchedule::getUserId, userId)
        ) > 0;
        repMap.put("flag", flag);
        repMap.put("list", new ArrayList<>());
        repMap.put("appSchedule", "");
        if (!flag) {
            return repMap;
        }

        AppScheduleVo appScheduleVo = baseMapper.selectVoById(update.getId());
        appScheduleVo.setDetails(updateDetails(bo.getDetails()));
        repMap.put("appSchedule", appScheduleVo);
        return repMap;
    }

    private void validEntityBeforeSave(AppSchedule entity) {
        // TODO business validation
    }

    @Override
    @Transactional
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        boolean flag = baseMapper.deleteByIds(ids) > 0;
        if (flag) {
            for (Long id : ids) {
                schedulingDeviceMapper.delete(new QueryWrapper<AppSchedulingDevice>().eq("schedule_id", id));
                scheduleDetailMapper.delete(new QueryWrapper<AppScheduleDetail>().eq("schedule_id", id));
            }
        }
        return flag;
    }

    public List checkConflict(String deviceNo, AppScheduleVo newSchedule) {
        List<AppSchedulingDevice> existingBindings = schedulingDeviceMapper.selectList(
            new QueryWrapper<AppSchedulingDevice>().eq("device_no", deviceNo)
        );
        List conflicts = new ArrayList<>();
        List<AppScheduleDetail> scheduleDetails = scheduleDetailMapper.selectList(
            new QueryWrapper<AppScheduleDetail>().eq("schedule_id", newSchedule.getId())
        );

        for (AppScheduleDetail newDay : scheduleDetails) {
            for (AppSchedulingDevice existing : existingBindings) {
                AppScheduleVo appScheduleVo = baseMapper.selectVoById(existing.getScheduleId());
                if (ObjectUtil.isNotNull(appScheduleVo) && !"0".equals(appScheduleVo.getStatus())) {
                    List<AppScheduleDetail> details = scheduleDetailMapper.selectList(
                        new QueryWrapper<AppScheduleDetail>().eq("schedule_id", existing.getScheduleId())
                    );

                    for (AppScheduleDetail existingDay : details) {
                        if (newDay.getWeekday().equals(existingDay.getWeekday())) {
                            // TODO time overlap validation
                        }
                    }
                }
            }
        }
        return conflicts;
    }

    private List<Map<String, Object>> saveDetails(Long scheduleId, List<AppScheduleBo.AppScheduleDetail> details) {
        List<Map<String, Object>> detailList = new ArrayList<>();
        if (details == null) {
            return detailList;
        }
        for (AppScheduleBo.AppScheduleDetail item : details) {
            AppScheduleDetail detail = new AppScheduleDetail();
            detail.setScheduleId(scheduleId);
            detail.setWeekday(item.getWeekday());
            if (ObjectUtil.isNotNull(item.getTimeData())) {
                detail.setTimeData(JsonUtils.toJsonString(item.getTimeData()));
            }
            detail.setZones(item.getZones());
            detail.setTriggerType(item.getTriggerType());
            detail.setStatus(item.getStatus());
            scheduleDetailMapper.insert(detail);
            detailList.add(toDetailMap(detail, item.getTimeData()));
        }
        return detailList;
    }

    private List<Map<String, Object>> updateDetails(List<AppScheduleBo.AppScheduleDetail> details) {
        List<Map<String, Object>> detailList = new ArrayList<>();
        if (details == null) {
            return detailList;
        }
        for (AppScheduleBo.AppScheduleDetail item : details) {
            AppScheduleDetail detail = new AppScheduleDetail();
            detail.setId(item.getId());
            detail.setWeekday(item.getWeekday());
            detail.setTimeData(JsonUtils.toJsonString(item.getTimeData()));
            detail.setZones(item.getZones());
            detail.setTriggerType(item.getTriggerType());
            detail.setStatus(item.getStatus());
            scheduleDetailMapper.updateById(detail);
            detailList.add(toDetailMap(detail, item.getTimeData()));
        }
        return detailList;
    }

    private Map<String, Object> toDetailMap(AppScheduleDetail detail, List<AppScheduleBo.TimeSlot> timeData) {
        Map<String, Object> map = new HashMap<>();
        map.put("id", detail.getId());
        map.put("weekday", detail.getWeekday());
        map.put("timeData", timeData == null ? new ArrayList<>() : new ArrayList<>(timeData));
        map.put("triggerType", detail.getTriggerType());
        map.put("status", detail.getStatus());
        map.put("createTime", detail.getCreateTime());
        return map;
    }
}
