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

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.sikey.wa04.business.application.converter.MessageConverter;
import com.sikey.wa04.business.application.service.BaseService;
import com.sikey.wa04.business.application.service.message.PowerOnOrShutdownMessageService;
import com.sikey.wa04.business.domain.adapter.DeviceBindStateAdapter;
import com.sikey.wa04.business.domain.adapter.DeviceConfigAdapter;
import com.sikey.wa04.business.domain.adapter.MessageAdapter;
import com.sikey.wa04.business.domain.adapter.MessageCenterAdapter;
import com.sikey.wa04.business.domain.entity.ChildId;
import com.sikey.wa04.business.domain.entity.DeviceId;
import com.sikey.wa04.business.domain.entity.MessageCenter;
import com.sikey.wa04.business.domain.entity.message.Message;
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.ChildModel;
import com.sikey.wa04.business.infrastructure.dto.model.ChildRefModel;
import com.sikey.wa04.business.infrastructure.dto.model.DeviceAppManageModel;
import com.sikey.wa04.business.infrastructure.dto.model.DeviceModel;
import com.sikey.wa04.business.infrastructure.dto.request.device.*;
import com.sikey.wa04.business.infrastructure.dto.request.messaging.WristwatchStartupRequest;
import com.sikey.wa04.business.infrastructure.dto.response.device.DeviceBindResponse;
import com.sikey.wa04.business.infrastructure.dto.response.device.DeviceWristwatchBindingResponse;
import com.sikey.wa04.business.infrastructure.dto.response.device.ExchangeDeviceResponse;
import com.sikey.wa04.business.infrastructure.dto.response.device.GetAppManageResponse;
import com.sikey.wa04.business.infrastructure.repository.*;
import com.sikey.wa04.common.dto.header.GenericManufacturerHeader;
import com.sikey.wa04.common.dto.message.AppManageAppSwitchNoticeMessage;
import com.sikey.wa04.common.dto.message.DeviceBindStateChangeNoticeMessage;
import com.sikey.wa04.common.enums.IdentityEnum;
import com.sikey.wa04.common.enums.MessageCenterType;
import com.sikey.wa04.common.enums.MessageType;
import com.sikey.wa04.common.exception.BusinessException;
import com.sikey.wa04.common.exception.errors.Errors;
import com.sikey.wa04.common.interceptor.CurrentRequestUser;
import com.sikey.wa04.common.utils.AccessTokenUtil;
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.data.redis.core.RedisOperations;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.Instant;
import java.util.*;

@Slf4j
@Service
public class DeviceService extends BaseService {

    private static final String LOGIN_TOKEN = "login:token:%s";
    private static final String LOGIN_USER_ID = "login:user:id:%s";
    @Resource
    private RedisMessageIdIncrementingAdapter messageIdIncrementingAdapter;
    @Resource
    private DeviceBindStateAdapter deviceBindStateAdapter;
    @Resource
    private DeviceRepository deviceRepository;
    @Resource
    private RabbitMQMessaging rabbitMQMessaging;
    @Resource
    private MessageAdapter messageAdapter;
    @Resource
    private DeviceBindRepository deviceBindRepository;
    @Resource
    private DeviceConfigAdapter deviceConfigAdapter;
    @Qualifier("myTaskExecutor")
    @Autowired
    private ThreadPoolTaskExecutor myTaskExecutor;
    @Resource
    private MessageCenterAdapter messageCenterAdapter;
    @Resource
    private PowerOnOrShutdownMessageService powerOnOrShutdownMessageService;
    @Resource
    private ChildRepository childRepository;
    @Resource
    private ChildRefRepository childRefRepository;
    @Resource
    private DeviceAppManageRepository deviceAppManageRepository;
    @Resource
    private RedisMessageIdIncrementingAdapter redisMessageIdIncrementingAdapter;
    @Resource
    private RedisOperations<String, String> operations;

