package org.dromara.app.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.app.domain.AppDevice;
import org.dromara.app.domain.AppSchedulingDevice;
import org.dromara.app.domain.AppWateringLog;
import org.dromara.app.domain.bo.AppDeviceBo;
import org.dromara.app.domain.vo.AppDeviceVo;
import org.dromara.app.mapper.AppDeviceMapper;
import org.dromara.app.mapper.AppSchedulingDeviceMapper;
import org.dromara.app.mapper.AppWateringLogMapper;
import org.dromara.app.service.IAppDeviceService;
import org.dromara.app.service.IAppWateringLogService;
import org.dromara.app.service.IDeviceCommandService;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
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.common.satoken.utils.LoginHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Slf4j
@RequiredArgsConstructor
@Service
public class AppDeviceServiceImpl implements IAppDeviceService {

    private final AppDeviceMapper baseMapper;
    private final AppSchedulingDeviceMapper schedulingDeviceMapper;
    private final AppWateringLogMapper wateringLogMapper;

    // @Lazy 打破 AppDeviceServiceImpl ↔ DeviceCommandServiceImpl 双向构造器循环
    @Lazy
    @Autowired
    private IDeviceCommandService deviceCommandService;
    private final IAppWateringLogService wateringLogService;

    @Override
    public AppDeviceVo queryById(String deviceNo) {
        return baseMapper.selectVoById(deviceNo);
    }

    @Override
    public TableDataInfo<AppDeviceVo> queryPageList(AppDeviceBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<AppDevice> lqw = buildQueryWrapper(bo);
        Page<AppDeviceVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    @Override
    public List<AppDeviceVo> queryList(AppDeviceBo bo) {
        LambdaQueryWrapper<AppDevice> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<AppDevice> buildQueryWrapper(AppDeviceBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<AppDevice> lqw = Wrappers.lambdaQuery();
        lqw.orderByAsc(AppDevice::getDeviceNo);
        lqw.eq(StringUtils.isNotBlank(bo.getDeviceNo()), AppDevice::getDeviceNo, bo.getDeviceNo());
        lqw.eq(bo.getUserId() != null, AppDevice::getUserId, bo.getUserId());
        lqw.like(StringUtils.isNotBlank(bo.getDeviceName()), AppDevice::getDeviceName, bo.getDeviceName());
        lqw.eq(StringUtils.isNotBlank(bo.getDeviceImg()), AppDevice::getDeviceImg, bo.getDeviceImg());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), AppDevice::getStatus, bo.getStatus());
        lqw.eq(StringUtils.isNotBlank(bo.getPowerLevel()), AppDevice::getPowerLevel, bo.getPowerLevel());
        lqw.eq(bo.getPowerLevelUpdatatime() != null, AppDevice::getPowerLevelUpdatatime, bo.getPowerLevelUpdatatime());
        lqw.like(StringUtils.isNotBlank(bo.getWifiName()), AppDevice::getWifiName, bo.getWifiName());
        lqw.eq(StringUtils.isNotBlank(bo.getWifiPassword()), AppDevice::getWifiPassword, bo.getWifiPassword());
        lqw.eq(StringUtils.isNotBlank(bo.getDeviceEm()), AppDevice::getDeviceEm, bo.getDeviceEm());
        lqw.eq(StringUtils.isNotBlank(bo.getDeviceSn()), AppDevice::getDeviceSn, bo.getDeviceSn());
        lqw.eq(StringUtils.isNotBlank(bo.getFwVer()), AppDevice::getFwVer, bo.getFwVer());
        lqw.eq(StringUtils.isNotBlank(bo.getMacAddress()), AppDevice::getMacAddress, bo.getMacAddress());
        lqw.eq(bo.getExpirationTime() != null, AppDevice::getExpirationTime, bo.getExpirationTime());
        return lqw;
    }

    @Override
    public Boolean insertByBo(AppDeviceBo bo) {
       // fillBindTokenHash(bo);
        AppDevice add = MapstructUtils.convert(bo, AppDevice.class);
        validEntityBeforeSave(add);
        return baseMapper.insert(add) > 0;
    }

