package com.fs.app.websocket.service;


import com.alibaba.fastjson.JSONObject;
import com.fs.app.websocket.bean.SendMsgVo;
import com.fs.common.core.domain.R;
import com.fs.common.core.redis.RedisCache;
import com.fs.common.exception.base.BaseException;
import com.fs.common.utils.StringUtils;
import com.fs.common.utils.spring.SpringUtils;
import com.fs.his.domain.FsUser;
import com.fs.his.service.IFsUserService;
import com.fs.live.domain.LiveData;
import com.fs.live.domain.LiveMsg;
import com.fs.live.domain.LiveWatchUser;
import com.fs.live.service.ILiveDataService;
import com.fs.live.service.ILiveMsgService;
import com.fs.live.service.ILiveService;
import com.fs.live.service.ILiveWatchUserService;
import com.fs.live.vo.LiveWatchUserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

@ServerEndpoint("/app/webSocket")
@Component
@Slf4j
public class WebSocketServer {

    // 直播间用户session
    private final static ConcurrentHashMap<Long, ConcurrentHashMap<Long, Session>> rooms = new ConcurrentHashMap<>();
    // 管理端连接
    private final static ConcurrentHashMap<Long, CopyOnWriteArrayList<Session>> adminRooms = new ConcurrentHashMap<>();
    private final RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
    private final ILiveMsgService liveMsgService = SpringUtils.getBean(ILiveMsgService.class);
    private final ILiveService liveService = SpringUtils.getBean(ILiveService.class);
    private final ILiveWatchUserService liveWatchUserService = SpringUtils.getBean(ILiveWatchUserService.class);
    private final IFsUserService fsUserService = SpringUtils.getBean(IFsUserService.class);
    private final ILiveDataService liveDataService = SpringUtils.getBean(ILiveDataService.class);
    // 直播间在线用户缓存
    //private static final ConcurrentHashMap<Long, Integer> liveOnlineUsers = new ConcurrentHashMap<>();

    private static final String USER_VISIT_KEY = "live:user:visit:";  // 用户访问标识用于判断是否是首次访问
    private static final String UNIQUE_VISITORS_KEY = "live:unique:visitors:";  //访客数
    private static final String UNIQUE_VIEWERS_KEY = "live:unique:viewers:";  //累计观看人数
    private static final String PAGE_VIEWS_KEY = "live:page:views:";  //浏览量
    private static final String TOTAL_VIEWS_KEY = "live:total:views:";  //累计观看人次
    private static final String MAX_ONLINE_USERS_KEY = "live:max:online:"; //最大在线人数
    private static final String ONLINE_USERS_KEY = "live:online:users:";  //当前在线人数
    //建立连接成功调用
    @OnOpen
    public void onOpen(Session session) {

        Map<String, Object> userProperties = session.getUserProperties();
        long liveId = (long) userProperties.get("liveId");
        long userId = (long) userProperties.get("userId");
        long userType = (long) userProperties.get("userType");

        if (liveService.getById(liveId) == null) {
            throw new BaseException("未找到直播间");
        }

        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
        List<Session> adminRoom = getAdminRoom(liveId);

        // 记录连接信息 管理员不记录
        if (userType == 0) {
            FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
            if (Objects.isNull(fsUser)) {
                throw new BaseException("用户信息错误");
            }

            liveWatchUserService.join(liveId, userId);
            room.put(userId, session);
            // 直播间浏览量 +1
            redisCache.increment(PAGE_VIEWS_KEY + liveId, 1);

            // 累计观看人次 +1
            redisCache.increment(TOTAL_VIEWS_KEY + liveId, 1);

            // 记录在线人数
            redisCache.increment(ONLINE_USERS_KEY + liveId, 1);
            Long currentOnline = redisCache.getCacheObject(ONLINE_USERS_KEY + liveId);
            //最大同时在线人数
            Long maxOnline = redisCache.getCacheObject(MAX_ONLINE_USERS_KEY + liveId);
            if (maxOnline == null || currentOnline > maxOnline) {
                redisCache.setCacheObject(MAX_ONLINE_USERS_KEY + liveId, currentOnline);
            }

            // 判断是否是该直播间的首次访客（独立访客统计）
            boolean isFirstVisit = redisCache.setIfAbsent(USER_VISIT_KEY + userId, 1, 1, TimeUnit.DAYS);
            if (isFirstVisit) {

                redisCache.increment(UNIQUE_VISITORS_KEY + liveId, 1);
            }

            // 判断是否是首次进入直播间的观众
            boolean isFirstViewer = redisCache.setIfAbsent(UNIQUE_VIEWERS_KEY + liveId + ":" + userId, 1, 1, TimeUnit.DAYS);
            if (isFirstViewer) {
                redisCache.increment(UNIQUE_VIEWERS_KEY + liveId, 1);
            }

            LiveWatchUserVO liveWatchUserVO = liveWatchUserService.selectWatchUserByLiveIdAndUserId(liveId, userId);

            SendMsgVo sendMsgVo = new SendMsgVo();
            sendMsgVo.setLiveId(liveId);
            sendMsgVo.setUserId(userId);
            sendMsgVo.setUserType(userType);
            sendMsgVo.setCmd("entry");
            sendMsgVo.setMsg("用户进入");
            sendMsgVo.setData(JSONObject.toJSONString(liveWatchUserVO));
            sendMsgVo.setNickName(fsUser.getNickName());
            sendMsgVo.setAvatar(fsUser.getAvatar());

            // 广播连接消息
            broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
        } else {
            adminRoom.add(session);
        }

        log.debug("加入webSocket liveId: {}, userId: {}, 直播间人数: {}", liveId, userId, room.size());
    }

