package com.ttstd.signaling.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ttstd.signaling.manager.ConnectionRequestManager;
import com.ttstd.signaling.manager.SessionManager;
import com.ttstd.signaling.manager.SignalMetrics;
import com.ttstd.signaling.model.DeviceType;
import com.ttstd.signaling.model.SignalMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class SignalWebSocketHandler extends TextWebSocketHandler {

    private static final Logger logger = LoggerFactory.getLogger(SignalWebSocketHandler.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();

    private final SessionManager sessionManager;
    private final ConnectionRequestManager connectionRequestManager;
    private final SignalMetrics metrics;

    public SignalWebSocketHandler(SessionManager sessionManager,
                                  ConnectionRequestManager connectionRequestManager,
                                  SignalMetrics metrics) {
        this.sessionManager = sessionManager;
        this.connectionRequestManager = connectionRequestManager;
        this.metrics = metrics;
        this.connectionRequestManager.setSender(this::sendToDevice);
    }

    /**
     * 向指定设备回送服务端事件（错误/超时通知等）。
     */
    private void sendToDevice(String deviceId, Object message) {
        WebSocketSession session = sessionManager.getSession(deviceId);
        if (session == null || !session.isOpen()) {
            logger.warn("Cannot send event to {}: device offline", deviceId);
            return;
        }
        sendToSession(session, message);
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        logger.info("New WebSocket connection: {}", session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        logger.debug("Received message: {}", payload);
        metrics.incMessage();

        try {
            SignalMessage signalMessage = objectMapper.readValue(payload, SignalMessage.class);
            String type = signalMessage.getType();

            if (type == null) {
                logger.warn("Message type is null");
                return;
            }

            switch (type.toUpperCase()) {
                case "REGISTER":
                    handleRegister(session, signalMessage);
                    break;
                case "DEVICE_LIST":
                    handleDeviceList(session, signalMessage);
                    break;
                case "PING":
                    // 客户端心跳保活消息，无需处理，仅用于防止中间代理因空闲超时断开连接
                    break;
                case "OFFER":
                    metrics.incOffer();
                    // 连接请求：统一经 ConnectionRequestManager 做校验/去重/待确认跟踪后再转发
                    handleConnectionRequest(signalMessage);
                    break;
                case "ANSWER":
                    metrics.incAnswer();
                    // 被控端已接受：清理待确认状态并转发 Answer 给主控端
                    connectionRequestManager.completePendingRequest(
                            signalMessage.getToDeviceId(), signalMessage.getFromDeviceId());
                    forwardMessage(signalMessage, true);
                    break;
                case "CONNECTION_REJECTED":
                    metrics.incRejected();
                    // 被控端已拒绝：清理待确认状态并转发拒绝通知给主控端
                    connectionRequestManager.completePendingRequest(
                            signalMessage.getToDeviceId(), signalMessage.getFromDeviceId());
                    forwardMessage(signalMessage, false);
                    break;
                case "ICE_CANDIDATE":
                case "CONTROL_COMMAND":
                    // 普通信令：目标不在线时静默丢弃，不返回提示
                    forwardMessage(signalMessage, false);
                    break;
                default:
                    // 其他消息类型直接转发，目标不在线时静默丢弃
                    forwardMessage(signalMessage, false);
                    break;
            }
        } catch (Exception e) {
            logger.error("Error handling message: {}", e.getMessage(), e);
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        logger.info("WebSocket connection closed: {} ({})", session.getId(), status);
        sessionManager.unregisterSession(session);
        metrics.recordSessionCount(sessionManager.getOnlineCount());
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // EOFException / 连接被对端重置 属于正常网络事件（客户端网络中断、代理空闲超时等），
        // 并非服务器错误，降级为 DEBUG 记录，避免误导。
        boolean benign = exception instanceof java.io.EOFException
                || (exception.getMessage() != null
                    && (exception.getMessage().contains("Connection reset")
                        || exception.getMessage().contains("Broken pipe")
                        || exception.getMessage().contains("An established connection")));
        if (benign) {
            logger.debug("Transport closed (client disconnected) on session {}: {}",
                    session.getId(), exception.getMessage());
        } else {
            logger.warn("Transport error on session {}: {}", session.getId(), exception.getMessage());
        }
        sessionManager.unregisterSession(session);
        metrics.recordSessionCount(sessionManager.getOnlineCount());
        // 连接已不可用时无需再以 SERVER_ERROR(1011) 关闭；仅在仍打开时温和关闭。
        if (session.isOpen()) {
            session.close(CloseStatus.GOING_AWAY);
        }
    }

    private void handleRegister(WebSocketSession session, SignalMessage message) {
        String deviceId = message.getFromDeviceId();
        String deviceTypeStr = message.getDeviceType();

        if (deviceId == null || deviceTypeStr == null) {
            logger.warn("Invalid REGISTER message: missing deviceId or deviceType");
            return;
        }

        try {
            DeviceType deviceType = DeviceType.valueOf(deviceTypeStr.toUpperCase());
            sessionManager.registerDevice(deviceId, deviceType, session);
            metrics.incRegistered();
            metrics.recordSessionCount(sessionManager.getOnlineCount());

            // 回复注册成功
            Map<String, Object> response = new HashMap<>();
            response.put("type", "REGISTER_SUCCESS");
            response.put("deviceId", deviceId);
            sendToSession(session, response);

            logger.info("Device {} registered as {}", deviceId, deviceType);
        } catch (IllegalArgumentException e) {
            logger.warn("Invalid device type: {}", deviceTypeStr);
        }
    }

    private void handleDeviceList(WebSocketSession session, SignalMessage message) {
        List<String> controllers = sessionManager.getDevicesByType(DeviceType.CONTROLLER);
        List<String> controlled = sessionManager.getDevicesByType(DeviceType.CONTROLLED);

        Map<String, Object> response = new HashMap<>();
        response.put("type", "DEVICE_LIST");
        response.put("controllers", controllers);
        response.put("controlled", controlled);
        sendToSession(session, response);
    }

    /**
     * 处理主控端发来的连接请求（OFFER）：先做设备类型校验与去重，
     * 再登记“待被控端确认”状态，最后转发给被控端。
     */
    private void handleConnectionRequest(SignalMessage message) {
        String fromDeviceId = message.getFromDeviceId();
        String toDeviceId = message.getToDeviceId();

        // 1. 校验：仅允许 CONTROLLER -> CONTROLLED
        String error = connectionRequestManager.validateOffer(fromDeviceId, toDeviceId);
        if (error != null) {
            logger.warn("Invalid connection request {} -> {}: {}", fromDeviceId, toDeviceId, error);
            Map<String, Object> response = new HashMap<>();
            response.put("type", "REQUEST_ERROR");
            response.put("toDeviceId", toDeviceId);
            response.put("payload", error);
            sendToDevice(fromDeviceId, response);
            return;
        }

        // 鉴权类型分类：动态验证码(CODE) / 固定密码(PASSWORD) / 免密(NONE)。
        // 服务器只做分类与转发，真实校验在被控端完成（服务器不保存任何密钥）。
        String authType = message.getAuthType();
        if (authType != null && !authType.trim().isEmpty()) {
            if ("CODE".equalsIgnoreCase(authType)) {
                logger.info("连接请求 {} -> {} 使用【动态验证码】鉴权", fromDeviceId, toDeviceId);
            } else if ("PASSWORD".equalsIgnoreCase(authType)) {
                logger.info("连接请求 {} -> {} 使用【固定密码】鉴权", fromDeviceId, toDeviceId);
            } else if ("NONE".equalsIgnoreCase(authType)) {
                // 显式标记为免密，便于被控端识别
                message.setAuthType("NONE");
                logger.info("连接请求 {} -> {} 使用【免密连接】鉴权（被控端手动确认）", fromDeviceId, toDeviceId);
            } else {
                logger.warn("连接请求 {} -> {} 携带未知鉴权类型 authType={}", fromDeviceId, toDeviceId, authType);
            }
        } else {
            // 未携带 authType 也视为免密连接（向后兼容），规范化为 NONE
            message.setAuthType("NONE");
            logger.info("连接请求 {} -> {} 未携带鉴权，按【免密连接】处理（被控端手动确认）", fromDeviceId, toDeviceId);
        }

        // 2. 去重：短时间内重复 OFFER 直接忽略，避免被控端反复弹窗
        if (connectionRequestManager.isDuplicateOffer(fromDeviceId, toDeviceId)) {
            logger.info("Duplicate connection request {} -> {} ignored", fromDeviceId, toDeviceId);
            return;
        }

        // 3. 目标不在线：立即回送 TARGET_OFFLINE，且不登记待确认（避免等待超时）
        if (!sessionManager.isDeviceOnline(toDeviceId)) {
            logger.warn("Target device {} is offline, cannot deliver connection request", toDeviceId);
            notifySenderTargetOffline(message, toDeviceId);
            return;
        }

        // 4. 登记待确认并转发 OFFER 给被控端
        connectionRequestManager.registerPendingRequest(fromDeviceId, toDeviceId);
        forwardMessage(message, false);
    }

    private void forwardMessage(SignalMessage message, boolean notifyOffline) {
        String toDeviceId = message.getToDeviceId();
        if (toDeviceId == null) {
            logger.warn("Cannot forward message: toDeviceId is null");
            return;
        }

        WebSocketSession targetSession = sessionManager.getSession(toDeviceId);
        if (targetSession == null || !targetSession.isOpen()) {
            logger.warn("Target device {} is not online", toDeviceId);
            // 仅 OFFER / ANSWER 在目标不在线时回送 TARGET_OFFLINE，其余类型静默丢弃
            if (notifyOffline) {
                notifySenderTargetOffline(message, toDeviceId);
            }
            return;
        }

        try {
            String jsonMessage = objectMapper.writeValueAsString(message);
            targetSession.sendMessage(new TextMessage(jsonMessage));
            logger.debug("Forwarded {} from {} to {}", message.getType(), message.getFromDeviceId(), toDeviceId);
        } catch (IOException e) {
            logger.error("Error forwarding message to {}: {}", toDeviceId, e.getMessage());
        }
    }

    /**
     * 目标被控端不在线时，向发送方（主控端）回送 TARGET_OFFLINE 提醒。
     */
    private void notifySenderTargetOffline(SignalMessage message, String offlineDeviceId) {
        String fromDeviceId = message.getFromDeviceId();
        if (fromDeviceId == null) {
            logger.warn("Cannot notify offline state: fromDeviceId is null");
            return;
        }

        WebSocketSession senderSession = sessionManager.getSession(fromDeviceId);
        if (senderSession == null || !senderSession.isOpen()) {
            logger.warn("Sender {} session not found, cannot notify target offline", fromDeviceId);
            return;
        }

        Map<String, Object> response = new HashMap<>();
        response.put("type", "TARGET_OFFLINE");
        response.put("toDeviceId", offlineDeviceId);
        response.put("payload", "目标被控端（" + offlineDeviceId + "）不在线，请确认设备已开启并连接到信令服务器");

        sendToSession(senderSession, response);
        logger.info("Notified sender {} that target {} is offline", fromDeviceId, offlineDeviceId);
    }

    private void sendToSession(WebSocketSession session, Object data) {
        try {
            String json = objectMapper.writeValueAsString(data);
            session.sendMessage(new TextMessage(json));
        } catch (IOException e) {
            logger.error("Error sending message to session {}: {}", session.getId(), e.getMessage());
        }
    }
}
