package org.dromara.app.handler;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.app.domain.AppDevice;
import org.dromara.app.mapper.AppDeviceMapper;
import org.dromara.app.service.IAppWateringLogService;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@RequiredArgsConstructor
public class MqttDeviceStatusService {

    private static final String DEVICE_STATUS_LOCK_PREFIX = "lock:mqtt:device:status:";

    private final AppDeviceMapper appDeviceMapper;
    private final IAppWateringLogService wateringLogService;

    @Value("${mqtt.command-ack.device-status-cache-prefix}")
    private String deviceStatusCachePrefix;

    @Value("${mqtt.command-ack.device-status-cache-ttl-seconds:600}")
    private long deviceStatusCacheTtlSeconds;

    public void markOnline(String deviceNo) {
        if (StringUtils.isBlank(deviceNo)) {
            return;
        }
        withDeviceStatusLock(deviceNo, () -> {
            Map<String, Object> statusCache = new HashMap<>();
            statusCache.put("deviceNo", deviceNo);
            statusCache.put("status", "1");
            statusCache.put("lastReportTime", Instant.now().toString());
            RedisUtils.setCacheObject(
                deviceStatusCachePrefix + deviceNo,
                statusCache,
                Duration.ofSeconds(deviceStatusCacheTtlSeconds)
            );
            appDeviceMapper.update(null,
                new LambdaUpdateWrapper<AppDevice>()
                    .set(AppDevice::getStatus, "1")
                    .eq(AppDevice::getDeviceNo, deviceNo)
            );
            return true;
        });
    }

    public void markOffline(String deviceNo, String reason) {
        if (StringUtils.isBlank(deviceNo)) {
            return;
        }
        markOfflineWithLock(deviceNo, reason, false);
    }

    public boolean markOfflineIfCacheMissing(String deviceNo, String reason) {
        if (StringUtils.isBlank(deviceNo)) {
            return false;
        }
        return markOfflineWithLock(deviceNo, reason, true);
    }

    private boolean markOfflineWithLock(String deviceNo, String reason, boolean skipWhenCacheExists) {
        return withDeviceStatusLock(deviceNo, () -> {
            if (skipWhenCacheExists && RedisUtils.hasKey(deviceStatusCachePrefix + deviceNo)) {
                return false;
            }
            RedisUtils.deleteObject(deviceStatusCachePrefix + deviceNo);
            int updated = appDeviceMapper.update(null,
                new LambdaUpdateWrapper<AppDevice>()
                    .set(AppDevice::getStatus, "0")
                    .set(AppDevice::getWorkStatus, "0")
                    .ne(AppDevice::getStatus, "0")
                    .eq(AppDevice::getDeviceNo, deviceNo)
            );
            if (updated > 0) {
                int count = wateringLogService.finishRunningLogsByDevice(deviceNo, new Date(), reason);
                if (count > 0) {
                    log.info("[MQTT] 已结束设备进行中的浇水记录 设备编号={} 记录数={} 原因={}", deviceNo, count, reason);
                }
            }
            return updated > 0;
        });
    }

    private boolean withDeviceStatusLock(String deviceNo, java.util.function.BooleanSupplier action) {
        RLock lock = RedisUtils.getClient().getLock(DEVICE_STATUS_LOCK_PREFIX + deviceNo);
        boolean locked = false;
        try {
            locked = lock.tryLock(0, 10, TimeUnit.SECONDS);
            if (!locked) {
                log.debug("[MQTT] 获取设备状态锁失败，本次跳过 设备编号={}", deviceNo);
                return false;
            }
            return action.getAsBoolean();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("[MQTT] 获取设备状态锁被中断 设备编号={}", deviceNo);
            return false;
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