    /**
     * 绑定设备
     *
     * @param request 绑定设备请求
     * @throws Exception 绑定设备异常
     */
    public DeviceBindResponse bind(BindDeviceRequest request) throws Exception {
        if (deviceBindStateAdapter.isBinding(request.getTicket())) {
            throw Errors.BEEN_BOUND.getException();
        }

        Optional<DeviceModel> optional = deviceRepository.findByTicket(request.getTicket());
        DeviceModel deviceModel = optional.orElseThrow();

        String userId = CurrentRequestUser.get().getId();

        deviceBindStateAdapter.bind(new DeviceId(deviceModel.getId()), ChildIdFactory.create(request.getCid()));
        log.info("The device has been successfully bound. childId: {}, deviceId: {}", request.getCid(), deviceModel.getId());

        myTaskExecutor.execute(() -> {
            // Push binding notification message to the device
            // at this time, the device is still using Ticket online, so the recipient here uses Ticket
            DeviceBindStateChangeNoticeMessage noticeMessage = new DeviceBindStateChangeNoticeMessage();
            noticeMessage.setMsgType(MessageType.DEVICE_BIND_STATE_CHANGE_MESSAGE)
                    .setMsgId(messageIdIncrementingAdapter.incr())
                    .setSendTime(Instant.now().toEpochMilli())
                    .setSendId(userId)
                    .setRecvId(request.getCid());
            DeviceBindStateChangeNoticeMessage.DeviceBindStateChangeNoticeContent content =
                    new DeviceBindStateChangeNoticeMessage.DeviceBindStateChangeNoticeContent()
                            .setIsBind(true);
            noticeMessage.setContent(content);

            Message message = MessageConverter.formMessaging(noticeMessage);
            messageAdapter.save(message);

            log.info("Send message to receiver: {}", noticeMessage.getRecvId());
            Object messagingObject = MessageConverter.toMessaging(message);
            rabbitMQMessaging.publish(request.getHeader(), messagingObject);
        });

        return new DeviceBindResponse().setModel(deviceModel.getModel());
    }

    /**
     * 解绑设备
     *
     * @param request                   解绑设备请求
     * @param genericManufacturerHeader header
     * @return
     */
    @Transactional
    public void unbind(UnbindDeviceRequest request, GenericManufacturerHeader genericManufacturerHeader) throws Exception {
        String cId = request.cid();

        DeviceModel deviceModel = deviceRepository.findByTicket(request.ticket())
                .orElseThrow(() -> new RuntimeException("The device does not exist."));
        deviceBindStateAdapter.unbind(new DeviceId(deviceModel.getId()));
        log.info("The device has been successfully unbound. childId: {}, deviceId: {}", cId, deviceModel.getId());

        String userId = CurrentRequestUser.get().getId();
        // 查询角色是否管理员
        ChildRefModel childRefModel = childRefRepository.findByUserIdAndChildId(userId, cId);
        if (Objects.isNull(childRefModel)) {
            throw Errors.CHILD_NOT_EXIST.getException();
        }

        Integer identity = childRefModel.getIdentity();
        if (Objects.isNull(identity)) {
            throw new BusinessException(HttpStatus.HTTP_INTERNAL_ERROR, "身份角色是空");
        }

        if (identity.intValue() != IdentityEnum.IDENTITY_ADMIN.getValue() && identity.intValue() != IdentityEnum.IDENTITY_SUPER_ADMIN.getValue()) {
            return;
        }


        myTaskExecutor.execute(() -> {
            // Push unbind notification message to the device
            // at this time, the device is still using Ticket online, so the recipient here uses Ticket
            DeviceBindStateChangeNoticeMessage noticeMessage = new DeviceBindStateChangeNoticeMessage();
            noticeMessage.setMsgType(MessageType.DEVICE_BIND_STATE_CHANGE_MESSAGE)
                    .setMsgId(messageIdIncrementingAdapter.incr())
                    .setSendTime(Instant.now().toEpochMilli())
                    .setSendId(userId)
                    .setRecvId(cId);
            DeviceBindStateChangeNoticeMessage.DeviceBindStateChangeNoticeContent content =
                    new DeviceBindStateChangeNoticeMessage.DeviceBindStateChangeNoticeContent()
                            .setIsBind(false);
            noticeMessage.setContent(content);

            Message message = MessageConverter.formMessaging(noticeMessage);
            messageAdapter.save(message);

            // log.info("Send message to receiver: {}", JSONUtil.toJsonPrettyStr(noticeMessage));
            Object messagingObject = MessageConverter.toMessaging(message);
            rabbitMQMessaging.publish(genericManufacturerHeader, messagingObject);
        });

    }

