package com.sikey.wa04.business.application.service.user;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.sikey.wa04.business.application.converter.MessageConverter;
import com.sikey.wa04.business.domain.adapter.*;
import com.sikey.wa04.business.domain.entity.Child;
import com.sikey.wa04.business.domain.entity.ChildId;
import com.sikey.wa04.business.domain.entity.MessageCenter;
import com.sikey.wa04.business.domain.entity.message.Message;
import com.sikey.wa04.business.domain.entity.message.MessageId;
import com.sikey.wa04.business.domain.usecase.ChildIdFactory;
import com.sikey.wa04.business.infrastructure.adapter.RabbitMQMessaging;
import com.sikey.wa04.business.infrastructure.adapter.RedisMessageIdIncrementingAdapter;
import com.sikey.wa04.business.infrastructure.dto.model.ContactModel;
import com.sikey.wa04.business.infrastructure.dto.model.DeviceModel;
import com.sikey.wa04.business.infrastructure.dto.request.contact.AddFriendRequest;
import com.sikey.wa04.business.infrastructure.dto.request.contact.GenerateFriendCodeRequest;
import com.sikey.wa04.business.infrastructure.dto.response.contact.AddFriendResponse;
import com.sikey.wa04.business.infrastructure.dto.response.contact.GenerateFriendCodeResponse;
import com.sikey.wa04.business.infrastructure.repository.ChildRepository;
import com.sikey.wa04.business.infrastructure.repository.ContactRepository;
import com.sikey.wa04.business.infrastructure.repository.DeviceRepository;
import com.sikey.wa04.common.dto.header.GenericManufacturerHeader;
import com.sikey.wa04.common.dto.message.AddFriendNoticeMessage;
import com.sikey.wa04.common.enums.*;
import jakarta.annotation.Resource;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

/**
 * 朋友
 *
 * @author luoyangwei
 */
@Slf4j
@Service
public class FriendService {
    private static final int FRIEND_CODE_LENGTH = 4;
    private static final char[] RANDOM_CHARS = new char[]{'1', '2', '3', '4', '5'};

    /**
     * 好友码缓存
     */
    private static final Cache<String, String> friendCodeCache = CacheBuilder.newBuilder()
            // 设置并发级别为 8，并发级别是指可以同时写缓存的线程数
            .concurrencyLevel(8)
            // 设置缓存容器的初始容量为 10000
            .initialCapacity(10000)
            // 设置缓存最大容量为 1000000，超过 1000000 之后就会按照LRU最近虽少使用算法来移除缓存项
            .maximumSize(1000000)
            // 是否需要统计缓存情况,该操作消耗一定的性能,生产环境应该去除
            .recordStats()
            // 设置写缓存后 n 分钟过期
            .expireAfterWrite(3, TimeUnit.MINUTES)
            .build();

    @Resource
    private ContactRepository contactRepository;

    @Resource
    private DeviceBindStateAdapter deviceBindStateAdapter;

    @Resource
    private ChildQueryAdapter childQueryAdapter;

    @Resource
    private RabbitMQMessaging rabbitMQMessaging;

    @Resource
    private RedisMessageIdIncrementingAdapter messageIdIncrementingAdapter;

    @Resource
    private MessageAdapter messageAdapter;

    @Resource
    private MessageCenterAdapter messageCenterAdapter;

    @Qualifier("myTaskExecutor")
    @Autowired
    private ThreadPoolTaskExecutor myTaskExecutor;

    @Resource
    private ChildRepository childRepository;

    @Resource
    private DeviceRepository deviceRepository;

    @Resource
    private UserAdapter userAdapter;

