package com.wecloud.im.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chanjx.utils.StringUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.wecloud.dispatch.extend.ActionRequest;
import com.wecloud.dispatch.util.ActionRequestHolder;
import com.wecloud.im.entity.ImApplication;
import com.wecloud.im.entity.ImClient;
import com.wecloud.im.entity.ImClientDevice;
import com.wecloud.im.friend.entity.ImFriend;
import com.wecloud.im.friend.service.ImFriendService;
import com.wecloud.im.mapper.ImClientMapper;
import com.wecloud.im.param.ClientInfoParam;
import com.wecloud.im.param.ClientRelationVo;
import com.wecloud.im.param.GetClientInfoParam;
import com.wecloud.im.param.GetClientRelationParam;
import com.wecloud.im.param.ImClientPageParam;
import com.wecloud.im.param.ImClientQueryVo;
import com.wecloud.im.param.ImClientSimpleDto;
import com.wecloud.im.param.LogoutParam;
import com.wecloud.im.param.RegisterClientParam;
import com.wecloud.im.param.add.ClientDeviceUpdateParam;
import com.wecloud.im.param.add.ImClientHeadPortraitAdd;
import com.wecloud.im.param.add.ImClientHeadPortraitAndNicknameUpdate;
import com.wecloud.im.param.add.ImClientNicknameAdd;
import com.wecloud.im.param.add.ImClientNicknameUpdate;
import com.wecloud.im.sdk.enums.FriendStateEnum;
import com.wecloud.im.sdk.enums.RelationEnum;
import com.wecloud.im.service.ContextService;
import com.wecloud.im.service.ImApplicationService;
import com.wecloud.im.service.ImClientBlacklistService;
import com.wecloud.im.service.ImClientDeviceService;
import com.wecloud.im.service.ImClientService;
import com.wecloud.im.vo.ClientInfoVo;
import com.wecloud.im.vo.GetInfoListVo;
import com.wecloud.im.vo.MyInfoVo;
import com.wecloud.im.ws.utils.RedisUtils;
import com.wecloud.utils.JsonUtils;
import com.wecloud.utils.SnowflakeUtil;
import io.geekidea.springbootplus.framework.common.exception.BusinessException;
import io.geekidea.springbootplus.framework.common.service.impl.BaseServiceImpl;
import io.geekidea.springbootplus.framework.core.pagination.PageInfo;
import io.geekidea.springbootplus.framework.core.pagination.Paging;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.util.JwtUtil;
import io.geekidea.springbootplus.framework.shiro.util.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 终端表 服务实现类
 *
 * @author wei
 * @since 2021-04-27
 */
@Slf4j
@Service
public class ImClientServiceImpl extends BaseServiceImpl<ImClientMapper, ImClient> implements ImClientService {

    @Autowired
    private ImClientMapper imClientMapper;

    @Autowired
    private ImApplicationService imApplicationService;

    @Autowired
    private ImFriendService imFriendService;

    @Autowired
    private ImClientBlacklistService imClientBlacklistService;

    @Autowired
    private ImClientDeviceService imClientDeviceService;

    @Autowired
    private ContextService contextService;

    @Autowired
    private RedisUtils redisUtils;

