package com.zcloud.zcGbsServicer.service;

import cn.hutool.core.util.StrUtil;
import com.jjb.saas.framework.auth.utils.AuthContext;
import com.zcloud.zcGbsServicer.persistence.dataobject.FireAlarmMeetingUserDO;
import com.zcloud.zcGbsServicer.persistence.dataobject.FireBrigadeDO;
import com.zcloud.zcGbsServicer.persistence.dataobject.FireBrigadeUserDO;
import com.zcloud.zcGbsServicer.persistence.repository.FireAlarmMeetingUserRepository;
import com.zcloud.zcGbsServicer.persistence.repository.FireBrigadeUserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class FireBrigadeOnlineStatusService {

    private static final int USER_ONLINE_IN_MEETING = 1;
    private static final long REFRESH_DEBOUNCE_MS = 5000L;

    private final FireBrigadeUserRepository fireBrigadeUserRepository;
    private final FireAlarmMeetingUserRepository fireAlarmMeetingUserRepository;
    private final HstVideoPlatformClient hstVideoPlatformClient;

    private final ConcurrentHashMap<String, OnlineCacheEntry> onlineCache = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Long> refreshGuard = new ConcurrentHashMap<>();

    @Value("${fire.brigade.online-cache-ttl-seconds:20}")
    private long onlineCacheTtlSeconds;

    @Value("${fire.brigade.online-cache-fail-ttl-seconds:10}")
    private long onlineCacheFailTtlSeconds;

    /**
     * 列表展示用：优先读有效缓存，其次读过期缓存，最后默认未在线；不调用第三方。
     */
    public Map<String, Boolean> getOnlineMapForList(Collection<String> brigadeBizIds, Long tenantId) {
        if (brigadeBizIds == null || brigadeBizIds.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, Boolean> result = new LinkedHashMap<>();
        for (String brigadeBizId : brigadeBizIds) {
            if (StrUtil.isBlank(brigadeBizId)) {
                continue;
            }
            String normalizedId = brigadeBizId.trim();
            OnlineCacheEntry cacheEntry = onlineCache.get(buildCacheKey(normalizedId, tenantId));
            result.put(normalizedId, cacheEntry == null ? Boolean.FALSE : cacheEntry.online);
        }
        return result;
    }

    /**
     * 列表请求后异步刷新缓存，不阻塞接口响应。
     */
    public void scheduleRefreshOnlineMap(List<FireBrigadeDO> brigades) {
        if (brigades == null || brigades.isEmpty()) {
            return;
        }
        Long tenantId = resolveTenantId(brigades);
        if (isCacheValidForAll(brigades, tenantId)) {
            return;
        }
        List<FireBrigadeDO> snapshot = new ArrayList<>(brigades);
        String refreshKey = buildRefreshKey(snapshot, tenantId);
        long now = System.currentTimeMillis();
        Long lastRefreshAt = refreshGuard.get(refreshKey);
        if (lastRefreshAt != null && now - lastRefreshAt < REFRESH_DEBOUNCE_MS) {
            return;
        }
        refreshGuard.put(refreshKey, now);
        CompletableFuture.runAsync(() -> {
            try {
                refreshOnlineMap(snapshot);
            }
            catch (Exception ex) {
                log.warn("异步刷新消防队伍在线状态失败", ex);
            }
        });
    }

    /**
     * 同步调用第三方刷新在线状态并写入缓存。
     */
    public Map<String, Boolean> refreshOnlineMap(List<FireBrigadeDO> brigades) {
        if (brigades == null || brigades.isEmpty()) {
            return Collections.emptyMap();
        }
        Set<String> brigadeBizIds = brigades.stream()
                .map(FireBrigadeDO::getFireBrigadeId)
                .filter(StrUtil::isNotBlank)
                .map(String::trim)
                .collect(Collectors.toCollection(LinkedHashSet::new));
        if (brigadeBizIds.isEmpty()) {
            return Collections.emptyMap();
        }

        Long tenantId = resolveTenantId(brigades);
        long now = System.currentTimeMillis();
        OnlineComputeResult computeResult = computeOnlineMap(brigadeBizIds, tenantId);
        long ttlMs = computeResult.degraded
                ? onlineCacheFailTtlSeconds * 1000L
                : onlineCacheTtlSeconds * 1000L;
        long expireAt = now + ttlMs;
        for (Map.Entry<String, Boolean> entry : computeResult.onlineMap.entrySet()) {
            onlineCache.put(buildCacheKey(entry.getKey(), tenantId), new OnlineCacheEntry(entry.getValue(), expireAt));
        }
        return computeResult.onlineMap;
    }

    private OnlineComputeResult computeOnlineMap(Set<String> brigadeBizIds, Long tenantId) {
        List<FireBrigadeUserDO> brigadeUsers = fireBrigadeUserRepository.listByFireBrigadeIds(brigadeBizIds, tenantId);
        Map<String, List<FireBrigadeUserDO>> brigadeUserMap = brigadeUsers.stream()
                .filter(item -> item != null && StrUtil.isNotBlank(item.getFireBrigadeId()))
                .collect(Collectors.groupingBy(FireBrigadeUserDO::getFireBrigadeId, LinkedHashMap::new, Collectors.toList()));
        if (brigadeUserMap.isEmpty()) {
            return new OnlineComputeResult(buildOfflineMap(brigadeBizIds), false);
        }

        Set<Long> localUserIds = brigadeUserMap.values().stream()
                .flatMap(List::stream)
                .map(FireBrigadeUserDO::getUserId)
                .filter(java.util.Objects::nonNull)
                .collect(Collectors.toCollection(LinkedHashSet::new));
        if (localUserIds.isEmpty()) {
            return new OnlineComputeResult(buildOfflineMap(brigadeBizIds), false);
        }

        Map<Long, String> meetingAccountMap = fireAlarmMeetingUserRepository.listByLocalUserIds(localUserIds, tenantId).stream()
                .filter(item -> item != null && item.getLocalUserId() != null)
                .filter(item -> StrUtil.isNotBlank(item.getThirdUserName()))
                .collect(Collectors.toMap(FireAlarmMeetingUserDO::getLocalUserId,
                        item -> item.getThirdUserName().trim(),
                        (left, right) -> left,
                        LinkedHashMap::new));
        if (meetingAccountMap.isEmpty()) {
            return new OnlineComputeResult(buildOfflineMap(brigadeBizIds), false);
        }

        OnlineQueryResult queryResult = queryOnlineMeetingAccounts(new LinkedHashSet<>(meetingAccountMap.values()));
        Map<String, Boolean> result = buildOfflineMap(brigadeBizIds);
        if (!queryResult.onlineMeetingAccounts.isEmpty()) {
            for (Map.Entry<String, List<FireBrigadeUserDO>> entry : brigadeUserMap.entrySet()) {
                boolean online = entry.getValue().stream()
                        .map(FireBrigadeUserDO::getUserId)
                        .filter(java.util.Objects::nonNull)
                        .map(meetingAccountMap::get)
                        .filter(StrUtil::isNotBlank)
                        .anyMatch(queryResult.onlineMeetingAccounts::contains);
                result.put(entry.getKey(), online);
            }
        }
        return new OnlineComputeResult(result, queryResult.degraded);
    }

    private OnlineQueryResult queryOnlineMeetingAccounts(Set<String> meetingAccounts) {
        if (meetingAccounts == null || meetingAccounts.isEmpty()) {
            return new OnlineQueryResult(Collections.emptySet(), false);
        }
        Set<String> result = new LinkedHashSet<>();
        boolean authInvalid = false;
        boolean degraded = false;
        for (String meetingAccount : meetingAccounts) {
            if (authInvalid) {
                degraded = true;
                break;
            }
            try {
                List<Map<String, Object>> records = hstVideoPlatformClient.userOnline(meetingAccount);
                if (isInMeeting(records)) {
                    result.add(meetingAccount);
                }
            }
            catch (Exception ex) {
                degraded = true;
                if (isInvalidAuth(ex)) {
                    authInvalid = true;
                    log.warn("查询视频平台用户在线状态失败，视频平台 HTTP API 鉴权无效，请检查 key/secret 是否为后台 HTTP API 分配的凭证，当前账号={}",
                            meetingAccount, ex);
                    continue;
                }
                log.warn("查询视频平台用户在线状态失败，userName={}", meetingAccount, ex);
            }
        }
        return new OnlineQueryResult(result, degraded);
    }

    private Long resolveTenantId(List<FireBrigadeDO> brigades) {
        if (brigades != null) {
            for (FireBrigadeDO brigade : brigades) {
                if (brigade != null && brigade.getTenantId() != null) {
                    return brigade.getTenantId();
                }
            }
        }
        return AuthContext.getTenantId();
    }

    private boolean isCacheValidForAll(List<FireBrigadeDO> brigades, Long tenantId) {
        long now = System.currentTimeMillis();
        for (FireBrigadeDO brigade : brigades) {
            if (brigade == null || StrUtil.isBlank(brigade.getFireBrigadeId())) {
                continue;
            }
            OnlineCacheEntry cacheEntry = onlineCache.get(buildCacheKey(brigade.getFireBrigadeId().trim(), tenantId));
            if (cacheEntry == null || cacheEntry.expireAt <= now) {
                return false;
            }
        }
        return true;
    }

    private String buildCacheKey(String brigadeBizId, Long tenantId) {
        return (tenantId == null ? "0" : tenantId) + ":" + brigadeBizId;
    }

    private String buildRefreshKey(List<FireBrigadeDO> brigades, Long tenantId) {
        String ids = brigades.stream()
                .map(FireBrigadeDO::getFireBrigadeId)
                .filter(StrUtil::isNotBlank)
                .map(String::trim)
                .sorted()
                .collect(Collectors.joining(","));
        return (tenantId == null ? "0" : tenantId) + ":" + ids;
    }

    private boolean isInvalidAuth(Exception ex) {
        String message = ex == null ? null : ex.getMessage();
        return StrUtil.contains(message, "无效认证信息")
                || StrUtil.containsIgnoreCase(message, "1003");
    }

    private boolean isInMeeting(List<Map<String, Object>> records) {
        if (records == null || records.isEmpty()) {
            return false;
        }
        for (Map<String, Object> record : records) {
            if (record == null) {
                continue;
            }
            Integer online = asInteger(record.get("online"));
            Object roomId = record.get("roomId");
            if (hasRoomId(roomId)) {
                return true;
            }
            if (Integer.valueOf(USER_ONLINE_IN_MEETING).equals(online)) {
                return true;
            }
        }
        return false;
    }

    private boolean hasRoomId(Object roomId) {
        if (roomId == null) {
            return false;
        }
        return !(roomId instanceof String) || StrUtil.isNotBlank((String) roomId);
    }

    private Integer asInteger(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number) {
            return ((Number) value).intValue();
        }
        try {
            return Integer.valueOf(String.valueOf(value));
        }
        catch (Exception ex) {
            return null;
        }
    }

    private Map<String, Boolean> buildOfflineMap(Set<String> brigadeBizIds) {
        Map<String, Boolean> result = new LinkedHashMap<>();
        for (String brigadeBizId : brigadeBizIds) {
            result.put(brigadeBizId, Boolean.FALSE);
        }
        return result;
    }

    private static final class OnlineCacheEntry {
        private final boolean online;
        private final long expireAt;

        private OnlineCacheEntry(boolean online, long expireAt) {
            this.online = online;
            this.expireAt = expireAt;
        }
    }

    private static final class OnlineComputeResult {
        private final Map<String, Boolean> onlineMap;
        private final boolean degraded;

        private OnlineComputeResult(Map<String, Boolean> onlineMap, boolean degraded) {
            this.onlineMap = onlineMap;
            this.degraded = degraded;
        }
    }

    private static final class OnlineQueryResult {
        private final Set<String> onlineMeetingAccounts;
        private final boolean degraded;

        private OnlineQueryResult(Set<String> onlineMeetingAccounts, boolean degraded) {
            this.onlineMeetingAccounts = onlineMeetingAccounts;
            this.degraded = degraded;
        }
    }
}