    /**
     * 随机生成好友码
     *
     * @return 好友码
     */
    private static String randomFriendCode() {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < FRIEND_CODE_LENGTH; i++) {
            int index = RandomUtil.randomInt(RANDOM_CHARS.length - 1);
            stringBuilder.append(RANDOM_CHARS[index]);
        }
        return stringBuilder.toString();
    }

    /**
     * 生成好友码
     *
     * @param request GenerateFriendCodeRequest
     * @param request genericManufacturerHeader
     * @return GenerateFriendCodeResponse
     * @throws Exception 异常
     */
    public GenerateFriendCodeResponse generateCode(GenerateFriendCodeRequest request, GenericManufacturerHeader genericManufacturerHeader) throws Exception {
        log.info("[生成好友码]请求参数：{}", request);
        Optional<String> friendCodeOptional = Optional.ofNullable(friendCodeCache.getIfPresent(request.ticket()));
        String friendCode = friendCodeOptional.orElse(randomFriendCode());
        friendCodeCache.put(request.ticket(), friendCode);

        myTaskExecutor.execute(() -> {
            Optional<DeviceModel> optional = deviceRepository.findByTicket(request.ticket());
            DeviceModel deviceModel = optional.orElseThrow();
            String softWareVersion = genericManufacturerHeader.getSoftWareVersion();
            String model = genericManufacturerHeader.getDeviceModel();
            // 更新设备版本号
            deviceRepository.updateVersionAndModelById(deviceModel.getId(), softWareVersion, model);
        });
        log.info("[生成好友码]返回值：{}", friendCode);
        return new GenerateFriendCodeResponse(friendCode);
    }

    /**
     * 添加好友
     *
     * @param request AddFriendRequest
     * @throws Exception 异常
     */
    @Transactional
    public AddFriendResponse addFriend(AddFriendRequest request) {
        String ticket = null;
        ChildId passiveChildId;
        Child passiveChild;
        Child activeChild;
        try {
            ConcurrentMap<String, String> cacheCodes = friendCodeCache.asMap();

            Set<String> keys = cacheCodes.keySet();
            for (String key : keys) {
                Optional<String> friendCodeOptional = Optional.ofNullable(cacheCodes.getOrDefault(key, null));
                if (friendCodeOptional.isEmpty()) {
                    continue;
                }

                String friendCode = friendCodeOptional.get();
                if (request.getCode().equals(friendCode)) {
                    ticket = key;
                }
            }

            // 判断好友码是否存在，如果 ticket 存在 则开始执行下面的逻辑
            if (ticket == null) {
                return new AddFriendResponse().setResultType(AddFriendResultType.FRIEND_CODE_NOT_EXIST.getValue());
            }

            // 获取 被动添加 小孩信息
            passiveChildId = deviceBindStateAdapter.getBinder(ticket);
            passiveChild = childQueryAdapter.findByChildId(passiveChildId);
            // 获取 主动添加 小孩信息
            activeChild = childQueryAdapter.findByChildId(ChildIdFactory.create(request.getId()));

            //判断是否添加自己为好友
            if (Objects.equals(activeChild.getId(), passiveChild.getId())) {
                return new AddFriendResponse().setResultType(AddFriendResultType.ADD_MYSELF_FRIEND.getValue());
            }

            //判断是否重复添加好友 查询主动添加的小孩的通讯录中是否有被动添加的小孩了
            ContactModel contactModel = contactRepository.findByChildIdAndRefId(request.getId(), passiveChildId.getId());
            if (Objects.nonNull(contactModel)) {
                return new AddFriendResponse().setResultType(AddFriendResultType.FRIEND_REPEAT.getValue());
            }

            // 向被加用户的好友列表中添加好友
            ContactModel passiveContactModel = new ContactModel();
            passiveContactModel.setChildId(passiveChildId.getId())
                    .setRefId(request.getId())
                    .setRefRole(ContactRole.FRIEND.getValue())
                    .setRefType(UserType.CHILD.getValue())
                    .setRemarkName(activeChild.getName())
                    .setRemarkAvatar(activeChild.getAvatar());
            contactRepository.save(passiveContactModel);

            // 向发起用户的好友列表中添加好友
            ContactModel activeContactModel = new ContactModel();
            activeContactModel.setChildId(activeChild.getId())
                    .setRefId(passiveChildId.getId())
                    .setRefRole(ContactRole.FRIEND.getValue())
                    .setRefType(UserType.CHILD.getValue())
                    .setRemarkName(passiveChild.getName())
                    .setRemarkAvatar(passiveChild.getAvatar());
            contactRepository.save(activeContactModel);

            friendCodeCache.invalidate(ticket);

            // 更新家长通讯录中的手表儿童昵称等信息
            contactRepository.updateContactByChildId(passiveChild.getName(), passiveChild.getAvatar(), Instant.now(), activeChild.getId(), passiveChildId.getId());

            log.info("添加好友成功, ticket: {}", ticket);
        } catch (Exception e) {
            log.error("AddFriend failed error: Id:{}; ", request.getId(), e);
            return new AddFriendResponse().setResultType(AddFriendResultType.FAILED.getValue());
        }

        // 消息中心
        String finalTicket = ticket;
        myTaskExecutor.execute(() -> {
            Child cm = passiveChild;
            Child um = activeChild;
            // 加入消息到消息中心
            MessageCenter messageCenter = new MessageCenter();
            messageCenter.setCid(cm.getId());
            messageCenter.setMessageType(MessageCenterType.MESSAGE_CENTER_TYPE_CONTACTS.getValue());
            messageCenter.setMessageParams(Map.of("name", cm.getName(), "friendName", um.getName()));
            messageCenterAdapter.save(messageCenter, MessageCenterType.MESSAGE_CENTER_TYPE_CONTACTS);

            // 加入消息到消息中心
            messageCenter = new MessageCenter();
            messageCenter.setCid(um.getId());
            messageCenter.setMessageType(MessageCenterType.MESSAGE_CENTER_TYPE_CONTACTS.getValue());
            messageCenter.setMessageParams(Map.of("name", um.getName(), "friendName", cm.getName()));
            messageCenterAdapter.save(messageCenter, MessageCenterType.MESSAGE_CENTER_TYPE_CONTACTS);

            Optional<DeviceModel> optional = deviceRepository.findByTicket(finalTicket);
            DeviceModel deviceModel = optional.orElseThrow();
            String softWareVersion = request.getHeaders().getSoftWareVersion();
            String model = request.getHeaders().getDeviceModel();
            // 更新设备版本号
            deviceRepository.updateVersionAndModelById(deviceModel.getId(), softWareVersion, model);
        });

        // 添加好友加过通知消息
        GenericManufacturerHeader genericManufacturerHeader = request.getHeaders();
        myTaskExecutor.execute(() -> {
            // 向被加用户和发起用户发送加好友成功的通知消息
            AddFriendNoticeMessage.AddFriendNoticeContent content = new AddFriendNoticeMessage.AddFriendNoticeContent()
                    .setCode(request.getCode())
                    .setSponsorId(activeChild.getId())
                    .setAddedId(passiveChildId.getId());

            Instant sendTime = Instant.now();
            Message passiveChildMessage = new Message()
                    .setId(new MessageId(messageIdIncrementingAdapter.incr()))
                    .setType(MessageType.ADD_FRIEND_NOTICE_MESSAGE)
                    .setContent(content)
                    .setGroup(false)
                    .setSenderId(activeChild.getId())
                    .setReceiverId(List.of(passiveChild.getId()))
                    .setSendTime(sendTime);
            messageAdapter.save(passiveChildMessage);
            publish(genericManufacturerHeader, passiveChildMessage);

            Message activeChildMessage = new Message()
                    .setId(new MessageId(messageIdIncrementingAdapter.incr()))
                    .setType(MessageType.ADD_FRIEND_NOTICE_MESSAGE)
                    .setContent(content)
                    .setGroup(false)
                    .setSenderId(passiveChild.getId())
                    .setReceiverId(List.of(activeChild.getId()))
                    .setSendTime(sendTime);
            messageAdapter.save(activeChildMessage);
            publish(genericManufacturerHeader, activeChildMessage);
        });

        return new AddFriendResponse().setResultType(AddFriendResultType.SUCC.getValue());
    }

    private void publish(GenericManufacturerHeader headers, Message message) {
        log.info("Send message {}", message);
        Object messagingObject = MessageConverter.toMessaging(message);
        rabbitMQMessaging.publish(headers, messagingObject);
    }

}