    @Override
    public MyInfoVo getMyInfo() {
        ImClient currentClient = contextService.getImClientIfNotNullOrThrow();
        MyInfoVo myInfoVo = new MyInfoVo();
        BeanUtils.copyProperties(currentClient, myInfoVo);
        return myInfoVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateHeadPortrait(ImClientHeadPortraitAdd imClientHeadPortraitAdd) {
        ImClient curentClient = contextService.getImClientIfNotNullOrThrow();
        curentClient.setHeadPortrait(imClientHeadPortraitAdd.getHeadPortrait());
        imClientMapper.updateById(curentClient);

        // 清除client的redis缓存
        deleteCacheImClient(curentClient);

        return true;
    }

    @Override
    public boolean updateNickname(ImClientNicknameUpdate imClientNicknameUpdate) {
        ImClient curentClient = contextService.getImClientIfNotNullOrThrow();
        curentClient.setNickname(imClientNicknameUpdate.getNickname());
        imClientMapper.updateById(curentClient);

        // 清除client的redis缓存
        deleteCacheImClient(curentClient);

        return true;
    }

    @Override
    public boolean updateHeadAndNickname(ImClientHeadPortraitAndNicknameUpdate imClientHeadPortraitAndNicknameUpdate) {
        String clientId = imClientHeadPortraitAndNicknameUpdate.getClientId();
        ImClient curentClient = getOne(Wrappers.<ImClient>lambdaQuery().eq(ImClient::getClientId, clientId));
        curentClient.setHeadPortrait(imClientHeadPortraitAndNicknameUpdate.getHeadPortrait());
        curentClient.setNickname(imClientHeadPortraitAndNicknameUpdate.getNickname());
        imClientMapper.updateById(curentClient);

        // 清除client的redis缓存
        deleteCacheImClient(curentClient);

        return true;
    }

    @Override
    public List<GetInfoListVo> getInfoList(GetClientInfoParam getClientInfoParam) throws Exception {
        ImClient curentClient = contextService.getImClientIfNotNullOrThrow();

        if (getClientInfoParam.getClientIds() == null || getClientInfoParam.getClientIds().isEmpty()) {
            throw new BusinessException("getClientInfoParam.getClientIds() == null");
        }

//        List<ImClient> imClients = this.list(new QueryWrapper<ImClient>().lambda()
//                .eq(ImClient::getFkAppid, curentClient.getFkAppid())
//                .in(ImClient::getClientId, getClientInfoParam.getClientId())
//        );
//
//        List<GetInfoListVo> getInfoListVos = new ArrayList<>();
//
//        for (ImClient imClient : imClients) {
//
//            GetInfoListVo getInfoListVo = new GetInfoListVo();
//            getInfoListVo.setHeadPortrait(imClient.getHeadPortrait());
//            getInfoListVo.setNickname(imClient.getNickname());
//            getInfoListVo.setClientId(imClient.getClientId());
//            getInfoListVos.add(getInfoListVo);
//        }

        List<GetInfoListVo> infoList = imClientMapper.getInfoList(curentClient.getFkAppid(), getClientInfoParam.getConversationId(), getClientInfoParam.getClientIds());

        return infoList;
    }

    @Override
    public ClientInfoVo getClientInfo(ClientInfoParam param) {
        ImClient currentClient = contextService.getImClientIfNotNullOrThrow();
        if (currentClient == null) {
            throw new BusinessException("当前用户登录信息失效");
        }
        ClientInfoVo clientInfoVo = new ClientInfoVo();
        if (param.getConversationId() != null) {
            List<GetInfoListVo> infoList = imClientMapper.getInfoList(currentClient.getFkAppid(), param.getConversationId(),
                    Lists.newArrayList(param.getClientId()));
            if (CollectionUtils.isEmpty(infoList)) {
                throw new BusinessException("群内未查询到该用户信息");
            }
            BeanUtils.copyProperties(infoList.get(0), clientInfoVo);
        } else {
            ImClient imClient = this.getCacheImClient(currentClient.getFkAppid(), param.getClientId());
            BeanUtils.copyProperties(imClient, clientInfoVo);
        }
        clientInfoVo.setRelation(RelationEnum.STRANGER.getCode());
        // 查询该好友与自己关系： 陌生人、好友、被拉黑名单
        ImFriend imFriend = imFriendService.getByKey(currentClient.getClientId(), clientInfoVo.getClientId());
        if (imFriend != null && FriendStateEnum.CONFORM.getCode().equals(imFriend.getState())) {
            clientInfoVo.setRelation(RelationEnum.FRIEND.getCode());
        }
        // 查询是否被拉黑
        if (imClientBlacklistService.isBeBlack(currentClient.getClientId(), clientInfoVo.getClientId())) {
            clientInfoVo.setRelation(RelationEnum.BE_BLACK.getCode());
        }
        return clientInfoVo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateNickname(ImClientNicknameAdd imClientNicknameAdd) throws Exception {
        ImClient curentClient = contextService.getImClientIfNotNullOrThrow();

        curentClient.setNickname(imClientNicknameAdd.getNickname());
        imClientMapper.updateById(curentClient);

        // 清除client的redis缓存
        deleteCacheImClient(curentClient);

        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveImClient(ImClient imClient) throws Exception {
        return super.save(imClient);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateDeviceInfo(ClientDeviceUpdateParam param) {
        ImClient currentClient = getCurrentClient();

        ImClientDevice clientDevice = imClientDeviceService.getOne(new QueryWrapper<ImClientDevice>().lambda()
                .eq(ImClientDevice::getFkAppid, currentClient.getFkAppid())
                .eq(ImClientDevice::getDeviceToken, param.getDeviceToken()));
        if (clientDevice != null) {
            // client登陆的时候 判断数据库内是否已经存在这个设备token,如果存在就清空旧的
            JwtToken currentJwtToken = JwtUtil.getCurrentJwtToken();
            imClientDeviceService.removeOldToken(currentClient.getId(), currentJwtToken.getToken());
        }
        ImClientDevice clientDeviceToUpdate = imClientDeviceService.getOne(new QueryWrapper<ImClientDevice>().lambda()
                .eq(ImClientDevice::getFkAppid, currentClient.getFkAppid())
                .eq(ImClientDevice::getFkClientId, currentClient.getId())
                .eq(ImClientDevice::getDeviceType, param.getDeviceType())
        );
        if (clientDeviceToUpdate == null) {
            ImClientDevice imClientDevice = new ImClientDevice();
            imClientDevice.setId(SnowflakeUtil.getId());
            imClientDevice.setFkAppid(currentClient.getFkAppid());
            imClientDevice.setFkClientId(currentClient.getId());
            imClientDevice.setValid(1);
            imClientDevice.setDeviceType(param.getDeviceType());
            imClientDevice.setCreateTime(new Date());
            imClientDevice.setUpdateTime(new Date());
            imClientDeviceService.save(imClientDevice);
        } else {
            clientDeviceToUpdate.setDeviceToken(param.getDeviceToken());
            imClientDeviceService.updateById(clientDeviceToUpdate);
        }
        return true;
    }

    @Override
    public boolean logout(LogoutParam param) {
        ImClient currentClient = contextService.getImClientIfNotNullOrThrow();
        // 清除设备token
        return imClientDeviceService.update(new UpdateWrapper<ImClientDevice>().lambda()
                .eq(ImClientDevice::getFkAppid, currentClient.getFkAppid())
                .eq(ImClientDevice::getFkClientId, currentClient.getId())
                .eq(ImClientDevice::getDeviceType, param.getDeviceType())
                .set(ImClientDevice::getDeviceToken, null));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateImClient(ImClient imClient) {
        return super.updateById(imClient);
    }

    @Override
    public boolean deleteImClient(Long id) {
        return super.removeById(id);
    }

    @Override
    public Paging<ImClientQueryVo> getImClientPageList(ImClientPageParam imClientPageParam) throws Exception {
        Page<ImClientQueryVo> page = new PageInfo<>(imClientPageParam, OrderItem.desc(getLambdaColumn(ImClient::getCreateTime)));
        IPage<ImClientQueryVo> iPage = imClientMapper.getImClientPageList(page, imClientPageParam);
        return new Paging<>(iPage);
    }

    @Override
    public ImClient getCurrentClient() {
        ImClient imClient = null;
        // modify by  luozh 2022/04/03 增加是否存在ActionRequest的判断，如果是则说明请求来源于ws 否则是HTTP请求
        if (ActionRequestHolder.getActionRequest() != null) {
            ActionRequest request = ActionRequestHolder.getActionRequest();
            imClient = getCacheImClient(request.getSenderClientId());
        } else {
            // shiro线程中获取当前token
            JwtToken curentJwtToken = JwtUtil.getCurrentJwtToken();
            //  根据appKey查询appid
            ImApplication imApplication = imApplicationService.getCacheAppByAppKey(curentJwtToken.getAppKey());
            imClient = getCacheImClient(imApplication.getId(), curentJwtToken.getClientId());
        }
        return imClient;
    }

    @Override
    public ImClient getCacheImClient(Long applicationId, String clientId) {
        String key = "app_client_" + applicationId + clientId;
        String clientStr = redisUtils.getKey(key);
        if (StringUtils.isNotBlank(clientStr)) {
            return JsonUtils.decodeJson(clientStr, ImClient.class);
        }
        ImClient client = this.getOne(new QueryWrapper<ImClient>().lambda()
                .eq(ImClient::getFkAppid, applicationId)
                .eq(ImClient::getClientId, clientId));
        if (client == null) {
            return null;
        }
        redisUtils.addKey(key, JsonUtils.encodeJson(client), Duration.ofHours(1));
        return client;
    }

    @Override
    public void deleteCacheImClient(ImClient client) {
        String key = "app_client_" + client.getFkAppid() + client.getClientId();
        redisUtils.delKey(key);
        redisUtils.delKey("client_" + client.getId());
    }

    @Override
    public ImClient getCacheImClient(Long id) {
        String key = "client_" + id;
        String clientStr = redisUtils.getKey(key);
        if (StringUtils.isNotBlank(clientStr)) {
            return JsonUtils.decodeJson(clientStr, ImClient.class);
        }
        ImClient client = imClientMapper.selectById(id);
        if (client == null) {
            return null;
        }
        redisUtils.addKey(key, JsonUtils.encodeJson(client), Duration.ofHours(1));
        return client;
    }

    @Override
    public List<ImClientSimpleDto> getSimpleClients(Long applicationId, List<String> clientIds) {
        return imClientMapper.getSimpleClients(applicationId, clientIds);
    }

    @Override
    public List<ImClientSimpleDto> getSimpleClients(List<Long> ids) {
        return imClientMapper.getSimpleClientsByIds(ids);
    }

    @Override
    public Long registerClient(RegisterClientParam param) {

        Long appId = SecurityUtils.getCurrentAppId();

        // 判断client是否存在
        ImClient imClient = getOne(new QueryWrapper<ImClient>().lambda()
                .eq(ImClient::getFkAppid, appId)
                .eq(ImClient::getClientId, param.getUserId()));
        if (imClient == null) {
            imClient = new ImClient();
            imClient.setId(SnowflakeUtil.getId());
            imClient.setFkAppid(appId);
            imClient.setClientId(param.getUserId().toString());
            imClient.setHeadPortrait(param.getHeadPortrait());
            imClient.setNickname(param.getNickname());
            save(imClient);
        }
        return imClient.getId();
    }

    @Override
    public ClientRelationVo getClientRelation(GetClientRelationParam param) {
        ClientRelationVo relationVo = new ClientRelationVo();
        relationVo.setClientId(param.getClientId());
        relationVo.setFriendClientId(param.getFriendClientId());
        // 查询该好友与自己关系： 陌生人、好友、被拉黑名单
        ImFriend imFriend = imFriendService.getByKey(param.getClientId(), param.getFriendClientId());
        if (imFriend != null) {
            relationVo.setFriendName(imFriend.getFriendName());
        }
        if (imFriend != null && FriendStateEnum.CONFORM.getCode().equals(imFriend.getState())) {
            relationVo.setFriend(true);
        }
        // 查询是否被拉黑
        if (imClientBlacklistService.isBeBlack(param.getClientId(), param.getFriendClientId())) {
            relationVo.setBlacklist(true);
        }

        return relationVo;
    }

    @Override
    public Map<String, String> getUserAttributes(String userId) {
        Long appId = SecurityUtils.getCurrentAppId();
        ImClient targetClient =
                getOne(Wrappers.<ImClient>lambdaQuery().eq(ImClient::getClientId, userId).eq(ImClient::getFkAppid,
                        appId));
        if (targetClient == null) {
            throw new BusinessException("用户不存在");
        }
        TypeReference<Map<String, String>> typeReference = new TypeReference<Map<String, String>>() {
        };
        return JSONObject.parseObject(targetClient.getAttributes(), typeReference);
    }

    @Override
    public Boolean modifyUserAttributes(String userId, Map<String, Object> attributes) {
        Long appId = SecurityUtils.getCurrentAppId();
        ImClient targetClient =
                getOne(Wrappers.<ImClient>lambdaQuery().eq(ImClient::getClientId, userId).eq(ImClient::getFkAppid,
                        appId));

        if (targetClient == null) {
            throw new BusinessException("用户不存在");
        }

        TypeReference<Map<String, Object>> typeReference = new TypeReference<Map<String, Object>>() {
        };
        Map<String, Object> clientAttribute = JSONObject.parseObject(targetClient.getAttributes(), typeReference);
        if (clientAttribute == null) {
            clientAttribute = Maps.newHashMap();
        }

        if (attributes == null || attributes.isEmpty()) {
            clientAttribute = Maps.newHashMap();
        } else {
            clientAttribute.putAll(attributes);
        }

        targetClient.setAttributes(JSONObject.toJSONString(clientAttribute));
        // 清除client的redis缓存
        redisUtils.delKey("cache" + targetClient.getId());
        redisUtils.delKey("cache" + targetClient.getFkAppid() + targetClient.getClientId());
        this.deleteCacheImClient(targetClient);
        return this.updateById(targetClient);
    }
}