    //关闭连接时调用
    @OnClose
    public void onClose(Session session) {
        Map<String, Object> userProperties = session.getUserProperties();

        long liveId = (long) userProperties.get("liveId");
        long userId = (long) userProperties.get("userId");
        long userType = (long) userProperties.get("userType");

        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
        List<Session> adminRoom = getAdminRoom(liveId);
        if (userType == 0) {
            FsUser fsUser = fsUserService.selectFsUserByUserId(userId);
            if (Objects.isNull(fsUser)) {
                throw new BaseException("用户信息错误");
            }

            liveWatchUserService.close(liveId, userId);
            room.remove(userId);

            if (room.isEmpty()) {
                rooms.remove(liveId);
            }

            LiveWatchUserVO liveWatchUserVO = liveWatchUserService.selectWatchUserByLiveIdAndUserId(liveId, userId);

            // 直播间在线人数 -1
            redisCache.increment(ONLINE_USERS_KEY + liveId, -1);
            SendMsgVo sendMsgVo = new SendMsgVo();
            sendMsgVo.setLiveId(liveId);
            sendMsgVo.setUserId(userId);
            sendMsgVo.setUserType(userType);
            sendMsgVo.setCmd("out");
            sendMsgVo.setMsg("用户离开");
            sendMsgVo.setData(JSONObject.toJSONString(liveWatchUserVO));
            sendMsgVo.setNickName(fsUser.getNickName());
            sendMsgVo.setAvatar(fsUser.getAvatar());

            // 广播离开消息
            broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", sendMsgVo)));
        } else {
            adminRoom.remove(session);
        }

        log.debug("断开webSocket liveId: {}, userId: {}, 直播间人数: {}", liveId, userId, room.size());
    }

