package com.lingniu.ingest.protocol.gb32960.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

@ConfigurationProperties(prefix = "lingniu.ingest.gb32960")
public class Gb32960Properties {

    /** 是否装配 32960 协议解析组件。TCP 监听由 {@link #server} 单独控制。 */
    private boolean enabled = false;
    /** 32960 TCP 监听开关。默认跟随协议启用后启动，查询类应用可单独关闭。 */
    private Server server = new Server();
    /** 32960 TCP 监听端口；当前生产验证线使用 32960。 */
    private int port = 9000;
    /** Netty worker 线程数；0 表示按 Netty/CPU 默认策略分配。 */
    private int workerThreads = 0;

    /**
     * 读空闲告警阈值（秒）。连接在该时长内没有任何上行帧则打一条 WARN 日志，方便排查
     * "对端登入成功但不推数据"之类的静默场景。仅告警不断链；0 或负数禁用。
     */
    private int idleReadSeconds = 60;

    /** VIN 白名单认证。关闭时任何 VIN 都能登入。 */
    private Auth auth = new Auth();

    /** TLS 双向认证。 */
    private Tls tls = new Tls();

    /**
     * 报文解析行为配置。
     *
     * <p>默认 {@code lenientBlockFailure=true}：单个信息块解析异常时兜成
     * {@link com.lingniu.ingest.protocol.gb32960.model.InfoBlock.Raw} 继续解析，
     * 不再整帧放弃。关闭后恢复旧行为（任意异常直接抛 {@code DecodeException}
     * 放弃整帧），仅在灰度回滚时使用。
     */
    private Parse parse = new Parse();

    /**
     * 高频入站诊断配置。用于限制每个连接保存的"首次见到帧形态"去重 key 数量，
     * 避免异常设备制造大量不同 Raw 组合时造成 channel attribute 无界增长。
     */
    private Diagnostics diagnostics = new Diagnostics();