    /**
     * 交换三码
     *
     * @param request                   请求
     * @param genericManufacturerHeader 请求头
     * @return Response
     */
    public ExchangeDeviceResponse exchange(ExchangeDeviceRequest request, GenericManufacturerHeader genericManufacturerHeader) {
        Optional<DeviceModel> optional = deviceRepository.findByHc(request.hc());
        DeviceModel deviceModel = optional.orElseThrow();
        String softWareVersion = genericManufacturerHeader.getSoftWareVersion();
        String model = genericManufacturerHeader.getDeviceModel();
        if (deviceModel.getActivationTime() == null) {

            // 设备版本号
            deviceModel.setVersion(softWareVersion);

            deviceModel.setActivationTime(Instant.now());
            deviceRepository.save(deviceModel);
        }

        // 更新设备版本号
        deviceRepository.updateVersionAndModelById(deviceModel.getId(), softWareVersion, model);

        // The log analysis platform will make statistics based on the [DeviceActivation] keyword
        log.info("[DeviceActivation] The device activation time has been updated. deviceId: {}, activationTime: {}",
                deviceModel.getId(), deviceModel.getActivationTime());

        /*String cId = CurrentRequestUser.get().getId();

        myTaskExecutor.execute(() ->{
            // 设备配置
            String battery = genericManufacturerHeader.getBattery();
            DeviceConfigReqVO deviceConfigReqVO = new DeviceConfigReqVO();
            deviceConfigReqVO.setCId(cId);
            deviceConfigReqVO.setKey(HeaderConstant.BATTERY);
            deviceConfigReqVO.setValue(battery);
            deviceConfigReqVO.setBeforeValue(battery);
            deviceConfigAdapter.deviceConfigMessageCenter(deviceConfigReqVO);
        });*/

        return new ExchangeDeviceResponse(
                deviceModel.getTicket(),
                deviceModel.getAesKey(),
                deviceModel.getHc(),
                deviceModel.getActivationTime().toEpochMilli());
    }

    /**
     * 手表绑定状态
     *
     * @param request 请求
     * @return Response
     */
    public DeviceWristwatchBindingResponse wristwatchBinding(DeviceWristwatchBindingRequest request, GenericManufacturerHeader genericManufacturerHeader) {
        ChildId childId = deviceBindStateAdapter.getBinder(request.ticket());
        if (Objects.isNull(childId)) {
            log.info("The device is not bound. ticket: {}", request.ticket());
            return new DeviceWristwatchBindingResponse()
                    .setIsBind(false);
        }
        AccessTokenUtil.AccessTokenBuilder builder = AccessTokenUtil.newBuilder()
                .setId(childId.getId())
                .setExpire(Duration.ofDays(36500).getSeconds() * 1000);
        String accessToken = AccessTokenUtil.create(builder);

        // 删除旧的token
        String existingToken = operations.opsForValue().
                get(String.format(LOGIN_USER_ID, childId.getId()));
        if (StrUtil.isNotBlank(existingToken)) {
            operations.delete(String.format(LOGIN_TOKEN, existingToken));
            operations.delete(String.format(LOGIN_USER_ID, childId.getId()));
        }

        // 记录token
        operations.opsForValue().
                set(String.format(LOGIN_TOKEN, accessToken), childId.getId());
        operations.opsForValue().
                set(String.format(LOGIN_USER_ID, childId.getId()), accessToken);

        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);

