package com.lingniu.ingest.protocol.gb32960.codec.profile;

import com.lingniu.ingest.protocol.gb32960.codec.Gb32960ParserContext;
import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 配置驱动的 vendor 扩展选择器。按 {@link Gb32960Properties#getVendorExtensions()} 配置的
 * 顺序自上而下首匹（first-match-wins），命中的 entry 的 {@code name} 即返回值。
 *
 * <p>命中字段：{@code platformAccount} / {@code vin} / {@code vinPrefix}（任一命中即整条
 * entry 命中）。比较为 case-insensitive。
 *
 * <p>结果按 {@code (account, vin)} 缓存以避免每帧重复扫描；约定 cache key 不含 version
 * 因为版本不参与匹配。Cache 用 {@link Optional} 包裹以同时缓存 "no match"。
 */
public final class RuleBasedVendorExtensionSelector implements VendorExtensionSelector {

    private final List<CompiledRule> rules;
    private final ConcurrentHashMap<CacheKey, Optional<String>> cache = new ConcurrentHashMap<>();

    public RuleBasedVendorExtensionSelector(List<Gb32960Properties.VendorExtension> entries,
                                            Set<String> validNames) {
        List<CompiledRule> compiled = new ArrayList<>(entries.size());
        for (Gb32960Properties.VendorExtension e : entries) {
            if (e.getName() == null || e.getName().isBlank()) {
                throw new IllegalStateException("vendor extension entry missing 'name'");
            }
            if (!validNames.contains(e.getName())) {
                throw new IllegalStateException(
                        "vendor extension '" + e.getName() + "' not registered in catalog. "
                                + "Available: " + validNames);
            }
            compiled.add(CompiledRule.from(e));
        }
        this.rules = List.copyOf(compiled);
    }

    @Override
    public String select(Gb32960ParserContext ctx) {
        if (rules.isEmpty()) return null;
        CacheKey key = new CacheKey(normalize(ctx.platformAccount()), normalize(ctx.vin()));
        Optional<String> cached = cache.get(key);
        if (cached != null) return cached.orElse(null);
        // 32960 高频上报通常同一连接/同一 VIN 连续上报，缓存可避免每帧扫描所有规则。
        String hit = scan(key);
        cache.put(key, Optional.ofNullable(hit));
        return hit;
    }

    private String scan(CacheKey key) {
        for (CompiledRule r : rules) {
            if (r.matches(key.account, key.vin)) return r.name;
        }
        return null;
    }

    private static String normalize(String s) {
        return s == null || s.isEmpty() ? null : s.toUpperCase(Locale.ROOT);
    }

    /** 暴露给测试和 actuator 端点（如果以后加）。 */
    public int cacheSize() { return cache.size(); }

    private record CacheKey(String account, String vin) {}

    /** 预编译后的单条规则：所有列表已 normalize 成大写 set。 */
    private static final class CompiledRule {
        final String name;
        final Set<String> accounts;
        final Set<String> vins;
        final List<String> vinPrefixes;

        CompiledRule(String name, Set<String> accounts, Set<String> vins, List<String> vinPrefixes) {
            this.name = name;
            this.accounts = accounts;
            this.vins = vins;
            this.vinPrefixes = vinPrefixes;
        }

        static CompiledRule from(Gb32960Properties.VendorExtension e) {
            Gb32960Properties.VendorExtension.Match m = e.getMatch();
            return new CompiledRule(
                    e.getName(),
                    Set.copyOf(toUpper(m.getPlatformAccounts())),
                    Set.copyOf(toUpper(m.getVins())),
                    List.copyOf(toUpper(m.getVinPrefixes())));
        }

        boolean matches(String account, String vin) {
            if (account != null && accounts.contains(account)) return true;
            if (vin != null && vins.contains(vin)) return true;
            if (vin != null) {
                for (String prefix : vinPrefixes) {
                    if (vin.startsWith(prefix)) return true;
                }
            }
            return false;
        }

        private static List<String> toUpper(List<String> list) {
            if (list == null || list.isEmpty()) return List.of();
            List<String> out = new ArrayList<>(list.size());
            for (String s : list) {
                if (s == null || s.isBlank()) continue;
                out.add(s.trim().toUpperCase(Locale.ROOT));
            }
            return out;
        }
    }
}