    /**
     * 厂商扩展协议（vendor extensions）路由配置。
     *
     * <p>每个 entry 声明一个**已被实现**的 vendor 扩展套件名（{@code name}）以及该套件
     * 的命中规则（{@link VendorExtension.Match}）。命中规则由
     * {@code RuleBasedVendorExtensionSelector} 在收到帧时按上下文（platformAccount /
     * vin / vin-prefix）逐条按 list 顺序首匹，命中后用对应套件的 parser 集合替换默认的
     * 标准解析器集合（仍包含 0x01..0x09 标准块）。
     *
     * <p>套件名必须与 {@code VendorExtensionCatalog} 里注册的 key 一致；
     * 当前内置：{@code guangdong-fc}（广东燃料电池汽车示范规范 0x30~0x34 + 0x80）。
     *
     * <p>未匹配任何规则时回退到默认 profile，仅保留 GB/T 32960 标准块。
     */
    private List<VendorExtension> vendorExtensions = new ArrayList<>();

    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }
    public Server getServer() { return server; }
    public void setServer(Server server) { this.server = server; }
    public int getPort() { return port; }
    public void setPort(int port) { this.port = port; }
    public int getWorkerThreads() { return workerThreads; }
    public void setWorkerThreads(int workerThreads) { this.workerThreads = workerThreads; }
    public int getIdleReadSeconds() { return idleReadSeconds; }
    public void setIdleReadSeconds(int idleReadSeconds) { this.idleReadSeconds = idleReadSeconds; }
    public Auth getAuth() { return auth; }
    public void setAuth(Auth auth) { this.auth = auth; }
    public Tls getTls() { return tls; }
    public void setTls(Tls tls) { this.tls = tls; }
    public Parse getParse() { return parse; }
    public void setParse(Parse parse) { this.parse = parse; }
    public Diagnostics getDiagnostics() { return diagnostics; }
    public void setDiagnostics(Diagnostics diagnostics) { this.diagnostics = diagnostics; }
    public List<VendorExtension> getVendorExtensions() { return vendorExtensions; }
    public void setVendorExtensions(List<VendorExtension> vendorExtensions) {
        this.vendorExtensions = vendorExtensions;
    }

    public static class Server {
        private boolean enabled = true;

        public boolean isEnabled() { return enabled; }
        public void setEnabled(boolean enabled) { this.enabled = enabled; }
    }

    /**
     * VIN 白名单认证。
     *
     * <p>GB/T 32960 协议没有设备账号密码，身份由报文头的 17 字节 VIN + 数据单元里的 ICCID
     * 决定。此处通过内存白名单实现最小可用的接入控制：
     * <ul>
     *   <li>{@code enabled=false}（默认）：接受任意 VIN 接入，兼容未注册的测试终端
     *   <li>{@code enabled=true}：
     *     <ul>
     *       <li>车辆登入 0x01：VIN 不在白名单时返回 {@code 0x04 VIN_NOT_EXIST} 应答，关闭连接
     *       <li>实时上报 0x02 / 补发 0x03 / 登出 0x04 / 心跳 0x07：VIN 不在白名单时丢弃，关闭连接
     *     </ul>
     * </ul>
     */
    public static class Auth {
        private boolean enabled = false;
        /** 是否大小写敏感比较 VIN。GB 16735 要求 VIN 大写，默认大小写不敏感以容错。 */
        private boolean caseSensitive = false;
        private List<String> whitelist = new ArrayList<>();

        /**
         * 平台登入 (0x05) 账号列表。GB/T 32960.3 表 29 规定平台登入携带
         * 12B 用户名 + 20B 密码 + 1B 加密规则。此处维护允许登入的账号集合，
         * 收到 0x05 时比对 username / password：
         * <ul>
         *   <li>匹配且（可选）IP 在 {@code allowedIps} 内 → 返回 {@code SUCCESS} 并产出登入事件
         *   <li>不匹配 → 返回 {@code 0x02 OTHER_ERROR} 并关闭连接
         * </ul>
         * 列表为空时默认放行所有平台登入（兼容旧行为）。
         */
        private List<Platform> platforms = new ArrayList<>();

        public boolean isEnabled() { return enabled; }
        public void setEnabled(boolean enabled) { this.enabled = enabled; }
        public boolean isCaseSensitive() { return caseSensitive; }
        public void setCaseSensitive(boolean caseSensitive) { this.caseSensitive = caseSensitive; }
        public List<String> getWhitelist() { return whitelist; }
        public void setWhitelist(List<String> whitelist) { this.whitelist = whitelist; }
        public List<Platform> getPlatforms() { return platforms; }
        public void setPlatforms(List<Platform> platforms) { this.platforms = platforms; }

        /** 转换为规范化 Set 供 O(1) 查找。 */
        public Set<String> normalizedWhitelist() {
            Set<String> out = new LinkedHashSet<>(whitelist.size());
            for (String v : whitelist) {
                if (v == null || v.isBlank()) continue;
                out.add(caseSensitive ? v.trim() : v.trim().toUpperCase());
            }
            return out;
        }

        /**
         * 平台账号定义。对应 GB/T 32960.3 表 29 的字段。
         *
         * @param username     12 字节以内的用户名
         * @param password     20 字节以内的密码（明文配置；推荐通过 env var 注入避免入库代码）
         * @param allowedIps   可选：允许的客户端 IP 列表，为空不做 IP 限制
         * @param description  可选：平台描述（仅日志用）
         */
        public static class Platform {
            private String username;
            private String password;
            private List<String> allowedIps = new ArrayList<>();
            private String description;

            public String getUsername() { return username; }
            public void setUsername(String username) { this.username = username; }
            public String getPassword() { return password; }
            public void setPassword(String password) { this.password = password; }
            public List<String> getAllowedIps() { return allowedIps; }
            public void setAllowedIps(List<String> allowedIps) { this.allowedIps = allowedIps; }
            public String getDescription() { return description; }
            public void setDescription(String description) { this.description = description; }
        }
    }

    /**
     * TLS 双向认证。
     *
     * <p>启用后 Netty pipeline 前置 {@code SslHandler}，服务端加载自身证书+私钥，
     * 强制要求客户端提供证书（{@code NEED_CLIENT_AUTH}）并使用 CA 证书验证。
     */
    public static class Tls {
        private boolean enabled = false;
        /** 服务端证书 PEM 路径。文件路径或 classpath 资源。 */
        private String certPath;
        /** 服务端私钥 PEM 路径（PKCS#8 未加密）。 */
        private String keyPath;
        /** 受信任的 CA 证书 PEM 路径。用于验证客户端证书。 */
        private String trustCertPath;
        /** 是否要求客户端证书。默认 true（双向）；false 则变成单向 TLS。 */
        private boolean requireClientAuth = true;

        public boolean isEnabled() { return enabled; }
        public void setEnabled(boolean enabled) { this.enabled = enabled; }
        public String getCertPath() { return certPath; }
        public void setCertPath(String certPath) { this.certPath = certPath; }
        public String getKeyPath() { return keyPath; }
        public void setKeyPath(String keyPath) { this.keyPath = keyPath; }
        public String getTrustCertPath() { return trustCertPath; }
        public void setTrustCertPath(String trustCertPath) { this.trustCertPath = trustCertPath; }
        public boolean isRequireClientAuth() { return requireClientAuth; }
        public void setRequireClientAuth(boolean requireClientAuth) { this.requireClientAuth = requireClientAuth; }
    }

    /**
     * 报文解析容错策略。
     */
    public static class Parse {
        /**
         * 单块解析异常时是否兜底为 {@link com.lingniu.ingest.protocol.gb32960.model.InfoBlock.Raw}
         * 继续解析。默认开启。
         *
         * <ul>
         *   <li>{@code true}（默认）：parser 抛 DecodeException / BufferUnderflowException /
         *       IndexOutOfBoundsException 时，固定长度块按 fixedLen 截取 Raw 后 continue；
         *       变长块或剩余字节不足时，剩余全部兜成 Raw 后 break 循环。
         *   <li>{@code false}：保留旧行为，任意异常直接抛出 DecodeException 放弃整帧。
         * </ul>
         */
        private boolean lenientBlockFailure = true;

        public boolean isLenientBlockFailure() { return lenientBlockFailure; }
        public void setLenientBlockFailure(boolean lenientBlockFailure) {
            this.lenientBlockFailure = lenientBlockFailure;
        }
    }

    /**
     * 入站诊断限流/限内存策略。
     */
    public static class Diagnostics {
        /** 每个 TCP channel 最多保存的首次帧诊断 key 数量。 */
        private int maxLoggedFrameKeysPerChannel = 128;

        public int getMaxLoggedFrameKeysPerChannel() {
            return maxLoggedFrameKeysPerChannel;
        }

        public void setMaxLoggedFrameKeysPerChannel(int maxLoggedFrameKeysPerChannel) {
            this.maxLoggedFrameKeysPerChannel = maxLoggedFrameKeysPerChannel;
        }
    }

    /**
     * 单条 vendor 扩展路由配置。{@code name} 必须与
     * {@code VendorExtensionCatalog} 注册的 key 一致。
     */
    public static class VendorExtension {
        /** 扩展套件名，必须在 catalog 中已注册（如 {@code guangdong-fc}）。 */
        private String name;
        /** 命中规则。任一字段命中即视为整条 entry 命中。 */
        private Match match = new Match();

        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public Match getMatch() { return match; }
        public void setMatch(Match match) { this.match = match; }

        /**
         * 命中规则。所有字段为 OR 关系：任意一个命中即匹配。
         * 字段内部为 case-insensitive 比较；字段为空 list 视为不参与匹配（不会主动命中）。
         */
        public static class Match {
            /** 平台登入用户名精确匹配列表（来自 0x05 PlatformLogin 的 username 字段）。 */
            private List<String> platformAccounts = new ArrayList<>();
            /** VIN 精确匹配列表。 */
            private List<String> vins = new ArrayList<>();
            /** VIN 前缀匹配列表（startsWith）。 */
            private List<String> vinPrefixes = new ArrayList<>();

            public List<String> getPlatformAccounts() { return platformAccounts; }
            public void setPlatformAccounts(List<String> platformAccounts) {
                this.platformAccounts = platformAccounts;
            }
            public List<String> getVins() { return vins; }
            public void setVins(List<String> vins) { this.vins = vins; }
            public List<String> getVinPrefixes() { return vinPrefixes; }
            public void setVinPrefixes(List<String> vinPrefixes) { this.vinPrefixes = vinPrefixes; }
        }
    }
}