    //收到客户端信息
    @OnMessage
    public void onMessage(Session session,String message) throws IOException {
        Map<String, Object> userProperties = session.getUserProperties();

        long liveId = (long) userProperties.get("liveId");
        long userType = (long) userProperties.get("userType");

        SendMsgVo msg = JSONObject.parseObject(message, SendMsgVo.class);
        if(msg.isOn()) return;
        try {
            switch (msg.getCmd()) {
                case "heartbeat":
                    sendMessage(session, JSONObject.toJSONString(R.ok().put("data", msg)));
                    break;
                case "sendMsg":
                    LiveMsg liveMsg = new LiveMsg();
                    liveMsg.setLiveId(msg.getLiveId());
                    liveMsg.setUserId(msg.getUserId());
                    liveMsg.setNickName(msg.getNickName());
                    liveMsg.setAvatar(msg.getAvatar());
                    liveMsg.setMsg(msg.getMsg());
                    liveMsg.setCreateTime(new Date());

                    if (userType == 0) {
                        LiveWatchUser liveWatchUser = liveWatchUserService.getByLiveIdAndUserId(msg.getLiveId(), msg.getUserId());
                        if(liveWatchUser.getMsgStatus() == 1){
                            sendMessage(session, JSONObject.toJSONString(R.error("你以被禁言")));
                            return;
                        }

                        liveMsgService.save(liveMsg);
                    }

                    msg.setOn(true);
                    msg.setData(JSONObject.toJSONString(liveMsg));

                    // 广播消息
                    broadcastMessage(liveId, JSONObject.toJSONString(R.ok().put("data", msg)));
                    break;
            }
        } catch (Exception e) {
            log.error("webSocket 消息处理失败 msg: {}", e.getMessage(), e);
        }
    }

    //错误时调用
    @OnError
    public void onError(Session session, Throwable throwable) {
        log.error("webSocKet连接错误 msg: {}", throwable.getMessage(), throwable);
    }

    /**
     * 获取房间
     * @param liveId 直播间ID
     * @return 容器
     */
    private ConcurrentHashMap<Long, Session> getRoom(Long liveId) {
        return rooms.computeIfAbsent(liveId, k -> new ConcurrentHashMap<>());
    }

    /**
     * 获取管理端房间
     * @param liveId  直播间ID
     * @return  容器
     */
    private List<Session> getAdminRoom(Long liveId) {
        return adminRooms.computeIfAbsent(liveId, k -> new CopyOnWriteArrayList<>());
    }

    //发送消息
    public void sendMessage(Session session, String message) throws IOException {
        session.getAsyncRemote().sendText(message);
    }

    /**
     * 广播消息
     * @param liveId   直播间ID
     * @param message  消息内容
     */
    public void broadcastMessage(Long liveId, String message) {
        ConcurrentHashMap<Long, Session> room = getRoom(liveId);
        List<Session> adminRoom = getAdminRoom(liveId);

        room.forEach((k, v) -> v.getAsyncRemote().sendText(message));
        adminRoom.forEach(v -> v.getAsyncRemote().sendText(message));
    }

    /**
     *定期将缓存的数据写入数据库
     */
    @Scheduled(fixedRate = 60000) // 每分钟执行一次
    public void syncLiveDataToDB() {
        List<Long> liveIds = liveDataService.getAllLiveIds(); // 获取所有正在直播的直播间ID
        for (Long liveId : liveIds) {
            LiveData liveData = liveDataService.selectLiveDataByLiveId(liveId);
            if (liveData == null) {
                continue; // 防止空指针异常
            }

            // 从 redis 获取数据，并提供默认值，避免 NPE
            liveData.setPageViews(
                    Optional.ofNullable(redisCache.incrementCacheValue(PAGE_VIEWS_KEY + liveId,0)).orElse(0L)
            );
            liveData.setTotalViews(
                    Optional.ofNullable(redisCache.incrementCacheValue(TOTAL_VIEWS_KEY + liveId,0)).orElse(0L)
            );
            liveData.setUniqueVisitors(
                    Optional.ofNullable(redisCache.getCacheSet(UNIQUE_VISITORS_KEY + liveId))
                            .map(Set::size)  // 获取集合大小
                            .map(Long::valueOf)  // 转换为 Long 类型
                            .orElse(0L)
            );
            liveData.setUniqueViewers(
                    Optional.ofNullable(redisCache.getCacheSet(UNIQUE_VIEWERS_KEY + liveId))
                            .map(Set::size)  // 获取集合大小
                            .map(Long::valueOf)  // 转换为 Long 类型
                            .orElse(0L)
            );
            liveData.setPeakConcurrentViewers(
                    Optional.ofNullable(redisCache.incrementCacheValue(MAX_ONLINE_USERS_KEY + liveId,0)).orElse(0L)
            );

            // 更新数据库
            liveDataService.updateLiveData(liveData);
        }
    }


}
