package com.gameplatform.server.device;

import com.gameplatform.server.model.dto.device.DeviceStatusResponse;
import com.gameplatform.server.model.entity.agent.LinkTask;
import com.gameplatform.server.service.link.LinkStatusService;
import com.gameplatform.server.mapper.agent.LinkTaskMapper;
import com.gameplatform.server.service.external.ScriptClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 检测模块：封装与脚本端交互的设备状态读取能力。
 * - 提供全量快照与单设备状态两种接口
 * - 同时提供异步（Mono）与阻塞（block）调用
 * - 内置短TTL缓存，降低高并发下对脚本端的压力
 */
@Service
public class Detection {
    private static final Logger log = LoggerFactory.getLogger(Detection.class);

    private final ScriptClient scriptClient;
    private final DeviceStats deviceStats;
    private final LinkStatusService linkStatusService;
    private final LinkTaskMapper linkTaskMapper;

    // 缓存配置（可通过 application.yml 覆盖）
    private final long listAllCacheTtlMs;
    private final long readOneCacheTtlMs;

    // 全量快照缓存
    private volatile DeviceStatusResponse lastSnapshot;
    private volatile long lastSnapshotAtMs = 0L;

    // 单设备缓存
    private final ConcurrentHashMap<String, DeviceEntry> deviceCache = new ConcurrentHashMap<>();

    public Detection(
            ScriptClient scriptClient,
            DeviceStats deviceStats,
            LinkStatusService linkStatusService,
            LinkTaskMapper linkTaskMapper,
            @Value("${detection.listAll.cacheTtlMs:500}") long listAllCacheTtlMs,
            @Value("${detection.readOne.cacheTtlMs:200}") long readOneCacheTtlMs
    ) {
        this.scriptClient = scriptClient;
        this.deviceStats = deviceStats;
        this.linkStatusService = linkStatusService;
        this.linkTaskMapper = linkTaskMapper;
        this.listAllCacheTtlMs = listAllCacheTtlMs;
        this.readOneCacheTtlMs = readOneCacheTtlMs;
    }

    /**
     * 异步获取全量设备快照（带TTL缓存）。
     */
    public Mono<DeviceStatusResponse> listAllDevicesAsync() {
        DeviceStatusResponse cached = getFreshSnapshot();
        if (cached != null) {
            if (log.isDebugEnabled()) {
                log.debug("listAllDevicesAsync use cache: availableCount={}, total={}",
                        cached.getAvailableCount(), cached.getTotalDevices());
            }
            return Mono.just(cached);
        }
        return scriptClient.checkAvailableDeviceStatus()
                .doOnSuccess(resp -> {
                    updateSnapshotCache(resp);
                    if (resp != null && log.isDebugEnabled()) {
                        log.debug("listAllDevicesAsync fetched fresh: availableCount={}, total={}",
                                resp.getAvailableCount(), resp.getTotalDevices());
                    }
                });
    }

    /**
     * 阻塞获取全量设备快照（带TTL缓存）。
     */
    public DeviceStatusResponse listAllDevices() {
        long t0 = System.currentTimeMillis();
        DeviceStatusResponse cached = getFreshSnapshot();
        if (cached != null) {
            long elapsed = System.currentTimeMillis() - t0;
            if (log.isDebugEnabled()) {
                log.debug("listAllDevices use cache in {} ms: availableCount={}, total={}",
                        elapsed, cached.getAvailableCount(), cached.getTotalDevices());
            }
            return cached;
        }

        long tFetchStart = System.currentTimeMillis();
        DeviceStatusResponse resp = scriptClient.checkAvailableDeviceStatus().block();
        long tFetchEnd = System.currentTimeMillis();

        long tCacheStart = tFetchEnd;
        updateSnapshotCache(resp);
        long tCacheEnd = System.currentTimeMillis();

        if (resp != null && log.isInfoEnabled()) {
            long total = tCacheEnd - t0;
            long fetch = tFetchEnd - tFetchStart;
            long cacheWrite = tCacheEnd - tCacheStart;
            log.info("listAllDevices fetched fresh in {} ms (http+parse={} ms, cacheWrite={} ms): availableCount={}, total={}",
                    total, fetch, cacheWrite, resp.getAvailableCount(), resp.getTotalDevices());
        }
        return resp;
    }

    /**
     * 获取最近一次缓存的全量设备快照（不触发刷新，不受TTL约束）。
     */
    public DeviceStatusResponse getLastSnapshot() {
        return this.lastSnapshot;
    }

