package com.snack.server.module.chat.websocket;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.snack.server.module.chat.dto.req.ChatAckReq;
import com.snack.server.module.chat.dto.req.ChatSendMessageReq;
import com.snack.server.module.chat.entity.ChatMessage;
import com.snack.server.module.chat.entity.ChatSession;
import com.snack.server.module.chat.service.ChatMessageService;
import com.snack.server.module.chat.service.ChatSessionService;
import com.snack.server.module.chat.vo.ChatMessageVO;
import com.snack.server.module.chat.vo.ChatSessionVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 客服聊天 - 原生 WebSocket 处理器（JSON 文本协议）
 *
 * 客户端 → 服务端：
 *   {"type":"send","sessionId":1,"content":"你好","clientMsgId":"x"}   发送消息
 *   {"type":"ack","sessionId":1,"lastSeq":12}                           已读确认
 *   {"type":"close","sessionId":1}                                      关闭会话
 *   {"type":"accept","sessionId":1}                                     客服接管
 *   {"type":"ping"}                                                     心跳
 *
 * 服务端 → 客户端：
 *   {"type":"message","message":{...ChatMessageVO}}                     会话消息（含发送方回执）
 *   {"type":"event","eventType":"session_accepted","message":"...","data":{...}}  个人事件
 *   {"type":"admin_event","eventType":"session_message","message":"...","data":{...}} 客服广播
 *   {"type":"error","message":"..."}                                    错误
 *   pong                                                              心跳应答
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ChatWebSocketHandler extends TextWebSocketHandler {

    private final ChatMessageService chatMessageService;
    private final ChatSessionService chatSessionService;

    /** 在线连接表：key = "user:{id}" / "admin:{id}"，同身份只保留最新连接 */
    private final Map<String, WebSocketSession> connections = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        Object loginId = session.getAttributes().get("loginId");
        Object loginType = session.getAttributes().get("loginType");
        if (loginId == null || loginType == null) {
            try {
                session.close(CloseStatus.POLICY_VIOLATION);
            } catch (IOException e) {
                log.warn("[ws] 关闭异常连接失败: {}", e.getMessage());
            }
            return;
        }
        String key = loginType + ":" + loginId;
        connections.put(key, session);
        log.info("[ws] 连接建立 key={}", key);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage textMessage) {
        String text = textMessage.getPayload();
        // 心跳（原生 ping/pong）
        if ("ping".equalsIgnoreCase(text)) {
            sendTextSafe(session, "pong");
            return;
        }

        JSONObject json;
        try {
            json = JSONUtil.parseObj(text);
        } catch (Exception e) {
            sendError(session, "消息格式错误");
            return;
        }

        String type = json.getStr("type");
        String key = principalKey(session);
        if (key == null) {
            sendError(session, "未认证连接");
            return;
        }
        String[] parts = key.split(":");
        boolean isAdmin = "admin".equals(parts[0]);
        Long operatorId = Long.parseLong(parts[1]);

        try {
            switch (type == null ? "" : type) {
                case "ping" -> sendJson(session, payload("pong", null, null, null));
                case "send" -> handleSend(session, key, isAdmin, operatorId, json);
                case "ack" -> handleAck(isAdmin, json);
                case "close" -> handleClose(isAdmin, operatorId, json);
                case "accept" -> handleAccept(key, operatorId, json);
                default -> sendError(session, "未知消息类型");
            }
        } catch (Exception e) {
            log.error("[ws] 处理消息失败 type={} msg={}", type, e.getMessage());
            sendError(session, e.getMessage());
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        Object loginId = session.getAttributes().get("loginId");
        Object loginType = session.getAttributes().get("loginType");
        if (loginId != null && loginType != null) {
            String key = loginType + ":" + loginId;
            connections.remove(key, session);
            log.info("[ws] 连接断开 key={}", key);
        }
    }

    // ==================== 消息处理 ====================

    private void handleSend(WebSocketSession session, String senderKey, boolean isAdmin,
                            Long senderId, JSONObject json) {
        ChatSendMessageReq req = new ChatSendMessageReq();
        req.setSessionId(json.getLong("sessionId"));
        req.setType("text");
        req.setContent(json.getStr("content"));
        req.setClientMsgId(json.getStr("clientMsgId"));
        if (req.getSessionId() == null || StrUtil.isBlank(req.getContent())) {
            sendError(session, "参数不完整");
            return;
        }

        int senderType = isAdmin ? 1 : 0;
        ChatMessage message = chatMessageService.sendMessage(req, senderId, senderType);
        ChatMessageVO vo = chatMessageService.toVO(message);
        vo.setClientMsgId(req.getClientMsgId());

        ChatSession chatSession = chatSessionService.getById(req.getSessionId());
        if (chatSession == null) {
            sendError(session, "会话不存在");
            return;
        }
        // 归属校验：用户只能发自己的会话；客服必须是接待人（或会话未分配）
        if (senderType == 0 && !chatSession.getUserId().equals(senderId)) {
            sendError(session, "无权向该会话发送消息");
            return;
        }
        if (senderType == 1 && chatSession.getAdminId() != null
                && !chatSession.getAdminId().equals(senderId)) {
            sendError(session, "该会话已分配给其他客服");
            return;
        }

        Map<String, Object> payload = messagePayload(vo);
        // 1. 回执给发送方（前端按 clientMsgId 替换乐观消息）
        sendJsonSafe(session, payload);
        // 2. 推送给接收方
        if (senderType == 0) {
            if (chatSession.getAdminId() != null) {
                sendJsonSafe(connections.get("admin:" + chatSession.getAdminId()), payload);
            } else {
                broadcastAdmin(adminEventPayload("session_message", "有新消息待处理",
                        Map.of("sessionId", chatSession.getId(), "sessionNo", chatSession.getSessionNo())));
            }
        } else if (senderType == 1) {
            sendJsonSafe(connections.get("user:" + chatSession.getUserId()), payload);
        } else {
            sendJsonSafe(connections.get("user:" + chatSession.getUserId()), payload);
            if (chatSession.getAdminId() != null) {
                sendJsonSafe(connections.get("admin:" + chatSession.getAdminId()), payload);
            }
        }
    }

    private void handleAck(boolean isAdmin, JSONObject json) {
        Long sessionId = json.getLong("sessionId");
        Long lastSeq = json.getLong("lastSeq");
        if (sessionId == null || lastSeq == null) return;
        ChatAckReq req = new ChatAckReq();
        req.setSessionId(sessionId);
        req.setLastSeq(lastSeq);
        chatMessageService.handleAck(req, isAdmin);
    }

    private void handleClose(boolean isAdmin, Long operatorId, JSONObject json) {
        Long sessionId = json.getLong("sessionId");
        if (sessionId == null) return;
        chatSessionService.closeSession(sessionId, operatorId, isAdmin);
        ChatSession session = chatSessionService.getById(sessionId);
        if (session == null) return;

        Map<String, Object> evt = eventPayload("session_closed", "会话已关闭",
                Map.of("sessionId", session.getId()));
        sendJsonSafe(connections.get("user:" + session.getUserId()), evt);
        if (session.getAdminId() != null) {
            sendJsonSafe(connections.get("admin:" + session.getAdminId()), evt);
        }
        broadcastAdmin(adminEventPayload("session_closed", "会话已关闭",
                Map.of("sessionId", session.getId())));
    }

    private void handleAccept(String key, Long adminId, JSONObject json) {
        if (!key.startsWith("admin:")) return;
        Long sessionId = json.getLong("sessionId");
        if (sessionId == null) return;

        ChatSession session = chatSessionService.acceptByAdmin(sessionId, adminId, "");
        ChatSessionVO vo = chatSessionService.toVO(session);

        // 通知用户：客服已接入
        sendJsonSafe(connections.get("user:" + session.getUserId()),
                eventPayload("session_accepted", "客服已接入",
                        Map.of("sessionId", session.getId(), "adminName", vo.getAdminName())));
        // 通知其他客服刷新待处理列表
        broadcastAdmin(adminEventPayload("session_accepted", "会话已被接管",
                Map.of("sessionId", session.getId())));
    }

    // ==================== 推送工具 ====================

    private String principalKey(WebSocketSession session) {
        Object loginId = session.getAttributes().get("loginId");
        Object loginType = session.getAttributes().get("loginType");
        if (loginId == null || loginType == null) return null;
        return loginType + ":" + loginId;
    }

    private void broadcastAdmin(Map<String, Object> payload) {
        connections.forEach((key, session) -> {
            if (key.startsWith("admin:")) {
                sendJsonSafe(session, payload);
            }
        });
    }

    private Map<String, Object> messagePayload(ChatMessageVO vo) {
        Map<String, Object> p = new LinkedHashMap<>();
        p.put("type", "message");
        p.put("message", vo);
        return p;
    }

    private Map<String, Object> eventPayload(String eventType, String message, Map<String, Object> data) {
        return eventPayloadOf("event", eventType, message, data);
    }

    private Map<String, Object> adminEventPayload(String eventType, String message, Map<String, Object> data) {
        return eventPayloadOf("admin_event", eventType, message, data);
    }

    private Map<String, Object> eventPayloadOf(String type, String eventType,
                                               String message, Map<String, Object> data) {
        Map<String, Object> p = new LinkedHashMap<>();
        p.put("type", type);
        p.put("eventType", eventType);
        p.put("message", message);
        p.put("data", data);
        return p;
    }

    private Map<String, Object> payload(String type, String message, Map<String, Object> data, Object extra) {
        Map<String, Object> p = new LinkedHashMap<>();
        p.put("type", type);
        if (message != null) p.put("message", message);
        if (data != null) p.put("data", data);
        if (extra != null) p.put("extra", extra);
        return p;
    }

    private void sendJson(WebSocketSession session, Object payload) {
        sendTextSafe(session, JSONUtil.toJsonStr(payload));
    }

    private void sendJsonSafe(WebSocketSession session, Object payload) {
        if (session == null || !session.isOpen()) return;
        sendTextSafe(session, JSONUtil.toJsonStr(payload));
    }

    private void sendTextSafe(WebSocketSession session, String text) {
        if (session == null || !session.isOpen()) return;
        try {
            synchronized (session) {
                session.sendMessage(new TextMessage(text));
            }
        } catch (IOException e) {
            log.warn("[ws] 推送失败: {}", e.getMessage());
        }
    }

    private void sendError(WebSocketSession session, String message) {
        Map<String, Object> p = new LinkedHashMap<>();
        p.put("type", "error");
        p.put("message", message);
        sendJsonSafe(session, p);
    }
}