    @Override
    public Boolean updateByBo(AppDeviceBo bo) {
       // fillBindTokenHash(bo);
        AppDevice update = MapstructUtils.convert(bo, AppDevice.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    @Override
    public Boolean registerByMqtt(AppDeviceBo bo) {
        if (StringUtils.isBlank(bo.getDeviceNo())) {
            throw new ServiceException("设备编号不能为空");
        }
        AppDevice exists = baseMapper.selectById(bo.getDeviceNo());

        AppDevice device = MapstructUtils.convert(bo, AppDevice.class);
        device.setPowerLevelUpdatatime(new Date());
      //  applyRegisterBindToken(device, bo, exists);
        if (StringUtils.isBlank(device.getStatus())) {
            device.setStatus("2");
            device.setWorkStatus("2");
        }

        if (exists == null) {
            try {
                return baseMapper.insert(device) > 0;
            } catch (org.springframework.dao.DuplicateKeyException ignored) {
                return baseMapper.updateById(device) > 0;
            }
        }

        return baseMapper.updateById(device) > 0;
    }

    @Override
    public AppDeviceVo bindRegisteredDevice(AppDeviceBo bo) {
        if (StringUtils.isBlank(bo.getDeviceNo())) {
            throw new ServiceException("设备编号不能为空");
        }
        Long userId = LoginHelper.getUserId();
        AppDevice exists = baseMapper.selectById(bo.getDeviceNo());
        if (exists == null) {
            throw new ServiceException("设备未上线注册，请先完成配网");
        }
        if (exists.getUserId() != null && !exists.getUserId().equals(userId)) {
            throw new ServiceException("设备已被其他用户绑定");
        }
       // validBindToken(bo.getBindToken(), exists);

        String status = StringUtils.isBlank(bo.getStatus()) ? exists.getStatus() : bo.getStatus();
        String deviceName = StringUtils.isBlank(bo.getDeviceName()) ? exists.getDeviceName() : bo.getDeviceName();
        int rows = baseMapper.bindIfAvailable(exists.getDeviceNo(), userId, deviceName, status);
        if (rows <= 0) {
            AppDevice current = baseMapper.selectById(bo.getDeviceNo());
            if (current == null) {
                throw new ServiceException("设备未上线注册，请先完成配网");
            }
            if (current.getUserId() != null && !current.getUserId().equals(userId)) {
                throw new ServiceException("设备已被其他用户绑定");
            }
            throw new ServiceException("设备绑定失败，请重试");
        }
        //下发设备已绑定状态
        deviceCommandService.sendBindDeviceCommand(exists.getDeviceNo());
        return baseMapper.selectVoById(exists.getDeviceNo());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Lock4j(keys = {"'device:command:' + #deviceNo"}, acquireTimeout = 5000, expire = 30000)
    public Boolean switchDevice(String deviceNo, String workStatus, String startTime, Integer durationMin) {
        if (StringUtils.isBlank(deviceNo)) {
            throw new ServiceException("设备编号不能为空");
        }
        if (StringUtils.isBlank(workStatus)) {
            throw new ServiceException("设备工作状态不能为空");
        }
        if ("1".equals(workStatus) && (durationMin == null || durationMin <= 0)) {
            throw new ServiceException("持续时间必须大于0");
        }

        Long userId = LoginHelper.getUserId();
        int rows = baseMapper.updateWorkStatusByUser(deviceNo, userId, workStatus);
        if (rows <= 0) {
            throw new ServiceException("设备不存在或无权操作");
        }

        AppDeviceVo device = baseMapper.selectVoById(deviceNo);
        if (ObjectUtil.isNotNull(device) && StringUtils.isNotBlank(device.getDeviceNo())) {
            // 通过业务层统一下发命令（含浇水日志记录）
            String commandId = deviceCommandService.sendSwitchCommand(deviceNo, workStatus, startTime, durationMin);
            log.info("[DEVICE] switch command sent deviceNo={} commandId={}", deviceNo, commandId);
        }

        return true;
    }


    private void validEntityBeforeSave(AppDevice entity) {
        if (StringUtils.isBlank(entity.getDeviceNo())) {
            throw new ServiceException("设备编号不能为空");
        }
    }

    @Override
    @Transactional
    public Boolean deleteWithValidByIds(Collection<String> deviceNos, Boolean isValid) {
        boolean flag = baseMapper.deleteByIds(deviceNos) > 0;
        if (flag) {
            schedulingDeviceMapper.delete(
                new QueryWrapper<AppSchedulingDevice>().in("device_no", deviceNos)
            );
            wateringLogMapper.delete(new QueryWrapper<AppWateringLog>().in("device_no", deviceNos));
        }
        return flag;
    }
}
