#if !CUSTOMER_BUILD
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Ounibo.Catalog.Viewer3D;

namespace Ounibo.Catalog.Core
{
    /// <summary>
    /// 自测 (--selftest): 通用目录包检查 + 样例包(SC/MAQ)专项回归。
    /// 结果写入数据包同目录 selftest.log, 退出码 0=全部通过。
    /// </summary>
    public static class SelfTest
    {
        public static int Run(string opcPath)
        {
            var log = new StringBuilder();
            int pass = 0, fail = 0;
            Action<string, bool> check = (name, ok) =>
            {
                log.AppendLine((ok ? "PASS " : "FAIL ") + name);
                if (ok) pass++; else fail++;
            };

            try
            {
                using (var pkg = OpcPackage.Open(opcPath))
                {
                    var cat = pkg.Catalog;
                    check("catalog.json 读取 (schemaVersion=" + cat.schemaVersion + ")", !string.IsNullOrEmpty(cat.schemaVersion));
                    check("分类数 >= 1 (" + (cat.categories == null ? 0 : cat.categories.Count) + ")", cat.categories != null && cat.categories.Count >= 1);
                    check("系列数 >= 1 (" + (cat.series == null ? 0 : cat.series.Count) + ")", cat.series != null && cat.series.Count >= 1);

                    bool isSample = cat.series.Count >= 2 && cat.series[0].code == "SC";

                    // ============ 通用检查 (任意目录包) ============
                    int totalV = cat.series.Sum(x => x.variants == null ? 0 : x.variants.Count);
                    check("变体总数 " + totalV, totalV > 0);

                    bool allValid = true;
                    foreach (var sAny in cat.series)
                        if (sAny.variants != null && new Configurator(sAny).ValidVariants().Count != sAny.variants.Count)
                            allValid = false;
                    check("所有变体满足各自系列规则", allValid);

                    var s0 = cat.series[0];
                    var cfg0 = new Configurator(s0);
                    var v0 = cfg0.ValidVariants().FirstOrDefault();
                    check("首个系列可解析出有效变体 (" + s0.code + ")", v0 != null);

                    // 回归: AllowedOptions 必须包含"已选参数"的键 (曾导致选择被 UI 清空的 bug)
                    var selReg = new Dictionary<string, object> { { s0.parameters[0].code, s0.parameters[0].values[0] } };
                    var allowedReg = cfg0.AllowedOptions(selReg);
                    check("回归: AllowedOptions 含已选参数键 (" + s0.parameters[0].code + ")", allowedReg.ContainsKey(s0.parameters[0].code));

                    // 回归: 完整选型后已选参数仍可改 (曾因自身值锁死可选列表)
                    var fullSel = new Dictionary<string, object>();
                    foreach (var p in s0.parameters) fullSel[p.code] = v0.@params[p.code];
                    var allowedFull = cfg0.AllowedOptions(fullSel);
                    var pAlt = s0.parameters.FirstOrDefault(x => (x.values ?? new List<object>()).Count > 1);
                    bool changeable = pAlt != null && allowedFull[pAlt.code].Count > 1;
                    check("回归: 完整选型后参数可改 (" + (pAlt == null ? "?" : pAlt.code) + " 可选项 " + (pAlt == null ? 0 : allowedFull[pAlt.code].Count) + ")", changeable);

                    var engine0 = new SearchEngine(cat);
                    var q0 = engine0.Query(s0.code, "zh");
                    check("按系列代码搜索命中 (" + q0.Count + ")", q0.Count > 0);

                    var tmpG = Path.Combine(Path.GetTempPath(), "ounibo_selftest");
                    Directory.CreateDirectory(tmpG);
                    var fG = pkg.ExtractStep(v0, tmpG);
                    var contentG = File.ReadAllText(fG);
                    check("STEP 提取成功且含实体", File.Exists(fG) && contentG.Contains("MANIFOLD_SOLID_BREP"));

                    var cylsG = StepMesh.Parse(contentG);
                    var cylCount = cylsG == null ? 0 : cylsG.Count;
                    var realStep = contentG.Contains("MANIFOLD_SOLID_BREP") || contentG.Contains("ADVANCED_BREP_SHAPE_REPRESENTATION");
                    check("STEP 可解析 (" + cylCount + " 圆柱" + (realStep && cylCount < 3 ? ", 真实 B-rep → 网页走新迪查看器" : "") + ")",
                        cylCount >= 3 || realStep);

                    var issuesG = PackageValidator.Validate(pkg);
                    check("目录包校验无 ERROR (" + issuesG.Count(i => i.Severity == "ERROR") + " 错误, " + issuesG.Count(i => i.Severity == "WARNING") + " 警告)",
                        issuesG.All(i => i.Severity != "ERROR"));

                    // 手册页附件 (kind=manual): 有则抽查首个系列可读 (尺寸图窗口对照)
                    var manSeries = cat.series.FirstOrDefault(x => x.attachments != null && x.attachments.Any(a => a.kind == "manual"));
                    if (manSeries != null)
                    {
                        var manAtt = manSeries.attachments.First(a => a.kind == "manual");
                        byte[] manBytes = null;
                        try { manBytes = pkg.ReadAsset(manAtt.path); } catch { }
                        check("手册页附件可读 (" + manSeries.code + " " + manAtt.path + ")", manBytes != null && manBytes.Length > 1000);
                    }

                    // 每个系列: 初始可选值非空 + 可解析出完整选型 (UI 可用性诊断)
                    foreach (var sAny in cat.series)
                    {
                        var cfgAny = new Configurator(sAny);
                        var allowedAny = cfgAny.AllowedOptions(new Dictionary<string, object>());
                        bool ok = true;
                        foreach (var p in sAny.parameters)
                        {
                            List<object> vals;
                            if (!allowedAny.TryGetValue(p.code, out vals) || vals.Count == 0)
                            {
                                check("系列 " + sAny.code + " 参数 " + p.code + " 初始可选值非空", false);
                                ok = false;
                            }
                        }
                        if (ok)
                        {
                            var selAny = new Dictionary<string, object>();
                            foreach (var p in sAny.parameters)
                                selAny[p.code] = allowedAny[p.code][0];
                            var vAny = cfgAny.Resolve(selAny);
                            check("系列 " + sAny.code + " 可解析出完整选型 (" + (vAny == null ? "null" : vAny.modelCode) + ")", vAny != null);
                        }
                    }

                    // 规则过滤通用验证: 找第一个含 eq→in 规则的系列, 验证过滤后的允许值都在 Then 列表内
                    var rSeries = cat.series.FirstOrDefault(x => x.rules != null && x.rules.Count > 0);
                    if (rSeries != null)
                    {
                        var r = rSeries.rules[0];
                        if (r.If != null && r.Then != null && r.If.Op == "eq" && r.Then.Op == "in")
                        {
                            var cfgR = new Configurator(rSeries);
                            var selR = new Dictionary<string, object> { { r.If.Param, r.If.Value } };
                            var allowedR = cfgR.AllowedOptions(selR);
                            List<object> thenVals;
                            if (allowedR.TryGetValue(r.Then.Param, out thenVals))
                            {
                                var allowedStr = ((object[])r.Then.Value).Select(x => Convert.ToString(x, System.Globalization.CultureInfo.InvariantCulture)).ToList();
                                bool ruleOk = thenVals.Count > 0 && thenVals.All(x => allowedStr.Contains(Convert.ToString(x, System.Globalization.CultureInfo.InvariantCulture)));
                                check("规则过滤生效 (" + rSeries.code + ": " + r.If.Param + "=" + Convert.ToString(r.If.Value) + " → " + r.Then.Param + " 仅 " + thenVals.Count + " 值)", ruleOk);
                            }
                        }
                    }

                    // ============ 参数化引擎 (mode=parametric 系列, 一期 2026-08-25) ============
                    var pSeries = cat.series.FirstOrDefault(x => ParametricEngine.IsParametric(x));
                    if (pSeries != null)
                    {
                        var ren1 = ParametricEngine.RenderCode("KC{type}{bore}-{stroke}{magnet}{mount}",
                            new Dictionary<string, object> { { "type", "00" }, { "bore", 32 }, { "stroke", 10 }, { "magnet", "" }, { "mount", "" } });
                        check("参数化: 模板渲染 KC0032-10 (" + ren1 + ")", ren1 == "KC0032-10");
                        var ren2 = ParametricEngine.RenderCode("A{type:pad2}", new Dictionary<string, object> { { "type", "3" } });
                        check("参数化: pad 补零 A03 (" + ren2 + ")", ren2 == "A03");
                        var ren3 = ParametricEngine.RenderCode("A{nofield}", new Dictionary<string, object> { { "type", "3" } });
                        check("参数化: 未知字段 → null", ren3 == null);

                        // 对拍回归: 参数化系列全部变体编码可被模板重新生成 (构建门禁的运行时复核)
                        var cfgP = new Configurator(pSeries);
                        var bad = (pSeries.variants ?? new List<Variant>()).Count(v =>
                            ParametricEngine.RenderCode(pSeries.modelCodeTemplate, v.@params) != v.modelCode);
                        check("参数化: " + pSeries.code + " 全部 " + (pSeries.variants == null ? 0 : pSeries.variants.Count) +
                            " 编码重生成一致 (不一致 " + bad + ")", bad == 0);

                        // 测试向量文件 (与网页同源): 包内 parametric-vectors.json → 逐条对拍
                        if (pkg.EntryNames.Contains("parametric-vectors.json"))
                        {
                            var vecText = Encoding.UTF8.GetString(pkg.ReadAsset("parametric-vectors.json"));
                            var vec = new System.Web.Script.Serialization.JavaScriptSerializer().DeserializeObject(vecText) as Dictionary<string, object>;
                            var cases = vec != null && vec.ContainsKey("cases") ? (object[])vec["cases"] : new object[0];
                            int vecOk = 0;
                            var vecFails = new List<string>();
                            foreach (var cObj in cases)
                            {
                                var c = cObj as Dictionary<string, object>;
                                if (c == null) { vecFails.Add("向量格式错误"); continue; }
                                var sCode = Convert.ToString(c["series"]);
                                var cs = cat.series.FirstOrDefault(x => x.code == sCode);
                                if (cs == null) { vecFails.Add("系列不存在 " + sCode); continue; }
                                var cc = new Configurator(cs);
                                var pars = new Dictionary<string, object>();
                                foreach (var kv in (Dictionary<string, object>)c["params"]) pars[kv.Key] = kv.Value;
                                var res = ParametricEngine.Generate(cs, cc, pars);
                                var errExp = c.ContainsKey("expectError") ? Convert.ToString(c["expectError"]) : null;
                                bool ok;
                                if (errExp != null)
                                    ok = res != null && res.Error == errExp;
                                else
                                {
                                    ok = res != null && res.Error == null &&
                                         Convert.ToString(c["expectCode"]) == res.ModelCode &&
                                         (!c.ContainsKey("expectStandard") || Convert.ToBoolean(c["expectStandard"]) == res.IsStandard);
                                    if (ok && c.ContainsKey("expectNearest"))
                                    {
                                        var nr = ParametricEngine.NearestStandard(cs, cc, pars);
                                        ok = nr != null && nr.modelCode == Convert.ToString(c["expectNearest"]);
                                    }
                                }
                                if (ok) vecOk++;
                                else vecFails.Add(sCode + " → " + (res == null ? "null" : (res.ModelCode ?? res.Error)));
                            }
                            check("参数化: 测试向量对拍 " + vecOk + "/" + cases.Length +
                                (vecFails.Count > 0 ? " (失败: " + string.Join("; ", vecFails.Take(5)) + ")" : ""),
                                cases.Length > 0 && vecOk == cases.Length);
                        }
                        else
                            check("参数化: 测试向量文件存在于包内", false);
                    }

                    // ============ 样例包专项回归 (SC/MAQ 虚构样例) ============
                    if (isSample)
                    {
                        var s = cat.series[0];
                        var cfg = new Configurator(s);
                        var valid = cfg.ValidVariants();
                        check("样例: SC 有效变体 72 个 (实际 " + valid.Count + ")", valid.Count == 72);
                        check("样例: 有效变体全部满足规则", valid.All(v => !cfg.RuleViolated(v)));

                        var sel1 = new Dictionary<string, object> { { "bore", 16 } };
                        var opts1 = cfg.AllowedOptions(sel1);
                        var strokes = opts1["stroke"].Select(o => Convert.ToInt32(o, System.Globalization.CultureInfo.InvariantCulture)).OrderBy(x => x).ToList();
                        check("样例: R1 缸径16→行程仅25/50", strokes.Count == 2 && strokes[0] == 25 && strokes[1] == 50);
                        check("样例: R2 缸径16→磁石仅空值", opts1["magnet"].Count == 1 && string.IsNullOrEmpty(Convert.ToString(opts1["magnet"][0])));

                        var sel2 = new Dictionary<string, object> { { "magnet", "S" } };
                        var bores = cfg.AllowedOptions(sel2)["bore"].Select(o => Convert.ToInt32(o, System.Globalization.CultureInfo.InvariantCulture)).ToList();
                        check("样例: R2 磁石S→缸径不含16", !bores.Contains(16) && bores.Count == 3);

                        var selCb = new Dictionary<string, object> { { "mount", "CB" } };
                        var strokesCb = cfg.AllowedOptions(selCb)["stroke"].Select(o => Convert.ToInt32(o, System.Globalization.CultureInfo.InvariantCulture)).ToList();
                        check("样例: R3 中摆CB→行程不含100", !strokesCb.Contains(100) && strokesCb.Count == 3);

                        var sM = cat.series[1];
                        check("样例: 系列2 = MAQ", sM.code == "MAQ");
                        var cfgM = new Configurator(sM);
                        var selM = new Dictionary<string, object> { { "bore", 6 } };
                        var strokesM = cfgM.AllowedOptions(selM)["stroke"].Select(o => Convert.ToInt32(o, System.Globalization.CultureInfo.InvariantCulture)).OrderBy(x => x).ToList();
                        check("样例: MAQ 缸径6→行程仅10/20", strokesM.Count == 2 && strokesM[0] == 10 && strokesM[1] == 20);
                        var vM = cfgM.Resolve(new Dictionary<string, object> { { "bore", 10 }, { "stroke", 40 }, { "magnet", "S" }, { "mount", "LB" } });
                        check("样例: MAQ 选型命中 MAQ10x40S-LB", vM != null && vM.modelCode == "MAQ10x40S-LB");

                        var sel3 = new Dictionary<string, object> { { "bore", 32 }, { "stroke", 50 }, { "magnet", "S" }, { "mount", "LB" } };
                        var v3 = cfg.Resolve(sel3);
                        check("样例: 完整选型命中 SC32x50S-LB", v3 != null && v3.modelCode == "SC32x50S-LB");
                        var tmp = Path.Combine(Path.GetTempPath(), "ounibo_selftest");
                        Directory.CreateDirectory(tmp);
                        var f = pkg.ExtractStep(v3, tmp);
                        var content = File.ReadAllText(f);
                        int solidCount = content.Split(new[] { "MANIFOLD_SOLID_BREP" }, StringSplitOptions.None).Length - 1;
                        check("样例: STEP 提取成功且含 4 个实体", File.Exists(f) && solidCount == 4);

                        var cyls = StepMesh.Parse(content);
                        check("样例: STEP 解析出 4 个圆柱", cyls != null && cyls.Count == 4);
                        if (cyls != null && cyls.Count == 4)
                        {
                            check("样例: 缸体R=18 杆R=6.4", Math.Abs(cyls[1].R - 18) < 0.01 && Math.Abs(cyls[3].R - 6.4) < 0.01);
                            var mesh = StepMesh.BuildMesh(cyls[0]);
                            check("样例: 网格生成", mesh.Positions.Count > 0 && mesh.TriangleIndices.Count % 3 == 0);
                        }

                        check("样例: 尺寸图资源存在", pkg.EntryNames.Any(n => n.Contains("dim-drawing")));
                        check("样例: 数据表 PDF 资源存在", pkg.EntryNames.Any(n => n.EndsWith(".pdf")));

                        var r1 = engine0.Query("SC32", "zh");
                        check("样例: 搜索 SC32 命中 SC32x50S-LB", r1.Any(x => x.ModelCode == "SC32x50S-LB"));
                        var r2 = engine0.Query("gangjing", "zh");
                        var hasKw = (s.keywords ?? "").Length > 0 ||
                                    (s.parameters ?? new List<ParameterDef>()).Any(p => (p.keywords ?? "").Length > 0);
                        if (hasKw)
                            check("样例: 拼音关键词 gangjing 命中", r2.Count > 0);
                        else
                            log.AppendLine("SKIP 拼音关键词 (数据包未提供关键词, 属已知限制)");
                        var r3 = engine0.Query("magnet", "en");
                        check("样例: 英文关键词 magnet 命中", r3.Count > 0);
                        var r4 = engine0.Query("32x50", "zh");
                        check("样例: 编码片段 32x50 命中 SC32x50S-LB", r4.Any(x => x.ModelCode == "SC32x50S-LB"));

                        var allItems = engine0.Query("SC", "zh");
                        var ranged = engine0.RangeFilter(allItems, new Dictionary<string, double?[]> { { "bore", new double?[] { 10, 30 } } });
                        var rangedBores = ranged.Select(x => Convert.ToInt32(x.Variant.@params["bore"], System.Globalization.CultureInfo.InvariantCulture)).Distinct().OrderBy(x => x).ToList();
                        check("样例: 缸径 10~30 过滤 → 仅16/25", ranged.Count > 0 && rangedBores.Count == 2 && rangedBores[0] == 16 && rangedBores[1] == 25);

                        // Builder 打包回路 (样例数据 → 新 .opc)
                        var sdDir = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(opcPath), "..", "..", "sample-data", "onb-sc"));
                        var tmpOpc = Path.Combine(Path.GetTempPath(), "ounibo_selftest_build.opc");
                        var spec = new Series
                        {
                            code = "SC",
                            nameZh = "SC 系列标准气缸",
                            nameEn = "SC Series Standard Cylinder",
                            parameters = new List<ParameterDef>
                            {
                                new ParameterDef { code = "bore", nameZh = "缸径", nameEn = "Bore", unit = "mm", type = "number", values = new List<object> { 16, 25, 32, 40 } },
                                new ParameterDef { code = "stroke", nameZh = "行程", nameEn = "Stroke", unit = "mm", type = "number", values = new List<object> { 25, 50, 75, 100 } },
                                new ParameterDef { code = "magnet", nameZh = "磁石", nameEn = "Magnet", unit = "", type = "enum", values = new List<object> { "", "S" } },
                                new ParameterDef { code = "mount", nameZh = "安装方式", nameEn = "Mounting", unit = "", type = "enum", values = new List<object> { "FA", "LB", "CB" } }
                            },
                            rules = new List<Rule>
                            {
                                new Rule { If = new RuleCond { Param = "bore", Op = "eq", Value = 16 }, Then = new RuleCond { Param = "stroke", Op = "in", Value = new object[] { 25, 50 } } },
                                new Rule { If = new RuleCond { Param = "magnet", Op = "eq", Value = "S" }, Then = new RuleCond { Param = "bore", Op = "in", Value = new object[] { 25, 32, 40 } } },
                                new Rule { If = new RuleCond { Param = "mount", Op = "eq", Value = "CB" }, Then = new RuleCond { Param = "stroke", Op = "in", Value = new object[] { 25, 50, 75 } } }
                            },
                            naming = new Naming { stepNameTemplate = "{model}.step" },
                            attachments = new List<Attachment>(),
                            variants = new List<Variant>()
                        };
                        var buildRes = OpcBuilder.Build(tmpOpc, "欧霓博气动目录", "2026.06", "ACT", "气动执行元件", "Pneumatic Actuators",
                            spec, Path.Combine(sdDir, "params.csv"), Path.Combine(sdDir, "step"),
                            new List<BuildAttachmentSpec>
                            {
                                new BuildAttachmentSpec { kind = "datasheet", lang = "zh", source = Path.Combine(sdDir, "docs", "datasheet", "SC_datasheet_zh.pdf") },
                                new BuildAttachmentSpec { kind = "dimDrawing", lang = "zh", source = Path.Combine(sdDir, "docs", "dim-drawing", "SC32x50S-LB.png"), model = "SC32x50S-LB" }
                            });
                        check("样例: Builder 打包成功 (变体 " + (buildRes.Ok ? "ok" : "fail") + ")", buildRes.Ok);
                        foreach (var be in buildRes.Errors.Take(10)) log.AppendLine("  builder-err: " + be);
                        if (buildRes.Ok)
                        {
                            using (var pkg2 = OpcPackage.Open(tmpOpc))
                            {
                                check("样例: Builder 产物可打开且 72 变体", pkg2.Catalog.series[0].variants.Count == 72);
                                var issues2 = PackageValidator.Validate(pkg2);
                                check("样例: Builder 产物校验通过", issues2.All(i => i.Severity != "ERROR"));
                                var cfg2 = new Configurator(pkg2.Catalog.series[0]);
                                var selB = new Dictionary<string, object> { { "bore", 40 }, { "stroke", 100 }, { "magnet", "" }, { "mount", "FA" } };
                                var vb = cfg2.Resolve(selB);
                                check("样例: Builder 产物选型命中 SC40x100-FA", vb != null && vb.modelCode == "SC40x100-FA");
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                fail++;
                log.AppendLine("EXCEPTION: " + ex);
            }

            log.AppendLine(string.Format("结果: {0} 通过, {1} 失败", pass, fail));
            var logPath = Path.Combine(Path.GetDirectoryName(opcPath) ?? ".", "selftest.log");
            File.WriteAllText(logPath, log.ToString(), Encoding.UTF8);
            Console.Out.WriteLine("selftest -> " + logPath);
            return fail == 0 ? 0 : 1;
        }
    }
}
#endif