package com.ltgk.boat.service.impl;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ltgk.boat.domain.dto.DsEnterPortDTO;
import com.ltgk.boat.domain.dto.GetEnterPortReq;
import com.ltgk.boat.domain.dto.SetConfirmReq;
import com.ltgk.boat.domain.entity.DsBoatData;
import com.ltgk.boat.domain.entity.DsEnterPort;
import com.ltgk.boat.domain.entity.DsRegressPort;
import com.ltgk.boat.domain.vo.DsEnterPortVO;
import com.ltgk.boat.mapper.DsEnterPortMapper;
import com.ltgk.boat.service.DsBoatDataService;
import com.ltgk.boat.service.IDsEnterPortService;
import com.ltgk.boat.service.IDsRegressPortService;
import com.ltgk.common.constant.CacheConstants;
import com.ltgk.common.constant.SysConstant;
import com.ltgk.common.core.domain.AjaxResult;
import com.ltgk.common.core.domain.entity.SysRole;
import com.ltgk.common.core.domain.entity.SysUser;
import com.ltgk.common.core.redis.RedisCache;
import com.ltgk.common.enums.EnterOutPortStatus;
import com.ltgk.common.enums.PersonalDataEnums;
import com.ltgk.common.utils.DateUtils;
import com.ltgk.common.utils.SecurityUtils;
import com.ltgk.common.utils.bean.BeanUtils;
import com.ltgk.common.utils.poi.ExcelUtil;
import com.ltgk.common.utils.push.PublicUniPushUtils;
import com.ltgk.crew.domain.entity.DsBoatCrew;
import com.ltgk.crew.domain.entity.DsCrewEnterCheck;
import com.ltgk.crew.service.IDsBoatCrewService;
import com.ltgk.crew.service.IDsCrewEnterCheckService;
import com.ltgk.port.domain.entity.DsMainArea;
import com.ltgk.port.domain.entity.DsPort;
import com.ltgk.port.service.IDsMainAreaService;
import com.ltgk.port.service.IDsPortService;
import com.ltgk.system.service.ISysUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.ltgk.common.utils.PageUtils.startPage;


/**
 * 渔船进港信息Service业务层处理
 *
 * @author Qi ChengBin
 * @date 2024-12-19
 */
@Service
public class DsEnterPortServiceImpl extends ServiceImpl<DsEnterPortMapper, DsEnterPort> implements IDsEnterPortService {

    @Resource
    private IDsBoatCrewService dsBoatCrewService;
    @Resource
    private DsBoatDataService dsBoatDataService;
    @Resource
    private IDsPortService dsPortService;
    @Resource
    private IDsCrewEnterCheckService dsCrewEnterCheckService;
    @Resource
    private RedisCache redisCache;
    @Resource
    private ISysUserService userService;
    @Resource
    private PublicUniPushUtils publicUniPushUtils;
    @Resource
    private IDsMainAreaService dsMainAreaService;
    @Resource
    private IDsRegressPortService dsRegressPortService;

    /**
     * 分页查询渔船进港信息
     *
     * @param getEnterPortReq:
     * @return java.util.List<com.ltgk.boat.domain.entity.DsEnterPort>
     * @author Qi ChengBin
     * @date 2024/12/20 08:50
     */
    @Override
    public List<DsEnterPort> selectByPage(GetEnterPortReq getEnterPortReq) {
        getEnterPortReq.queryCheck();
        DsEnterPort dsEnterPort = new DsEnterPort();
        BeanUtils.copyProperties(getEnterPortReq, dsEnterPort);
        LambdaQueryWrapper<DsEnterPort> lqw = new LambdaQueryWrapper<>(dsEnterPort);

        String startTime = getEnterPortReq.getStartTime();
        String endTime = getEnterPortReq.getEndTime();
        boolean beNotBlankTime = StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime);

        lqw.between(beNotBlankTime, DsEnterPort::getEnterTime, startTime, endTime)
                .orderByDesc(DsEnterPort::getEnterTime);

        // 根据登录用户判断是否有查询自己数据的角色信息，只要有查询自己数据角色，就查询自己的数据
        List<SysRole> loginUserRoleList = SecurityUtils.getLoginUser().getUser().getRoles();

        if (loginUserRoleList.stream().anyMatch(item -> item.getRoleKey().equals(PersonalDataEnums.PERSONAL_DATA.getValue()))) {
            lqw.eq(DsEnterPort::getRegisterId, SecurityUtils.getUserId());
        }

