package com.cmvr.device.service;

import com.cmvr.common.exception.GlobalException;
import com.cmvr.device.domain.DeDeviceTerminalConfig;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 巡检报警监听状态服务。
 *
 * <p>监听条件仍然按边缘端IP和报警类型匹配，同时保存工作流执行上下文，
 * 使异步上报的PPE事件能够归属到具体巡检任务、检测项和节点。</p>
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class InspectionAlertListenService
{
    private static final Set<String> SUPPORTED_EVENT_TYPES;

    static
    {
        Set<String> values = new LinkedHashSet<>();
        values.add("No-Glove");
        values.add("No-Helmet");
        SUPPORTED_EVENT_TYPES = Collections.unmodifiableSet(values);
    }

    private final IDeDeviceTerminalConfigService terminalConfigService;

    /** key=grpcIp，value=当前IP下的工作流监听订阅。 */
    private final ConcurrentMap<String, ConcurrentMap<String, ListenSubscription>> subscriptions =
            new ConcurrentHashMap<>();

    /** 兼容原有调用方式，创建一个不绑定工作流的监听。 */
    public ListenState startListen(String terminalId, Collection<String> eventTypes)
    {
        return startListen(terminalId, eventTypes, null, null, null, null, null);
    }

    public ListenState startListen(String terminalId, Collection<String> eventTypes,
                                   String flowInstanceId, String taskId, String itemId,
                                   String nodeId, String listenerKey)
    {
        String grpcIp = resolveTerminalIp(terminalId);
        Set<String> normalizedTypes = normalizeEventTypes(eventTypes, true);
        ConcurrentMap<String, ListenSubscription> target =
                subscriptions.computeIfAbsent(grpcIp, key -> new ConcurrentHashMap<>());

        for (String eventType : normalizedTypes)
        {
            ListenSubscription subscription = new ListenSubscription();
            subscription.setSubscriptionId(buildSubscriptionId(flowInstanceId, itemId, listenerKey, eventType));
            subscription.setTerminalId(terminalId);
            subscription.setGrpcIp(grpcIp);
            subscription.setEventType(eventType);
            subscription.setFlowInstanceId(flowInstanceId);
            subscription.setTaskId(taskId);
            subscription.setItemId(itemId);
            subscription.setNodeId(nodeId);
            subscription.setListenerKey(StringUtils.trimToNull(listenerKey));
            subscription.setStartedAt(System.currentTimeMillis());
            target.put(subscription.getSubscriptionId(), subscription);
        }

        log.info("开始监听巡检报警，terminalId={}，grpcIp={}，flowInstanceId={}，itemId={}，eventTypes={}",
                terminalId, grpcIp, flowInstanceId, itemId, normalizedTypes);
        return new ListenState(terminalId, grpcIp, currentTypes(target));
    }

    /** 兼容原有调用方式，移除该IP下指定类型的全部监听。 */
    public ListenState stopListen(String terminalId, Collection<String> eventTypes)
    {
        return stopListen(terminalId, eventTypes, null, null, null);
    }

    public ListenState stopListen(String terminalId, Collection<String> eventTypes,
                                  String flowInstanceId, String itemId, String listenerKey)
    {
        String grpcIp = resolveTerminalIp(terminalId);
        Set<String> normalizedTypes = normalizeEventTypes(eventTypes, true);
        ConcurrentMap<String, ListenSubscription> current = subscriptions.get(grpcIp);
        if (current == null || current.isEmpty())
        {
            log.warn("结束监听巡检报警时未找到IP监听状态，terminalId={}，grpcIp={}，eventTypes={}",
                    terminalId, grpcIp, normalizedTypes);
            return new ListenState(terminalId, grpcIp, Collections.emptyList());
        }

        for (String eventType : normalizedTypes)
        {
            int removed = removeMatching(current, eventType, flowInstanceId, itemId, listenerKey);
            if (removed == 0)
            {
                log.warn("结束监听巡检报警时未找到匹配订阅，terminalId={}，grpcIp={}，flowInstanceId={}，itemId={}，eventType={}",
                        terminalId, grpcIp, flowInstanceId, itemId, eventType);
            }
        }
        if (current.isEmpty())
        {
            subscriptions.remove(grpcIp, current);
        }

        List<String> remainingTypes = currentTypes(current);
        log.info("结束监听巡检报警，terminalId={}，grpcIp={}，flowInstanceId={}，itemId={}，remainingTypes={}",
                terminalId, grpcIp, flowInstanceId, itemId, remainingTypes);
        return new ListenState(terminalId, grpcIp, remainingTypes);
    }

    /** 返回命中IP和任一标签的全部活动订阅。 */
    public List<ListenSubscription> matchSubscriptions(String grpcIp, Collection<String> labels)
    {
        String normalizedIp = StringUtils.trimToNull(grpcIp);
        if (normalizedIp == null)
        {
            log.warn("忽略巡检报警：grpc_ip为空，labels={}", labels);
            return Collections.emptyList();
        }
        ConcurrentMap<String, ListenSubscription> current = subscriptions.get(normalizedIp);
        if (current == null || current.isEmpty())
        {
            log.info("忽略巡检报警：未开启该IP的报警监听，grpcIp={}，labels={}", normalizedIp, labels);
            return Collections.emptyList();
        }

        Set<String> alertTypes = normalizeEventTypes(labels, false);
        List<ListenSubscription> matched = new ArrayList<>();
        for (ListenSubscription subscription : current.values())
        {
            if (alertTypes.contains(subscription.getEventType()))
            {
                matched.add(subscription.copy());
            }
        }
        if (matched.isEmpty())
        {
            log.info("忽略巡检报警：事件类型未命中监听条件，grpcIp={}，labels={}，listeningTypes={}",
                    normalizedIp, labels, currentTypes(current));
        }
        return matched;
    }

    public boolean shouldStore(String grpcIp, Collection<String> labels)
    {
        return !matchSubscriptions(grpcIp, labels).isEmpty();
    }

    /** 任务完成、失败或终止时清理该流程的全部监听。 */
    public void clearByFlowInstanceId(String flowInstanceId)
    {
        String normalized = StringUtils.trimToNull(flowInstanceId);
        if (normalized == null)
        {
            return;
        }
        int removed = 0;
        for (ConcurrentMap.Entry<String, ConcurrentMap<String, ListenSubscription>> ipEntry : subscriptions.entrySet())
        {
            ConcurrentMap<String, ListenSubscription> current = ipEntry.getValue();
            for (ConcurrentMap.Entry<String, ListenSubscription> entry : current.entrySet())
            {
                if (normalized.equals(entry.getValue().getFlowInstanceId()) && current.remove(entry.getKey(), entry.getValue()))
                {
                    removed++;
                }
            }
            if (current.isEmpty())
            {
                subscriptions.remove(ipEntry.getKey(), current);
            }
        }
        if (removed > 0)
        {
            log.info("巡检任务结束，清理报警监听，flowInstanceId={}，removed={}", normalized, removed);
        }
    }

    public ListenState getListenState(String terminalId)
    {
        String grpcIp = resolveTerminalIp(terminalId);
        return new ListenState(terminalId, grpcIp, currentTypes(subscriptions.get(grpcIp)));
    }

    private int removeMatching(ConcurrentMap<String, ListenSubscription> current, String eventType,
                               String flowInstanceId, String itemId, String listenerKey)
    {
        int removed = 0;
        for (ConcurrentMap.Entry<String, ListenSubscription> entry : current.entrySet())
        {
            ListenSubscription subscription = entry.getValue();
            boolean legacyRemove = StringUtils.isBlank(flowInstanceId);
            boolean contextMatches = StringUtils.equals(flowInstanceId, subscription.getFlowInstanceId())
                    && (StringUtils.isBlank(itemId) || StringUtils.equals(itemId, subscription.getItemId()))
                    && (StringUtils.isBlank(listenerKey) || StringUtils.equals(listenerKey, subscription.getListenerKey()));
            if (eventType.equals(subscription.getEventType()) && (legacyRemove || contextMatches)
                    && current.remove(entry.getKey(), subscription))
            {
                removed++;
            }
        }
        return removed;
    }

    private String buildSubscriptionId(String flowInstanceId, String itemId, String listenerKey, String eventType)
    {
        String flow = StringUtils.defaultIfBlank(flowInstanceId, "legacy");
        String item = StringUtils.defaultIfBlank(itemId, "all");
        String key = StringUtils.defaultIfBlank(listenerKey, "default");
        return flow + "|" + item + "|" + key + "|" + eventType;
    }

    private String resolveTerminalIp(String terminalId)
    {
        String normalizedTerminalId = StringUtils.trimToNull(terminalId);
        if (normalizedTerminalId == null)
        {
            throw new GlobalException("terminalId不能为空");
        }
        DeDeviceTerminalConfig terminalConfig =
                terminalConfigService.selectDeDeviceTerminalConfigById(normalizedTerminalId);
        if (terminalConfig == null)
        {
            throw new GlobalException("未找到终端配置，terminalId=" + normalizedTerminalId);
        }
        String grpcIp = StringUtils.trimToNull(terminalConfig.getHost());
        if (grpcIp == null)
        {
            throw new GlobalException("终端配置host不能为空，terminalId=" + normalizedTerminalId);
        }
        return grpcIp;
    }

    private Set<String> normalizeEventTypes(Collection<String> eventTypes, boolean failOnEmpty)
    {
        Set<String> result = new LinkedHashSet<>();
        if (eventTypes != null)
        {
            for (String eventType : eventTypes)
            {
                String normalized = normalizeEventType(eventType);
                if (normalized != null)
                {
                    result.add(normalized);
                }
            }
        }
        if (failOnEmpty && result.isEmpty())
        {
            throw new GlobalException("eventTypes不能为空，当前支持No-Glove、No-Helmet");
        }
        return result;
    }

    private String normalizeEventType(String eventType)
    {
        String value = StringUtils.trimToNull(eventType);
        if (value == null)
        {
            return null;
        }
        for (String supported : SUPPORTED_EVENT_TYPES)
        {
            if (supported.equalsIgnoreCase(value))
            {
                return supported;
            }
        }
        log.warn("忽略不支持的巡检报警类型，eventType={}，supported={}", value, SUPPORTED_EVENT_TYPES);
        return null;
    }

    private List<String> currentTypes(ConcurrentMap<String, ListenSubscription> current)
    {
        if (current == null || current.isEmpty())
        {
            return Collections.emptyList();
        }
        Set<String> values = new LinkedHashSet<>();
        for (ListenSubscription subscription : current.values())
        {
            values.add(subscription.getEventType());
        }
        return new ArrayList<>(values);
    }

    @Data
    public static class ListenState
    {
        private final String terminalId;
        private final String grpcIp;
        private final List<String> eventTypes;
    }

    @Data
    public static class ListenSubscription
    {
        /** flowInstanceId、itemId、listenerKey、eventType组成的订阅唯一标识。 */
        private String subscriptionId;
        /** 工作流节点配置的终端ID。 */
        private String terminalId;
        /** 由终端配置解析出的gRPC IP，也是PPE事件匹配IP。 */
        private String grpcIp;
        /** 当前订阅监听的PPE事件类型。 */
        private String eventType;
        /** 工作流运行实例ID，用于关联巡检任务和结束时清理。 */
        private String flowInstanceId;
        /** 工作流编排任务ID。 */
        private String taskId;
        /** 当前巡检检测项ID。 */
        private String itemId;
        /** 启动监听的工作流节点ID。 */
        private String nodeId;
        /** 同一检测项存在多个监听器时使用的可选业务标识。 */
        private String listenerKey;
        /** 开始监听时间戳，单位毫秒。 */
        private long startedAt;

        private ListenSubscription copy()
        {
            ListenSubscription copy = new ListenSubscription();
            copy.subscriptionId = subscriptionId;
            copy.terminalId = terminalId;
            copy.grpcIp = grpcIp;
            copy.eventType = eventType;
            copy.flowInstanceId = flowInstanceId;
            copy.taskId = taskId;
            copy.itemId = itemId;
            copy.nodeId = nodeId;
            copy.listenerKey = listenerKey;
            copy.startedAt = startedAt;
            return copy;
        }
    }
}
