package com.ttstd.signaling.manager;

import com.ttstd.signaling.model.DeviceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * 连接请求管理器：对主控端发往被控端的 OFFER（连接请求）进行统一治理。
 *
 * <p>职责：
 * <ol>
 *   <li><b>校验</b>：OFFER 仅允许由 CONTROLLER 发往 CONTROLLED，否则拒绝并向发送方报错。</li>
 *   <li><b>去重</b>：同一主控端对同一被控端的重复 OFFER（如网络重传/重复点击）在短时间内被忽略，
 *       避免被控端反复弹窗。</li>
 *   <li><b>待确认跟踪</b>：记录处于“等待被控端确认”状态的请求，收到 ANSWER（接受）或
 *       CONNECTION_REJECTED（拒绝）时清理；超时未决则通知主控端请求超时。</li>
 * </ol>
 */
@Component
public class ConnectionRequestManager {

    private static final Logger logger = LoggerFactory.getLogger(ConnectionRequestManager.class);

    /** 同一连接的去重窗口：窗口内重复的 OFFER 将被忽略。 */
    private static final long DEDUP_WINDOW_MS = 5_000;
    /** 被控端确认超时：超过该时间既无 ANSWER 也无拒绝，则通知主控端超时。 */
    private static final long REQUEST_TIMEOUT_MS = 60_000;

    private final SessionManager sessionManager;
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
        Thread t = new Thread(r, "connection-request-timeout");
        t.setDaemon(true);
        return t;
    });

    /** 最近一次 OFFER 的时间戳，键为 "fromDeviceId->toDeviceId"。 */
    private final Map<String, Long> lastOfferTime = new ConcurrentHashMap<>();
    /** 处于“等待被控端确认”状态的请求，键同上。 */
    private final Map<String, PendingRequest> pendingRequests = new ConcurrentHashMap<>();

    public ConnectionRequestManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    /** 向指定设备发送消息的回调（由 Handler 注入，用于回送错误/超时通知）。 */
    public interface SignalSender {
        void send(String deviceId, Object message);
    }

    private SignalSender sender;

    public void setSender(SignalSender sender) {
        this.sender = sender;
    }

    private static String keyOf(String from, String to) {
        return from + "->" + to;
    }

    public long getDedupWindowMs() {
        return DEDUP_WINDOW_MS;
    }

    public long getRequestTimeoutMs() {
        return REQUEST_TIMEOUT_MS;
    }

    /** 返回当前处于“待被控端确认”状态的连接请求列表（供后台管理接口使用）。 */
    public List<Map<String, Object>> getPendingRequests() {
        long now = System.currentTimeMillis();
        List<Map<String, Object>> result = new ArrayList<>();
        for (PendingRequest p : pendingRequests.values()) {
            Map<String, Object> m = new HashMap<>();
            m.put("fromDeviceId", p.fromDeviceId);
            m.put("toDeviceId", p.toDeviceId);
            m.put("createdAt", p.createdAt);
            m.put("elapsedMs", now - p.createdAt);
            result.add(m);
        }
        return result;
    }

    /**
     * 校验 OFFER 的合法性（发送方必须为 CONTROLLER，接收方必须为 CONTROLLED）。
     *
     * @return 校验失败时的错误信息（含发送目标），成功返回 null。
     */
    public String validateOffer(String fromDeviceId, String toDeviceId) {
        if (fromDeviceId == null || toDeviceId == null) {
            return "连接请求缺少发送方或接收方设备ID";
        }
        DeviceType fromType = sessionManager.getDeviceType(fromDeviceId);
        DeviceType toType = sessionManager.getDeviceType(toDeviceId);
        if (fromType != DeviceType.CONTROLLER) {
            return "连接请求只能由主控端（CONTROLLER）发起";
        }
        if (toType != DeviceType.CONTROLLED) {
            return "连接请求的目标必须是被控端（CONTROLLED）";
        }
        return null;
    }

    /**
     * 去重判断：若同一连接的最近一次 OFFER 在去重窗口内，则视为重复。
     */
    public boolean isDuplicateOffer(String fromDeviceId, String toDeviceId) {
        String key = keyOf(fromDeviceId, toDeviceId);
        long now = System.currentTimeMillis();
        Long last = lastOfferTime.get(key);
        if (last != null && (now - last) < DEDUP_WINDOW_MS) {
            return true;
        }
        lastOfferTime.put(key, now);
        return false;
    }

    /**
     * 登记一个待确认的连接请求，并安排超时任务。
     */
    public void registerPendingRequest(String fromDeviceId, String toDeviceId) {
        String key = keyOf(fromDeviceId, toDeviceId);
        PendingRequest pending = new PendingRequest(fromDeviceId, toDeviceId);
        pending.timeoutTask = scheduler.schedule(() -> {
            // 超时：仅当该请求仍未被接受/拒绝时通知主控端。
            if (pendingRequests.remove(key, pending)) {
                logger.warn("Connection request {} timed out after {}ms", key, REQUEST_TIMEOUT_MS);
                if (sender != null) {
                    sender.send(fromDeviceId, buildEvent("REQUEST_TIMEOUT", toDeviceId,
                            "被控端（" + toDeviceId + "）未在限定时间内响应连接请求，请稍后重试"));
                }
            }
        }, REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
        pendingRequests.put(key, pending);
        logger.info("Registered pending connection request {}", key);
    }

    /**
     * 当被控端接受（收到 ANSWER）或拒绝（收到 CONNECTION_REJECTED）时，清理待确认状态。
     */
    public void completePendingRequest(String fromDeviceId, String toDeviceId) {
        String key = keyOf(fromDeviceId, toDeviceId);
        PendingRequest pending = pendingRequests.remove(key);
        if (pending != null) {
            if (pending.timeoutTask != null) {
                pending.timeoutTask.cancel(false);
            }
            logger.info("Completed pending connection request {}", key);
        }
    }

    private Map<String, Object> buildEvent(String type, String toDeviceId, String message) {
        Map<String, Object> event = new ConcurrentHashMap<>();
        event.put("type", type);
        event.put("toDeviceId", toDeviceId);
        event.put("payload", message);
        return event;
    }

    private static class PendingRequest {
        final String fromDeviceId;
        final String toDeviceId;
        final long createdAt;
        ScheduledFuture<?> timeoutTask;

        PendingRequest(String fromDeviceId, String toDeviceId) {
            this.fromDeviceId = fromDeviceId;
            this.toDeviceId = toDeviceId;
            this.createdAt = System.currentTimeMillis();
        }
    }
}