        startPage(getEnterPortReq.getPageNum(), getEnterPortReq.getPageSize());
        List<DsEnterPort> dsEnterPortList = this.list(lqw);
        // 获取船员信息
        if (!dsEnterPortList.isEmpty()) {
            List<String> crewIdList = dsEnterPortList.stream()
                    .filter(Objects::nonNull)
                    .map(DsEnterPort::getCrewIds)
                    .filter(StringUtils::isNotBlank)
                    .flatMap(
                            crewIds -> {
                                if (StringUtils.isNotBlank(crewIds)) {
                                    return Arrays.stream(crewIds.split(","));
                                }
                                return Stream.empty();
                            }
                    )
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
            if (!crewIdList.isEmpty()) {

                List<DsBoatCrew> dsBoatCrewList = dsBoatCrewService.listByIds(crewIdList);

                Map<String, DsBoatCrew> dsBoatCrewMap = dsBoatCrewList.stream()
                        .collect(Collectors.toMap(DsBoatCrew::getId, crew -> crew));

                dsEnterPortList.forEach(item -> {
                    // 排除没有绑定船员的数据
                    if (StringUtils.isBlank(item.getCrewIds())) {
                        return;
                    }
                    List<String> crewIds = Arrays.stream(item.getCrewIds().split(","))
                            .filter(StringUtils::isNotBlank)
                            .collect(Collectors.toList());
                    List<DsBoatCrew> associatedCrewList = crewIds.stream()
                            .map(dsBoatCrewMap::get)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList());
                    item.setBoatCrewList(associatedCrewList);
                });
            }
        }
        return dsEnterPortList;
    }

    /**
     * 移动端添加渔船进港报备信息
     *
     * @param dsEnterPortDTO: 渔船进港信息数据传输对象
     * @return boolean
     * @author Qi ChengBin
     * @date 2024/12/20 09:07
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean addByApp(DsEnterPortDTO dsEnterPortDTO) {
        SysUser user = SecurityUtils.getLoginUser().getUser();

        DsEnterPort dsEnterPort = new DsEnterPort();
        BeanUtils.copyProperties(dsEnterPortDTO, dsEnterPort);
        // 渔港信息
        DsPort port = dsPortService.getById(dsEnterPortDTO.getPortId());
        // 获取渔船信息
        DsBoatData boatById = dsBoatDataService.getBoatById(dsEnterPortDTO.getBoatId(), dsEnterPortDTO.getBoatName());
        dsEnterPort
                .setPortId(port.getId())
                .setPortName(port.getPortName())
                .setBoatId(boatById.getBoatId())
                .setBoatName(boatById.getBoatName())
                .setBoatMaterial(boatById.getBoatMaterial())
                .setLength(boatById.getLength())
                .setMainPower(boatById.getMainPower())
                .setRegisterId(String.valueOf(user.getUserId()))
                .setRegisterName(user.getNickName())
                .setRegisterUnit(SecurityUtils.getDeptName())
                .setRegisterPhone(user.getPhonenumber())
                .setAreaCode(port.getAreaCode()).setAreaName(port.getAreaName())
                .setEnterTime(DateUtils.parseDate(dsEnterPortDTO.getEnterTime()))
                .setIsLocal(boatById.getIsLocal())
                .setStatus(EnterOutPortStatus.UN_CONFIRM.getCode())
                .setCreateBy(user.getNickName())
                .setCreateAt(DateUtils.getNowDate())
                .setUpdateBy(user.getNickName())
                .setUpdateAt(DateUtils.getNowDate());

        if (this.saveOrUpdate(dsEnterPort)) {
            List<String> crewIdList = Arrays.asList(dsEnterPortDTO.getCrewIds().split(","));
            // 根据申报查询船员信息
            LambdaQueryWrapper<DsBoatCrew> dsBoatCrewLambdaQueryWrapper = new LambdaQueryWrapper<>();
            dsBoatCrewLambdaQueryWrapper.in(DsBoatCrew::getId, crewIdList);
            List<DsBoatCrew> dsBoatCrewList = dsBoatCrewService.list(dsBoatCrewLambdaQueryWrapper);
            // 保存船员申请信息
            String dsEnterPortId = dsEnterPort.getId();
            List<DsCrewEnterCheck> dsCrewEnterCheckList = new ArrayList<>();

            dsBoatCrewList.forEach(dsBoatCrew -> {
                DsCrewEnterCheck dsCrewEnterCheck = new DsCrewEnterCheck();
                dsCrewEnterCheck.setEnterPortId(dsEnterPortId)
                        .setPortId(dsEnterPort.getPortId())
                        .setPortName(dsEnterPort.getPortName())
                        .setCrewName(dsBoatCrew.getCrewName())
                        .setCrewId(dsBoatCrew.getId())
                        .setIdCard(dsBoatCrew.getIdCardNo())
                        .setTel(dsBoatCrew.getPhone())
                        .setPosition(dsBoatCrew.getBoatDutyName())
                        .setAddress(dsBoatCrew.getAddress())
                        .setCertificateNumber(dsBoatCrew.getCertificateCode())
                        .setStatus(EnterOutPortStatus.UN_CONFIRM.getCode())
                        .setIdCardPic(dsBoatCrew.getIdCardPic())
                        .setRemark(dsBoatCrew.getRemarks())
                        // 预计进港时间
                        .setEnterTime(DateUtils.parseDate(dsEnterPortDTO.getEnterTime()))
                        .setCreateAt(DateUtils.getNowDate())
                        .setCreateBy(user.getNickName())
                        .setUpdateAt(DateUtils.getNowDate())
                        .setUpdateBy(user.getNickName());
                dsCrewEnterCheckList.add(dsCrewEnterCheck);
            });
            dsCrewEnterCheckService.saveBatch(dsCrewEnterCheckList);

            // 将渔船进港申请存储到 redis 中，以便在点位数据渔船进出港时判断是否申报；；；根据申报的 进港时间 + 24h - 当前时间 来作为redis key 的过期时间
            Date overdueTime = DateUtil.offset(dsEnterPort.getEnterTime(), DateField.HOUR, 24);
            int intervalTime = (int) DateUtil.between(new Date(), overdueTime, DateUnit.HOUR);
            redisCache.setCacheObject(CacheConstants.DS_ENTER_PORT_KEY + dsEnterPort.getBoatId(), dsEnterPort, intervalTime, TimeUnit.HOURS);

            return true;
        }
        return false;
    }

    /**
     * 渔船进港信息审核确认
     *
     * @param setConfirmReq: 渔船进港信息数据传输对象
     * @return boolean
     * @author Qi ChengBin
     * @date 2024/12/20 10:23
     */
    @Override
    public boolean confirm(SetConfirmReq setConfirmReq) {
        SysUser user = SecurityUtils.getLoginUser().getUser();
        DsEnterPort dsEnterPort = new DsEnterPort();
        // 判断是否有未审核船员
//        LambdaQueryWrapper<DsCrewEnterCheck> dsCrewEnterCheckLambdaQueryWrapper = new LambdaQueryWrapper<>();
//        dsCrewEnterCheckLambdaQueryWrapper.eq(DsCrewEnterCheck::getEnterPortId, setConfirmReq.getId())
//                .eq(DsCrewEnterCheck::getStatus, SysConstant.CHECK_STATUS_UNCHECKED);
//        if (dsCrewEnterCheckService.count(dsCrewEnterCheckLambdaQueryWrapper) > 0) {
//            throw new RuntimeException("存在未审核船员，需先审核才能确认！");
//        }

        // 将进港渔船报备信息绑定的船员信息进行审核
        dsCrewEnterCheckService.lambdaUpdate()
                .eq(DsCrewEnterCheck::getEnterPortId, setConfirmReq.getId())
                // 将没有进行审核的船员进行审核，单独审核的船员除外
                .eq(DsCrewEnterCheck::getStatus, SysConstant.CHECK_STATUS_UNCHECKED)
                // 根据渔船的整体审核状态进行更新渔船绑定船员的审核状态
                .set(DsCrewEnterCheck::getStatus, setConfirmReq.getStatus())
                .set(DsCrewEnterCheck::getUpdateBy, user.getNickName())
                .set(DsCrewEnterCheck::getUpdateAt, DateUtils.getNowDate())
                .update();

        // 根据申报的渔港id 查询泊区信息
        List<DsMainArea> dsMainAreaList = dsMainAreaService.lambdaQuery()
                .eq(DsMainArea::getPortId, setConfirmReq.getPortId())
                .list();

        BeanUtils.copyBeanProp(dsEnterPort, setConfirmReq);
        dsEnterPort.setId(setConfirmReq.getId())
                .setStatus(setConfirmReq.getStatus())
                .setUpdateAt(DateUtils.getNowDate())
                .setUpdateBy(user.getNickName())
                .setReviewTime(DateUtils.getNowDate())
                .setReviewUserId(SecurityUtils.getUserId().toString())
                .setReviewUserName(user.getNickName())
                .setReviewDesc(setConfirmReq.getReviewDesc())
                .setActualEnterTime(DateUtils.parseDate(setConfirmReq.getActualEnterTime()))
                .setBeHaveRegressPort(CollectionUtils.isEmpty(dsMainAreaList) ? 0 : 1)
        ;

        // 更新数据
        if (this.updateById(dsEnterPort)) {
            log.error("主线程开始调用异步方法: " + Thread.currentThread().getName());
            if (setConfirmReq.getStatus().equals(1)) {
                // 根据进港审核信息给登记人员发送通知
                this.doPushAppNotice(setConfirmReq.getId());
            }
            log.error("主线程继续执行: " + Thread.currentThread().getName());
            return true;
        }
        return false;
    }

    /**
     * 根据渔船进港登记人员将渔船进港审核通过的消息推送给APP
     *
     * @param dsEnterPortId: 渔船进港审核主键
     * @author Qi ChengBin
     * @date 2025/5/21
     */
    @Async("threadPoolTaskExecutor")
    protected void doPushAppNotice(String dsEnterPortId) {
        log.error(">>>>异步方法开始执行：" + Thread.currentThread().getName());
        DsEnterPort dbEnterPort = this.getById(dsEnterPortId);
        // 查询登记人员的 cid 进行发布通知
        SysUser registerUser = userService.selectUserById(Long.valueOf(dbEnterPort.getRegisterId()));
        String cid = registerUser.getCid();

        String title = dbEnterPort.getBoatName() + "归港指引通知";
        String content = "渔船已经通过进港审核，请按照指引归港";

        AjaxResult pushResult = publicUniPushUtils.pushMsg(cid, title, content);

        // 存储信息到归港指引通知表中去
        DsRegressPort dsRegressPort = new DsRegressPort()
                .setEnterPortId(dbEnterPort.getId())
                .setNoticeContent(content)
                .setNoticeTime(DateUtils.getNowDate())
                .setCreateBy("SYS")
                .setCreateAt(DateUtils.getNowDate())
                .setUpdateBy("SYS")
                .setUpdateAt(DateUtils.getNowDate())
                .setPortId(dbEnterPort.getPortId())
                .setPortName(dbEnterPort.getPortName())
                .setBoatId(dbEnterPort.getBoatId())
                .setBoatName(dbEnterPort.getBoatName())
                .setHolderName(dbEnterPort.getHolderName())
                .setHolderPhone(dbEnterPort.getHolderPhone())
                .setRegisterId(dbEnterPort.getRegisterId())
                .setRegisterName(dbEnterPort.getRegisterName())
                .setRegisterPhone(dbEnterPort.getRegisterPhone())
                .setAreaCode(dbEnterPort.getAreaCode())
                .setAreaName(dbEnterPort.getAreaName())
                // 靠港状态为待靠港
                .setRegressPortStatus(1);
        dsRegressPortService.save(dsRegressPort);
    }

    @Override
    public void export(HttpServletResponse response, GetEnterPortReq getEnterPortReq) {
        LambdaQueryWrapper<DsEnterPort> lqw = new LambdaQueryWrapper<>();
        lqw.ge(StringUtils.isNotBlank(getEnterPortReq.getStartTime()), DsEnterPort::getEnterTime, getEnterPortReq.getStartTime())
                .le(StringUtils.isNotBlank(getEnterPortReq.getEndTime()), DsEnterPort::getEnterTime, getEnterPortReq.getEndTime())
                .eq(StringUtils.isNotBlank(getEnterPortReq.getBoatId()), DsEnterPort::getBoatId, getEnterPortReq.getBoatId())
                .eq(StringUtils.isNotBlank(getEnterPortReq.getPortId()), DsEnterPort::getPortId, getEnterPortReq.getPortId())
                .eq(DsEnterPort::getIsLocal, getEnterPortReq.getIsLocal())
                .eq(DsEnterPort::getStatus, getEnterPortReq.getStatus())
                .like(StringUtils.isNotBlank(getEnterPortReq.getBoatName()), DsEnterPort::getBoatName, getEnterPortReq.getBoatName())
                .like(StringUtils.isNotBlank(getEnterPortReq.getPortName()), DsEnterPort::getPortName, getEnterPortReq.getPortName())
                .orderByDesc(DsEnterPort::getEnterTime);
        List<DsEnterPort> dsEnterPortList = this.list(lqw);
        List<DsEnterPortVO> dsEnterPortVOList = dsEnterPortList.stream().map(item -> {
            DsEnterPortVO dsEnterPortVO = new DsEnterPortVO();
            BeanUtils.copyBeanProp(dsEnterPortVO, item);
            if (ObjectUtils.isNotEmpty(item.getEnterTime())) {
                dsEnterPortVO.setEnterTime(DateUtils.dateTime(item.getEnterTime()));
            }
            if (ObjectUtils.isNotEmpty(item.getReviewTime())) {
                dsEnterPortVO.setReviewTime(DateUtils.dateTime(item.getReviewTime()));
            }
            dsEnterPortVO.setStatus(EnterOutPortStatus.getByCode(item.getStatus()));
            return dsEnterPortVO;
        }).collect(Collectors.toList());
        ExcelUtil<DsEnterPortVO> util = new ExcelUtil<DsEnterPortVO>(DsEnterPortVO.class);
        util.exportExcel(response, dsEnterPortVOList, "渔船进港信息档案");
    }
}
