package org.dromara.main.service.impl;

import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.service.OssService;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.main.domain.CsSeatConfig;
import org.dromara.main.domain.CsSeatWaiter;
import org.dromara.main.domain.CsSession;
import org.dromara.main.domain.MainCompanyApply;
import org.dromara.main.domain.bo.CsSessionBo;
import org.dromara.main.domain.vo.CsSessionVo;
import org.dromara.main.mapper.CsSeatConfigMapper;
import org.dromara.main.mapper.CsSeatWaiterMapper;
import org.dromara.main.mapper.CsSessionMapper;
import org.dromara.main.mapper.MainCompanyApplyMapper;
import org.dromara.main.service.ICsSessionService;
import org.dromara.system.domain.SysUser;
import org.dromara.system.mapper.SysUserMapper;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class CsSessionServiceImpl implements ICsSessionService {

    private final CsSessionMapper baseMapper;
    private final CsSeatConfigMapper seatConfigMapper;
    private final CsSeatWaiterMapper seatWaiterMapper;
    private final MainCompanyApplyMapper mainCompanyApplyMapper;
    private final OssService ossService;
    private final SysUserMapper sysUserMapper;
    private final Converter converter;

    @Override
    public CsSessionVo createOrGetSession(Integer sessionType, Long fromUserId,
                                          String fromUserName, String fromUserAvatar) {
        LambdaQueryWrapper<CsSession> lqw = Wrappers.lambdaQuery();
        lqw.eq(CsSession::getSessionType, sessionType)
            .eq(CsSession::getFromUserId, fromUserId)
            .eq(CsSession::getStatus, 1)
            .orderByDesc(CsSession::getCreateTime)
            .last("LIMIT 1");

        CsSession existSession = baseMapper.selectOne(lqw);
        if (existSession != null) {
            return enrichSessionVo(converter.convert(existSession, CsSessionVo.class));
        }

        CsSession newSession = new CsSession();
        newSession.setSessionNo(generateSessionNo(sessionType, fromUserId));
        newSession.setSessionType(sessionType);
        newSession.setFromUserId(fromUserId);
        newSession.setFromUserName(fromUserName);
        newSession.setFromUserAvatar(fromUserAvatar);
        newSession.setStatus(1);
        newSession.setUnreadCount(0);
        newSession.setCreateTime(LocalDateTime.now());
        assignSeatAndWaiter(newSession);

        baseMapper.insert(newSession);
        return enrichSessionVo(converter.convert(newSession, CsSessionVo.class));
    }

    @Override
    public TableDataInfo<CsSessionVo> queryPageList(CsSessionBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<CsSession> lqw = buildQueryWrapper(bo);
        Page<CsSession> page = baseMapper.selectPage(pageQuery.build(), lqw);
        Page<CsSessionVo> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        voPage.setRecords(enrichSessionVos(converter.convert(page.getRecords(), CsSessionVo.class)));
        return TableDataInfo.build(voPage);
    }

    private LambdaQueryWrapper<CsSession> buildQueryWrapper(CsSessionBo bo) {
        LambdaQueryWrapper<CsSession> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getSessionType() != null, CsSession::getSessionType, bo.getSessionType());
        lqw.eq(bo.getStatus() != null, CsSession::getStatus, bo.getStatus());
        lqw.eq(bo.getWaiterId() != null, CsSession::getWaiterId, bo.getWaiterId());
        lqw.like(StringUtils.isNotBlank(bo.getKeyword()), CsSession::getFromUserName, bo.getKeyword());
        lqw.orderByDesc(CsSession::getLastMsgTime, CsSession::getCreateTime);
        return lqw;
    }

    @Override
    public CsSessionVo queryById(Long sessionId) {
        CsSession entity = baseMapper.selectById(sessionId);
        return enrichSessionVo(converter.convert(entity, CsSessionVo.class));
    }

    @Override
    public Boolean endSession(Long sessionId) {
        CsSession update = new CsSession();
        update.setId(sessionId);
        update.setStatus(2);
        update.setEndTime(LocalDateTime.now());
        return baseMapper.updateById(update) > 0;
    }

    @Override
    public Boolean updateLastMessage(Long sessionId, String lastMsg) {
        CsSession update = new CsSession();
        update.setId(sessionId);
        update.setLastMsg(lastMsg);
        update.setLastMsgTime(LocalDateTime.now());
        return baseMapper.updateById(update) > 0;
    }

    @Override
    public Boolean incrementUnreadCount(Long sessionId) {
        return baseMapper.update(null,
            Wrappers.lambdaUpdate(CsSession.class)
                .setSql("unread_count = unread_count + 1")
                .eq(CsSession::getId, sessionId)) > 0;
    }

    @Override
    public Boolean clearUnreadCount(Long sessionId) {
        CsSession update = new CsSession();
        update.setId(sessionId);
        update.setUnreadCount(0);
        return baseMapper.updateById(update) > 0;
    }

    private String generateSessionNo(Integer sessionType, Long fromUserId) {
        return String.format("CS%s%s", sessionType, IdUtil.getSnowflakeNextIdStr());
    }

    private void assignSeatAndWaiter(CsSession session) {
        String module = session.getSessionType() != null && session.getSessionType() == 2 ? "merchant" : "mini";
        List<String> moduleCandidates = "merchant".equals(module)
            ? Arrays.asList("merchant", "all", "官网", "商家")
            : Arrays.asList("mini", "all", "小程序");

        List<CsSeatConfig> seats = seatConfigMapper.selectList(
            Wrappers.lambdaQuery(CsSeatConfig.class)
                .eq(CsSeatConfig::getStatus, 1)
                .in(CsSeatConfig::getModule, moduleCandidates)
                .orderByAsc(CsSeatConfig::getId)
        );
        if (seats.isEmpty()) {
            return;
        }

        CsSeatConfig seat = seats.get(0);
        session.setSeatId(seat.getId());

        List<CsSeatWaiter> waiters = seatWaiterMapper.selectList(
            Wrappers.lambdaQuery(CsSeatWaiter.class)
                .eq(CsSeatWaiter::getSeatId, seat.getId())
                .orderByAsc(CsSeatWaiter::getId)
        );
        if (!waiters.isEmpty()) {
            session.setWaiterId(waiters.get(0).getUserId());
        }
    }

    private List<CsSessionVo> enrichSessionVos(List<CsSessionVo> sessionVos) {
        if (sessionVos == null || sessionVos.isEmpty()) {
            return sessionVos;
        }
        Map<Long, SysUser> waiterMap = loadWaiterMap(sessionVos);
        Map<Long, CsSeatConfig> seatMap = loadSeatMap(sessionVos);
        sessionVos.forEach(vo -> enrichSessionVo(vo, waiterMap, seatMap));
        return sessionVos;
    }

    private CsSessionVo enrichSessionVo(CsSessionVo vo) {
        if (vo == null) {
            return null;
        }
        Map<Long, SysUser> waiterMap = loadWaiterMap(Collections.singletonList(vo));
        Map<Long, CsSeatConfig> seatMap = loadSeatMap(Collections.singletonList(vo));
        enrichSessionVo(vo, waiterMap, seatMap);
        return vo;
    }

    private void enrichSessionVo(CsSessionVo vo, Map<Long, SysUser> waiterMap, Map<Long, CsSeatConfig> seatMap) {
        if (StringUtils.isBlank(vo.getFromUserAvatar())) {
            vo.setFromUserAvatar(resolveFromUserAvatar(vo));
        }
        if (vo.getSeatId() != null && seatMap.containsKey(vo.getSeatId())) {
            vo.setSeatName(seatMap.get(vo.getSeatId()).getSeatName());
            vo.setWaiterAvatar(seatMap.get(vo.getSeatId()).getAvatar());
        }
        if (vo.getWaiterId() != null && waiterMap.containsKey(vo.getWaiterId())) {
            SysUser waiter = waiterMap.get(vo.getWaiterId());
            vo.setWaiterName(StringUtils.defaultIfBlank(waiter.getNickName(), waiter.getUserName()));
            vo.setIsOnline("0".equals(waiter.getStatus()));
        }
    }

    private Map<Long, SysUser> loadWaiterMap(List<CsSessionVo> sessionVos) {
        List<Long> waiterIds = sessionVos.stream()
            .map(CsSessionVo::getWaiterId)
            .filter(Objects::nonNull)
            .distinct()
            .collect(Collectors.toList());
        if (waiterIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return sysUserMapper.selectBatchIds(waiterIds).stream()
            .collect(Collectors.toMap(SysUser::getUserId, Function.identity()));
    }

    private Map<Long, CsSeatConfig> loadSeatMap(List<CsSessionVo> sessionVos) {
        List<Long> seatIds = sessionVos.stream()
            .map(CsSessionVo::getSeatId)
            .filter(Objects::nonNull)
            .distinct()
            .collect(Collectors.toList());
        if (seatIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return seatConfigMapper.selectBatchIds(seatIds).stream()
            .collect(Collectors.toMap(CsSeatConfig::getId, Function.identity()));
    }

    private String resolveFromUserAvatar(CsSessionVo vo) {
        if (vo == null || vo.getSessionType() == null || vo.getSessionType() != 2) {
            return null;
        }
        MainCompanyApply companyApply = mainCompanyApplyMapper.selectOne(
            Wrappers.lambdaQuery(MainCompanyApply.class)
                .and(wrapper -> wrapper
                    .eq(MainCompanyApply::getTenantId, String.valueOf(vo.getFromUserId()))
                    .or()
                    .eq(MainCompanyApply::getId, vo.getFromUserId())
                    .or()
                    .eq(StringUtils.isNotBlank(vo.getFromUserName()), MainCompanyApply::getCompanyName, vo.getFromUserName()))
                .orderByDesc(MainCompanyApply::getCreateTime)
                .last("LIMIT 1")
        );
        if (companyApply == null || companyApply.getAvatar() == null) {
            return null;
        }
        return ossService.selectUrlByIds(companyApply.getAvatar().toString());
    }
}