    /**
     * 定时任务：全量拉取并交由 DeviceStats 更新内存分类与审计。
     * 默认每 30 秒执行一次，可通过配置覆盖：detection.poll.cron 或 detection.poll.fixedDelayMs
     */
    @Scheduled(fixedRate = 30000)
    public void pollAndUpdateDeviceStats() {
        try {
            log.info("定时拉取设备快照并更新统计");
            DeviceStatusResponse snapshot = listAllDevices();
            if (snapshot != null) {
                deviceStats.updateWithSnapshot(snapshot);
                log.info("设备快照更新统计完成");

                // 基于最新设备状态推进 USING→LOGGED_IN
                try {
                    // 查询所有 USING 任务
                    java.util.List<LinkTask> usingTasks = linkTaskMapper.findByStatus("USING");
                    if (usingTasks != null && !usingTasks.isEmpty()) {
                        for (LinkTask task : usingTasks) {
                            String machineId = task.getMachineId();
                            if (machineId == null) { continue; }
                            DeviceStatusResponse.DeviceInfo info = snapshot.getDevices() != null ? snapshot.getDevices().get(machineId) : null;
                            String val = info != null ? info.getVal() : null;
                            if ("已运行".equals(val)) {
                                // 设备显示已运行，触发一次登录检测
                                linkStatusService.pollLogin(task.getCodeNo())
                                        .subscribe(
                                                r -> { if (r.isSuccess()) { log.info("检测到设备已运行，推进链接登录成功 codeNo={}", task.getCodeNo()); } },
                                                e -> log.warn("基于快照推进登录失败 codeNo={} err={}", task.getCodeNo(), e.getMessage())
                                        );
                            }
                        }
                    }
                } catch (Exception ex) {
                    log.warn("基于设备快照推进USING→LOGGED_IN时出现异常", ex);
                }
            }
        } catch (Exception e) {
            log.error("定时拉取设备快照并更新统计失败", e);
        }
    }

    /**
     * 异步读取单个设备状态（包含 f0/f1 等信息，带短TTL缓存）。
     */
    public Mono<Map<String, Object>> readDeviceAsync(String machineId) {
        Map<String, Object> cached = getFreshDevice(machineId);
        if (cached != null) {
            if (log.isDebugEnabled()) {
                log.debug("readDeviceAsync use cache: machineId={}", machineId);
            }
            return Mono.just(cached);
        }
        return scriptClient.getDeviceStatus(machineId)
                .doOnSuccess(map -> {
                    updateDeviceCache(machineId, map);
                    if (log.isDebugEnabled()) {
                        log.debug("readDeviceAsync fetched fresh: machineId={}", machineId);
                    }
                });
    }

    /**
     * 阻塞读取单个设备状态（包含 f0/f1 等信息，带短TTL缓存）。
     */
    public Map<String, Object> readDevice(String machineId) {
        Map<String, Object> cached = getFreshDevice(machineId);
        if (cached != null) {
            if (log.isDebugEnabled()) {
                log.debug("readDevice use cache: machineId={}", machineId);
            }
            return cached;
        }
        Map<String, Object> map = scriptClient.getDeviceStatus(machineId).block();
        updateDeviceCache(machineId, map);
        if (log.isDebugEnabled()) {
            log.debug("readDevice fetched fresh: machineId={}", machineId);
        }
        return map;
    }

    // ---------------------- 内部：缓存管理 ----------------------

    private DeviceStatusResponse getFreshSnapshot() {
        DeviceStatusResponse snapshot = this.lastSnapshot;
        long now = System.currentTimeMillis();
        if (snapshot != null && (now - lastSnapshotAtMs) < listAllCacheTtlMs) {
            return snapshot;
        }
        return null;
    }

    private void updateSnapshotCache(DeviceStatusResponse snapshot) {
        if (snapshot == null) {
            return;
        }
        this.lastSnapshot = snapshot;
        this.lastSnapshotAtMs = System.currentTimeMillis();
    }

    private Map<String, Object> getFreshDevice(String machineId) {
        DeviceEntry entry = deviceCache.get(machineId);
        if (entry == null) {
            return null;
        }
        long now = System.currentTimeMillis();
        if ((now - entry.cachedAtMs) < readOneCacheTtlMs) {
            return entry.status;
        }
        return null;
    }

    private void updateDeviceCache(String machineId, Map<String, Object> status) {
        if (machineId == null || status == null) {
            return;
        }
        deviceCache.put(machineId, new DeviceEntry(status, System.currentTimeMillis()));
    }

    private static final class DeviceEntry {
        final Map<String, Object> status;
        final long cachedAtMs;
        DeviceEntry(Map<String, Object> status, long cachedAtMs) {
            this.status = status;
            this.cachedAtMs = cachedAtMs;
        }
    }
}