            // 设备配置
            deviceConfigAdapter.deviceConfigMessageCenter(childId.getId(), genericManufacturerHeader);
        });

        return new DeviceWristwatchBindingResponse()
                .setIsBind(true)
                .setCid(childId.getId())
                .setAccessToken(accessToken);
    }

    /**
     * 设备开关机
     *
     * @param powerOnOrShutdownRequest 设备开关机请求
     * @return
     */
    public void powerOnOrShutdown(PowerOnOrShutdownRequest powerOnOrShutdownRequest, GenericManufacturerHeader genericManufacturerHeader) {
        String ticket = powerOnOrShutdownRequest.getTicket();
        ChildId childId = deviceBindStateAdapter.getBinder(ticket);
        if (Objects.nonNull(childId) && StrUtil.isNotBlank(childId.getId())) {
            String cId = childId.getId();


            long deviceID = deviceRepository.findByTicket(ticket).orElseThrow(() -> new BusinessException(HttpStatus.HTTP_INTERNAL_ERROR, "设备不存在")).getId();
            int isPowerOnOrShutdown = powerOnOrShutdownRequest.getIsPowerOnOrShutdown() ? 1 : 0;
            MessageType messageType = powerOnOrShutdownRequest.getIsPowerOnOrShutdown() ? MessageType.WRISTWATCH_STARTUP_NOTICE_MESSAGE : MessageType.WRISTWATCH_SHUTDOWN_NOTICE_MESSAGE;

            // 消息中心
            myTaskExecutor.execute(() -> {
                // 加入消息到消息中心
                Optional<ChildModel> childModel = childRepository.findById(cId);
                if (childModel.isPresent()) {
                    ChildModel cm = childModel.get();
                    MessageCenter messageCenter = new MessageCenter();
                    messageCenter.setCid(cId);
                    messageCenter.setMessageType(MessageCenterType.MESSAGE_CENTER_TYPE_STARTUP_AND_SHUT_DOWN.getValue());
                    messageCenter.setMessageParams(Map.of("name", cm.getChildName(), "status", isPowerOnOrShutdown));
                    messageCenterAdapter.save(messageCenter, MessageCenterType.MESSAGE_CENTER_TYPE_STARTUP_AND_SHUT_DOWN);
                }
            });

            // 设备开关机消息通知
            List<ChildRefModel> childRefModelList = childRefRepository.findByChildId(cId);
            if (CollectionUtil.isNotEmpty(childRefModelList)) {
                childRefModelList.forEach(childRefModel -> {
                    myTaskExecutor.execute(() -> {
                        // 设备开关机消息通知
                        WristwatchStartupRequest wristwatchStartupRequest = new WristwatchStartupRequest();
                        wristwatchStartupRequest.setCid(cId);
                        wristwatchStartupRequest.setUserId(childRefModel.getUserId());
                        wristwatchStartupRequest.setDid(deviceID);
                        wristwatchStartupRequest.setHeader(genericManufacturerHeader);
                        wristwatchStartupRequest.setType(messageType);
                        powerOnOrShutdownMessageService.powerOnOrShutdownMessageChange(wristwatchStartupRequest);
                    });
                });
            }

            // 设备配置-开关机
            myTaskExecutor.execute(() -> {
                deviceConfigAdapter.deviceConfigStartupMessageCenter(cId, isPowerOnOrShutdown, genericManufacturerHeader);
            });
        }
    }

    /**
     * 获取应用管理列表
     *
     * @param request                           请求
     * @param genericManufacturerHeader         header头信息
     * @param isWristwatch                      是否手表端请求
     *
     * @return 返回应用管理列表
     */
    public GetAppManageResponse getAppManageList(GetAppManageRequest request, GenericManufacturerHeader genericManufacturerHeader, boolean isWristwatch) {
        // 通过 Ticket 查询设备
        Optional<DeviceModel> deviceModelOptional = deviceRepository.findByTicket(request.getTicket());
        if (deviceModelOptional.isEmpty()) {
            log.warn("[定位]不存在设备，设备ticket：{}", request.getTicket());
            throw new RuntimeException("不存在设备");
        }
        DeviceModel deviceModel = deviceModelOptional.get();
        log.info("[定位]设备数据：{}", JSONUtil.toJsonPrettyStr(deviceModel));

        long deviceId = deviceModel.getId();
        List<DeviceAppManageModel> models = deviceAppManageRepository.findByDeviceId(deviceId);
        if (models.isEmpty()) {
            Instant curTime = Instant.now();
            DeviceAppManageModel deviceAppManageModel = new DeviceAppManageModel();
            deviceAppManageModel.setDeviceId(deviceId);
            deviceAppManageModel.setAppId(100);
            deviceAppManageModel.setAppSwitch(true);
            deviceAppManageModel.setCreatedAt(curTime);
            deviceAppManageModel.setUpdatedAt(curTime);
            deviceAppManageRepository.save(deviceAppManageModel);
            models.add(deviceAppManageModel);
        }

        // 按 appId 字段正序排序（从小到大）
        models.sort(Comparator.comparing(DeviceAppManageModel::getAppId));

        List<GetAppManageResponse.GetAppManage> appManages = new ArrayList<>();
        for (DeviceAppManageModel model : models) {
            GetAppManageResponse.GetAppManage alarmClock = new GetAppManageResponse.GetAppManage()
                    .setAppId(model.getAppId())
                    .setAppSwitch(model.getAppSwitch());
            appManages.add(alarmClock);
        }
        //如果是手表发起的请求，需要更新一下Header中携带的手表电量等信息。
        if (isWristwatch) {
            ChildId childId = deviceBindStateAdapter.getBinder(request.getTicket());
            if (Objects.nonNull(childId) && StrUtil.isNotBlank(childId.getId())) {
                log.info("[定位]getAppManageList设备手表配置：{}", childId.getId());
                myTaskExecutor.execute(() -> {
                    // 设备配置
                    deviceConfigAdapter.deviceConfigMessageCenter(childId.getId(), genericManufacturerHeader);
                });
            }
        }

        return new GetAppManageResponse()
            .setAppManages(appManages);
    }

    /**
     * 发送设置应用管理中应用开关通知
     *
     * @param cid      小孩ID
     * @param header   请求header
     * @return
     */
    private void handleAppManageAppSwitchNoticeMessage(String cid, GenericManufacturerHeader header) {
        // 发送mq闹钟消息
        AppManageAppSwitchNoticeMessage noticeMessage = new AppManageAppSwitchNoticeMessage();
        noticeMessage.setMsgType(MessageType.REMOTE_APP_MANAGE_APP_SWITCH_NOTICE_MESSAGE)
                .setMsgId(redisMessageIdIncrementingAdapter.incr())
                .setSendTime(Instant.now().toEpochMilli())
                .setSendId(CurrentRequestUser.get().getId())
                .setRecvId(cid);
        noticeMessage.setContent(new AppManageAppSwitchNoticeMessage.AppManageAppSwitchNoticeContent());
        Message message = MessageConverter.formMessaging(noticeMessage);
        messageAdapter.save(message);

        log.info("Send message to receiver: {}", noticeMessage.getRecvId());
        myTaskExecutor.execute(() -> {
            Object messagingObject = MessageConverter.toMessaging(message);
            rabbitMQMessaging.publish(header, messagingObject);
        });
    }

    /**
     * 更新应用管理中应用开关
     *
     * @param request 请求
     * @return 返回
     */
    @Transactional
    public void updateAppManage(UpdateAppManageRequest request, GenericManufacturerHeader genericManufacturerHeader) {
        Optional<DeviceModel> deviceModelOptional = deviceRepository.findByTicket(request.ticket());
        if (deviceModelOptional.isEmpty()) {
            log.warn("[定位]不存在设备，设备ticket：{}", request.ticket());
            throw new RuntimeException("不存在设备");
        }
        DeviceModel deviceModel = deviceModelOptional.get();
        log.info("[定位]设备数据：{}", JSONUtil.toJsonPrettyStr(deviceModel));
        DeviceAppManageModel deviceAppManageModel = deviceAppManageRepository.findByDeviceIdAndAppId(deviceModel.getId(), request.appId());
        if (Objects.isNull(deviceAppManageModel)) {
            throw new BusinessException(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR.value(), "不存在该应用");
        }
        deviceAppManageModel.setAppSwitch(request.appSwitch());
        deviceAppManageRepository.save(deviceAppManageModel);

        // 发送设置应用管理应用开关mq消息
        handleAppManageAppSwitchNoticeMessage(request.cid(), genericManufacturerHeader);
    }


}
