using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SolutionErp.Application.Contracts.Services;
using SolutionErp.Domain.Contracts;
using SolutionErp.Domain.Forms;
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.Master;
using SolutionErp.Domain.Master.Catalogs;
using SolutionErp.Domain.PurchaseEvaluations;

namespace SolutionErp.Infrastructure.Persistence;

public static class DbInitializer
{
    public const string AdminEmail = "admin@solutions.com.vn";
    public const string AdminPassword = "Admin@123456";

    // Migration helper (Phase 6 rebrand): rename user email
    // @solutionerp.local → @solutions.com.vn. Idempotent — skip user đã rename.
    // Password + role + refresh token giữ nguyên. Chạy trước SeedAdmin.
    private static async Task BackfillUserEmailDomainAsync(
        UserManager<User> userManager, ILogger logger)
    {
        const string OldDomain = "@solutionerp.local";
        const string NewDomain = "@solutions.com.vn";
        var oldUsers = userManager.Users
            .Where(u => u.Email != null && u.Email.EndsWith(OldDomain))
            .ToList();
        if (oldUsers.Count == 0) return;

        var renamed = 0;
        foreach (var u in oldUsers)
        {
            var newEmail = u.Email!.Replace(OldDomain, NewDomain);
            var conflict = await userManager.FindByEmailAsync(newEmail);
            if (conflict != null && conflict.Id != u.Id) continue;
            u.Email = newEmail;
            u.NormalizedEmail = newEmail.ToUpperInvariant();
            u.UserName = newEmail;
            u.NormalizedUserName = newEmail.ToUpperInvariant();
            var res = await userManager.UpdateAsync(u);
            if (res.Succeeded) renamed++;
        }
        if (renamed > 0)
            logger.LogInformation("Backfilled {Count} user emails -> @solutions.com.vn", renamed);
    }

    public static async Task InitializeAsync(IServiceProvider services)
    {
        using var scope = services.CreateScope();
        var sp = scope.ServiceProvider;
        var logger = sp.GetRequiredService<ILoggerFactory>().CreateLogger("DbInitializer");
        var db = sp.GetRequiredService<ApplicationDbContext>();
        var userManager = sp.GetRequiredService<UserManager<User>>();
        var roleManager = sp.GetRequiredService<RoleManager<Role>>();

        logger.LogInformation("Applying migrations...");
        await db.Database.MigrateAsync();

        await SeedRolesAsync(roleManager, logger);
        // Phase 6 rebrand: rename user email @solutionerp.local → @solutions.com.vn
        // TRƯỚC SeedAdmin để SeedAdmin tìm theo new email thấy user đã rename → skip create.
        await BackfillUserEmailDomainAsync(userManager, logger);
        await SeedAdminAsync(userManager, logger);
        await SeedDepartmentsAsync(db, logger);
        await SeedDemoUsersAsync(db, userManager, logger);
        await SeedMenuTreeAsync(db, logger);
        await SeedAdminPermissionsAsync(db, roleManager, logger);
        await SeedDemoMasterDataAsync(db, logger);
        await SeedContractTemplatesAsync(db, logger);
        await SeedWorkflowDefinitionsAsync(db, logger);
        await SeedPurchaseEvaluationWorkflowsAsync(db, logger);
        await SeedCatalogsAsync(db, logger);

        // Backfill mã HĐ cho HĐ legacy chưa có (sau khi đổi policy gen-tại-create).
        // Idempotent: chỉ HĐ MaHopDong IS NULL được gen.
        var codeGen = sp.GetRequiredService<IContractCodeGenerator>();
        await BackfillContractCodesAsync(db, codeGen, logger);

        await SeedDemoContractsAsync(db, userManager, codeGen, logger);
        await SeedDemoPurchaseEvaluationsAsync(db, userManager, logger);

        await WarnDefaultAdminPasswordAsync(userManager, logger);
    }

    // Seed 4 master catalogs với defaults cho user nhập liệu Details. Idempotent:
    // skip per-table nếu đã có row (admin có thể đã thêm/sửa — không clobber).
    private static async Task SeedCatalogsAsync(ApplicationDbContext db, ILogger logger)
    {
        // 1. UnitsOfMeasure (~17)
        if (!await db.UnitsOfMeasure.AnyAsync())
        {
            var units = new[]
            {
                ("m2", "Mét vuông"), ("m3", "Mét khối"), ("m", "Mét"), ("md", "Mét dài"),
                ("kg", "Kilogram"), ("tan", "Tấn"), ("g", "Gram"),
                ("l", "Lít"), ("ml", "Mililít"),
                ("cai", "Cái"), ("chiec", "Chiếc"), ("bo", "Bộ"), ("goi", "Gói"), ("hop", "Hộp"),
                ("ngc", "Ngày công"), ("h", "Giờ"), ("ca", "Ca"),
                ("nguoi", "Người"), ("luot", "Lượt"), ("lan", "Lần"),
            };
            foreach (var (code, name) in units)
                db.UnitsOfMeasure.Add(new UnitOfMeasure { Code = code, Name = name });
            await db.SaveChangesAsync();
            logger.LogInformation("Seed {Count} UnitsOfMeasure", units.Length);
        }

        // 2. MaterialItems (~15 demo)
        if (!await db.MaterialItems.AnyAsync())
        {
            var materials = new[]
            {
                ("XM-PCB40", "Xi măng PCB40", "Xi măng", "kg"),
                ("XM-PCB30", "Xi măng PCB30", "Xi măng", "kg"),
                ("CAT-VANG", "Cát vàng đổ bê tông", "Cát đá", "m3"),
                ("CAT-DEN", "Cát đen xây trát", "Cát đá", "m3"),
                ("DA-1x2", "Đá 1x2", "Cát đá", "m3"),
                ("DA-4x6", "Đá 4x6", "Cát đá", "m3"),
                ("THEP-D6", "Thép cuộn phi 6", "Sắt thép", "kg"),
                ("THEP-D10", "Thép cây phi 10", "Sắt thép", "kg"),
                ("THEP-D14", "Thép cây phi 14", "Sắt thép", "kg"),
                ("THEP-D18", "Thép cây phi 18", "Sắt thép", "kg"),
                ("GACH-XANH", "Gạch xây 4 lỗ", "Gạch ngói", "cai"),
                ("GACH-DAT-NUNG", "Gạch đặc đất nung", "Gạch ngói", "cai"),
                ("SON-LOT", "Sơn lót chống kiềm", "Sơn", "l"),
                ("SON-NGOAI", "Sơn ngoại thất", "Sơn", "l"),
                ("ONG-PVC-90", "Ống PVC phi 90", "Vật tư cấp thoát nước", "m"),
            };
            foreach (var (code, name, cat, unit) in materials)
                db.MaterialItems.Add(new MaterialItem { Code = code, Name = name, Category = cat, DefaultUnit = unit });
            await db.SaveChangesAsync();
            logger.LogInformation("Seed {Count} MaterialItems", materials.Length);
        }

        // 3. ServiceItems (~10 demo)
        if (!await db.ServiceItems.AnyAsync())
        {
            var services = new[]
            {
                ("VC-OTO", "Vận chuyển ô tô tải", "Vận chuyển", "ca"),
                ("VC-CAN-TRUC", "Cẩu tháp / cẩu tự hành", "Vận chuyển", "h"),
                ("BT-MAY", "Bảo trì máy móc thiết bị", "Bảo trì", "lan"),
                ("BT-DIEN", "Bảo trì hệ thống điện", "Bảo trì", "lan"),
                ("TV-THIET-KE", "Tư vấn thiết kế", "Tư vấn", "goi"),
                ("TV-GIAM-SAT", "Giám sát thi công", "Tư vấn", "ngc"),
                ("KIEM-DINH", "Kiểm định an toàn", "Kiểm định", "lan"),
                ("VS-CT", "Vệ sinh công trường", "Vệ sinh", "ca"),
                ("BV-CT", "Bảo vệ công trường", "Bảo vệ", "ngc"),
                ("THUE-MAY", "Thuê máy móc thiết bị", "Thuê thiết bị", "ca"),
            };
            foreach (var (code, name, cat, unit) in services)
                db.ServiceItems.Add(new ServiceItem { Code = code, Name = name, Category = cat, DefaultUnit = unit });
            await db.SaveChangesAsync();
            logger.LogInformation("Seed {Count} ServiceItems", services.Length);
        }

        // 4. WorkItems (~15 demo)
        if (!await db.WorkItems.AnyAsync())
        {
            var works = new[]
            {
                ("DAO-MONG", "Đào móng công trình", "Phần thô", "m3"),
                ("DO-BTONG", "Đổ bê tông móng/cột/dầm", "Phần thô", "m3"),
                ("LAP-COT-THEP", "Lắp dựng cốt thép", "Phần thô", "kg"),
                ("LAP-COPPHA", "Lắp dựng cốp pha", "Phần thô", "m2"),
                ("XAY-TUONG", "Xây tường gạch", "Phần thô", "m3"),
                ("TRAT-TUONG", "Trát tường", "Hoàn thiện", "m2"),
                ("LAT-GACH-NEN", "Lát gạch nền", "Hoàn thiện", "m2"),
                ("OP-GACH-TUONG", "Ốp gạch tường", "Hoàn thiện", "m2"),
                ("SON-NUOC", "Sơn nước", "Hoàn thiện", "m2"),
                ("LAP-CUA", "Lắp cửa", "Hoàn thiện", "cai"),
                ("LAP-DIEN", "Lắp đặt hệ thống điện", "Cơ điện", "goi"),
                ("LAP-NUOC", "Lắp đặt hệ thống cấp thoát nước", "Cơ điện", "goi"),
                ("LAP-DIEU-HOA", "Lắp điều hòa", "Cơ điện", "cai"),
                ("THAM-CHONG", "Thấm chống thấm", "Hoàn thiện", "m2"),
                ("VC-PHE-THAI", "Vận chuyển phế thải", "Khác", "m3"),
            };
            foreach (var (code, name, cat, unit) in works)
                db.WorkItems.Add(new WorkItem { Code = code, Name = name, Category = cat, DefaultUnit = unit });
            await db.SaveChangesAsync();
            logger.LogInformation("Seed {Count} WorkItems", works.Length);
        }
    }

    // Backfill: re-assign supplier + project cho [DEMO] HĐ existing → đa dạng
    // theo DemoSupplierByType / DemoProjectByType. Idempotent (chỉ update nếu
    // mismatch). Hữu ích khi expand seed list rồi muốn HĐ cũ pick supplier mới.
    private static async Task BackfillDemoContractsSupplierProjectAsync(
        ApplicationDbContext db,
        IReadOnlyDictionary<string, Supplier> suppliersByCode,
        IReadOnlyDictionary<string, Project> projectsByCode,
        ILogger logger)
    {
        var demos = await db.Contracts
            .Where(c => c.TenHopDong != null && c.TenHopDong!.StartsWith("[DEMO]"))
            .ToListAsync();
        if (demos.Count == 0) return;

        int updated = 0;
        foreach (var c in demos)
        {
            var newSupplierId = c.SupplierId;
            var newProjectId = c.ProjectId;

            if (DemoSupplierByType.TryGetValue(c.Type, out var sCode)
                && suppliersByCode.TryGetValue(sCode, out var s)
                && c.SupplierId != s.Id)
            {
                newSupplierId = s.Id;
            }
            if (DemoProjectByType.TryGetValue(c.Type, out var pCode)
                && projectsByCode.TryGetValue(pCode, out var p)
                && c.ProjectId != p.Id)
            {
                newProjectId = p.Id;
            }

            if (newSupplierId != c.SupplierId || newProjectId != c.ProjectId)
            {
                c.SupplierId = newSupplierId;
                c.ProjectId = newProjectId;
                updated++;
            }
        }
        if (updated > 0)
        {
            await db.SaveChangesAsync();
            logger.LogInformation("Backfill [DEMO] contracts supplier+project: {Count} updated.", updated);
        }
    }

    private static async Task BackfillContractCodesAsync(
        ApplicationDbContext db, IContractCodeGenerator codeGen, ILogger logger)
    {
        var orphanCount = await db.Contracts.CountAsync(c => c.MaHopDong == null);
        if (orphanCount == 0)
        {
            logger.LogInformation("Backfill mã HĐ: không có HĐ nào thiếu mã.");
            return;
        }

        logger.LogInformation("Backfill mã HĐ: {Count} HĐ thiếu mã, đang gen...", orphanCount);
        var orphans = await db.Contracts.Where(c => c.MaHopDong == null).ToListAsync();
        int success = 0, failed = 0;

        foreach (var contract in orphans)
        {
            var supplier = await db.Suppliers.FirstOrDefaultAsync(s => s.Id == contract.SupplierId);
            var project = await db.Projects.FirstOrDefaultAsync(p => p.Id == contract.ProjectId);
            if (supplier is null || project is null)
            {
                logger.LogWarning(
                    "Backfill HĐ {Id}: skip vì supplier/project missing.", contract.Id);
                failed++;
                continue;
            }

            try
            {
                contract.MaHopDong = await codeGen.GenerateAsync(contract, project.Code, supplier.Code);
                await db.SaveChangesAsync();
                logger.LogInformation("Backfill HĐ {Id} → {Code}", contract.Id, contract.MaHopDong);
                success++;
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Backfill HĐ {Id} thất bại.", contract.Id);
                failed++;
            }
        }

        logger.LogInformation(
            "Backfill mã HĐ xong: {Success} thành công, {Failed} thất bại.", success, failed);
    }

    // Seed v01 per ContractType from hardcoded WorkflowPolicies. Idempotent:
    // skip if any active definition already exists for that type (admin may
    // have already created custom versions — don't clobber).
    private static async Task SeedWorkflowDefinitionsAsync(ApplicationDbContext db, ILogger logger)
    {
        var typeLabels = new Dictionary<ContractType, (string Code, string Name)>
        {
            [ContractType.HopDongThauPhu]          = ("QT-TP",   "Quy trình HĐ Thầu phụ"),
            [ContractType.HopDongGiaoKhoan]        = ("QT-GK",   "Quy trình HĐ Giao khoán"),
            [ContractType.HopDongNhaCungCap]       = ("QT-NCC",  "Quy trình HĐ Nhà cung cấp"),
            [ContractType.HopDongDichVu]           = ("QT-DV",   "Quy trình HĐ Dịch vụ"),
            [ContractType.HopDongMuaBan]           = ("QT-MB",   "Quy trình HĐ Mua bán"),
            [ContractType.HopDongNguyenTacNCC]     = ("QT-NTNCC","Quy trình HĐ Nguyên tắc NCC"),
            [ContractType.HopDongNguyenTacDichVu]  = ("QT-NTDV", "Quy trình HĐ Nguyên tắc Dịch vụ"),
        };

        var phaseNames = new Dictionary<ContractPhase, string>
        {
            [ContractPhase.DangSoanThao]    = "Soạn thảo",
            [ContractPhase.DangGopY]        = "Góp ý",
            [ContractPhase.DangDamPhan]     = "Đàm phán",
            [ContractPhase.DangInKy]        = "In ký",
            [ContractPhase.DangKiemTraCCM]  = "Kiểm tra CCM",
            [ContractPhase.DangTrinhKy]     = "Trình ký BOD",
            [ContractPhase.DangDongDau]     = "Đóng dấu",
            [ContractPhase.DaPhatHanh]      = "Phát hành",
        };

        var added = 0;
        foreach (var (type, info) in typeLabels)
        {
            var alreadyExists = await db.WorkflowDefinitions.AnyAsync(w => w.ContractType == type);
            if (alreadyExists) continue;

            var policy = WorkflowPolicyRegistry.For(type);
            var def = new WorkflowDefinition
            {
                Code = info.Code,
                Version = 1,
                ContractType = type,
                Name = $"{info.Name} (v01)",
                Description = policy.Description,
                IsActive = true,
                ActivatedAt = DateTime.UtcNow,
                Steps = policy.ActivePhases
                    .Where(p => p != ContractPhase.TuChoi)  // TuChoi is a terminal state, not a step
                    .Select((p, idx) =>
                    {
                        var roles = policy.Transitions
                            .Where(t => t.Key.To == p)
                            .SelectMany(t => t.Value)
                            .Distinct()
                            .ToList();
                        return new WorkflowStep
                        {
                            Order = idx + 1,
                            Phase = p,
                            Name = phaseNames.GetValueOrDefault(p, p.ToString()),
                            SlaDays = policy.PhaseSla.GetValueOrDefault(p) is TimeSpan s ? (int?)s.Days : null,
                            Approvers = roles.Select(r => new WorkflowStepApprover
                            {
                                Kind = WorkflowApproverKind.Role,
                                AssignmentValue = r,
                            }).ToList(),
                        };
                    })
                    .ToList(),
            };
            db.WorkflowDefinitions.Add(def);
            added++;
        }
        if (added > 0)
        {
            await db.SaveChangesAsync();
            logger.LogInformation("Seeded {Count} workflow definitions (v01)", added);
        }
    }

    // Seed default workflow v01 cho 2 PurchaseEvaluationType (A NccOnly / B
    // NccWithPlan) — mirror SeedWorkflowDefinitionsAsync của HĐ. Admin có
    // thể tạo version mới qua /system/pe-workflows/:typeCode designer.
    private static async Task SeedPurchaseEvaluationWorkflowsAsync(ApplicationDbContext db, ILogger logger)
    {
        var typeLabels = new Dictionary<PurchaseEvaluationType, (string Code, string Name)>
        {
            [PurchaseEvaluationType.DuyetNcc]          = ("QT-DN-A", "Quy trình Duyệt NCC"),
            [PurchaseEvaluationType.DuyetNccPhuongAn]  = ("QT-DN-B", "Quy trình Duyệt NCC và Giải pháp"),
        };

        var phaseNames = new Dictionary<PurchaseEvaluationPhase, string>
        {
            [PurchaseEvaluationPhase.DangSoanThao]     = "Soạn thảo",
            [PurchaseEvaluationPhase.ChoPurchasing]    = "Purchasing tổng hợp",
            [PurchaseEvaluationPhase.ChoDuAn]          = "Dự án kiểm tra",
            [PurchaseEvaluationPhase.ChoCCM]           = "CCM kiểm tra ngân sách",
            [PurchaseEvaluationPhase.ChoCEODuyetPA]    = "CEO duyệt phương án",
            [PurchaseEvaluationPhase.ChoCEODuyetNCC]   = "CEO duyệt chọn đơn vị",
            [PurchaseEvaluationPhase.DaDuyet]          = "Đã duyệt",
        };

        var added = 0;
        foreach (var (type, info) in typeLabels)
        {
            var alreadyExists = await db.PurchaseEvaluationWorkflowDefinitions
                .AnyAsync(w => w.EvaluationType == type);
            if (alreadyExists) continue;

            var policy = PurchaseEvaluationPolicyRegistry.For(type);
            var def = new PurchaseEvaluationWorkflowDefinition
            {
                Code = info.Code,
                Version = 1,
                EvaluationType = type,
                Name = $"{info.Name} (v01)",
                Description = policy.Description,
                IsActive = true,
                ActivatedAt = DateTime.UtcNow,
                Steps = policy.ActivePhases
                    .Where(p => p != PurchaseEvaluationPhase.TuChoi && p != PurchaseEvaluationPhase.DaDuyet)
                    .Select((p, idx) =>
                    {
                        var roles = policy.Transitions
                            .Where(t => t.Key.To == p)
                            .SelectMany(t => t.Value)
                            .Distinct()
                            .ToList();
                        return new PurchaseEvaluationWorkflowStep
                        {
                            Order = idx + 1,
                            Phase = p,
                            Name = phaseNames.GetValueOrDefault(p, p.ToString()),
                            SlaDays = policy.PhaseSla.GetValueOrDefault(p) is TimeSpan s ? (int?)s.Days : null,
                            Approvers = roles.Select(r => new PurchaseEvaluationWorkflowStepApprover
                            {
                                Kind = WorkflowApproverKind.Role,
                                AssignmentValue = r,
                            }).ToList(),
                        };
                    })
                    .ToList(),
            };
            db.PurchaseEvaluationWorkflowDefinitions.Add(def);
            added++;
        }
        if (added > 0)
        {
            await db.SaveChangesAsync();
            logger.LogInformation("Seeded {Count} PE workflow definitions (v01)", added);
        }
    }

    // Map ContractType → preferred supplier code (đa dạng dữ liệu demo theo
    // đúng business: ThauPhu/GiaoKhoan đi với NTP/TĐ, NCC/MuaBan/NTNcc đi
    // với NCC, DichVu/NTDv đi với DV).
    private static readonly Dictionary<ContractType, string> DemoSupplierByType = new()
    {
        [ContractType.HopDongThauPhu]          = "NTP-XD",
        [ContractType.HopDongGiaoKhoan]        = "TD-NEHOANG",
        [ContractType.HopDongNhaCungCap]       = "NCC-XIMANG",
        [ContractType.HopDongDichVu]           = "DV-VANCHUYEN",
        [ContractType.HopDongMuaBan]           = "NCC-DIEN",
        [ContractType.HopDongNguyenTacNCC]     = "NCC-THEP",
        [ContractType.HopDongNguyenTacDichVu]  = "DV-CLEAN",
    };

    // Map ContractType → preferred project code (đa dạng dự án per HĐ)
    private static readonly Dictionary<ContractType, string> DemoProjectByType = new()
    {
        [ContractType.HopDongThauPhu]          = "FLOCK01",
        [ContractType.HopDongGiaoKhoan]        = "FLOCK01",
        [ContractType.HopDongNhaCungCap]       = "VHOMES-OP",
        [ContractType.HopDongDichVu]           = "RESORT-PQ",
        [ContractType.HopDongMuaBan]           = "BWTOWER",
        [ContractType.HopDongNguyenTacNCC]     = "ECOPARK-VL",
        [ContractType.HopDongNguyenTacDichVu]  = "WAREHOUSE-LB",
    };

    // Seed 7 demo HĐ — 1 per ContractType, varied phases (Đang soạn / Góp ý /
    // Đã in ký / Đã phát hành), với Details + Approvals + Comments để admin
    // login thấy ngay sample data hoạt động full E2E.
    //
    // Idempotent 2 paths:
    //   1. Lần đầu (no [DEMO] HĐ): create 7 HĐ varied supplier+project per type
    //   2. Đã có [DEMO] HĐ: chạy backfill — update supplier+project nếu mismatched
    //      với DemoSupplierByType/DemoProjectByType (đa dạng dữ liệu).
    private static async Task SeedDemoContractsAsync(
        ApplicationDbContext db, UserManager<User> userManager,
        IContractCodeGenerator codeGen, ILogger logger)
    {
        // Lookup supplier + project map theo Code (cần cả 2 path)
        var suppliersByCode = await db.Suppliers.ToDictionaryAsync(s => s.Code, s => s);
        var projectsByCode = await db.Projects.ToDictionaryAsync(p => p.Code, p => p);
        if (suppliersByCode.Count == 0 || projectsByCode.Count == 0)
        {
            logger.LogWarning("SeedDemoContracts: skip — thiếu Supplier/Project.");
            return;
        }

        // Path 2: backfill nếu đã có [DEMO] HĐ
        if (await db.Contracts.AnyAsync(c => c.TenHopDong != null && c.TenHopDong!.StartsWith("[DEMO]")))
        {
            await BackfillDemoContractsSupplierProjectAsync(db, suppliersByCode, projectsByCode, logger);
            return;
        }

        // Path 1: tạo mới (lần đầu)
        // Default fallback supplier + project nếu type-specific không có (shouldn't happen với expanded seed)
        var fallbackSupplier = suppliersByCode.Values.First();
        var fallbackProject = projectsByCode.Values.First();

        var qsHoang = await userManager.FindByEmailAsync("qs.hoang@solutions.com.vn");
        var ccmTran = await userManager.FindByEmailAsync("ccm.tran@solutions.com.vn");
        var bodHuynh = await userManager.FindByEmailAsync("bod.huynh@solutions.com.vn");
        var hraDang = await userManager.FindByEmailAsync("hra.dang@solutions.com.vn");
        var qsDeptId = (await db.Departments.FirstOrDefaultAsync(d => d.Code == "QS"))?.Id;
        var nowUtc = DateTime.UtcNow;

        async Task<Contract> createDemoAsync(
            ContractType type, ContractPhase finalPhase, string ten, decimal giaTri, string? noiDung)
        {
            var activeWfId = await db.WorkflowDefinitions.AsNoTracking()
                .Where(w => w.ContractType == type && w.IsActive)
                .Select(w => (Guid?)w.Id)
                .FirstOrDefaultAsync();

            // Pick supplier + project theo type (đa dạng dữ liệu demo)
            var supplier = DemoSupplierByType.TryGetValue(type, out var sCode) && suppliersByCode.TryGetValue(sCode, out var s) ? s : fallbackSupplier;
            var project = DemoProjectByType.TryGetValue(type, out var pCode) && projectsByCode.TryGetValue(pCode, out var p) ? p : fallbackProject;

            var c = new Contract
            {
                Type = type,
                Phase = ContractPhase.DangSoanThao,        // Tạo ở phase 2, transition lên sau
                SupplierId = supplier.Id,
                ProjectId = project.Id,
                DepartmentId = qsDeptId,
                DrafterUserId = qsHoang?.Id,
                GiaTri = giaTri,
                TenHopDong = $"[DEMO] {ten}",
                NoiDung = noiDung,
                BypassProcurementAndCCM = false,
                WorkflowDefinitionId = activeWfId,
                SlaDeadline = nowUtc.AddDays(7),
            };
            c.MaHopDong = await codeGen.GenerateAsync(c, project.Code, supplier.Code);
            db.Contracts.Add(c);
            await db.SaveChangesAsync();

            // Mock approvals + Phase advance qua manual write (skip workflow service vì
            // service expect ICurrentUser context). Thực tế chỉ cần ContractApproval
            // history + cập nhật Phase entity.
            ContractPhase[] flow = type is ContractType.HopDongDichVu or ContractType.HopDongMuaBan
                or ContractType.HopDongNguyenTacNCC or ContractType.HopDongNguyenTacDichVu
                ? [ContractPhase.DangGopY, ContractPhase.DangDamPhan, ContractPhase.DangInKy,
                   ContractPhase.DangTrinhKy, ContractPhase.DangDongDau, ContractPhase.DaPhatHanh]
                : [ContractPhase.DangGopY, ContractPhase.DangDamPhan, ContractPhase.DangInKy,
                   ContractPhase.DangKiemTraCCM, ContractPhase.DangTrinhKy,
                   ContractPhase.DangDongDau, ContractPhase.DaPhatHanh];

            var current = ContractPhase.DangSoanThao;
            foreach (var next in flow)
            {
                if (next > finalPhase) break;
                Guid? actorId = next switch
                {
                    ContractPhase.DangKiemTraCCM => ccmTran?.Id,
                    ContractPhase.DangTrinhKy or ContractPhase.DangDongDau => bodHuynh?.Id,
                    ContractPhase.DaPhatHanh => hraDang?.Id,
                    _ => qsHoang?.Id,
                };
                db.ContractApprovals.Add(new ContractApproval
                {
                    ContractId = c.Id,
                    FromPhase = current,
                    ToPhase = next,
                    ApproverUserId = actorId,
                    Decision = ApprovalDecision.Approve,
                    Comment = next == ContractPhase.DangGopY ? "Demo seed — chuyển góp ý" : null,
                    ApprovedAt = nowUtc,
                });
                current = next;
            }
            c.Phase = current;
            return c;
        }

        async Task addThauPhuDetail(Guid cid, int order, string hangMuc, string dvt, decimal kl, decimal dg)
        {
            db.ThauPhuDetails.Add(new Domain.Contracts.Details.ThauPhuDetail
            {
                ContractId = cid, Order = order, HangMuc = hangMuc, DonViTinh = dvt,
                KhoiLuong = kl, DonGia = dg, ThanhTien = kl * dg,
            });
        }
        async Task addNccDetail(Guid cid, int order, string ma, string ten, string dvt, decimal sl, decimal dg)
        {
            db.NhaCungCapDetails.Add(new Domain.Contracts.Details.NhaCungCapDetail
            {
                ContractId = cid, Order = order, MaSP = ma, TenSP = ten, DonViTinh = dvt,
                SoLuong = sl, DonGia = dg, ThanhTien = sl * dg,
            });
        }
        async Task addMuaBanDetail(Guid cid, int order, string ma, string ten, string dvt, decimal sl, decimal dg, decimal vat)
        {
            db.MuaBanDetails.Add(new Domain.Contracts.Details.MuaBanDetail
            {
                ContractId = cid, Order = order, MaSP = ma, TenSP = ten, DonViTinh = dvt,
                SoLuong = sl, DonGia = dg, ThueVAT = vat, ThanhTien = sl * dg * (1 + vat / 100m),
            });
        }
        async Task addDichVuDetail(Guid cid, int order, string ma, string ten, string dvt, decimal tg, decimal dg)
        {
            db.DichVuDetails.Add(new Domain.Contracts.Details.DichVuDetail
            {
                ContractId = cid, Order = order, MaDichVu = ma, TenDichVu = ten, DonViTinh = dvt,
                ThoiGian = tg, DonGia = dg, ThanhTien = tg * dg,
            });
        }

        // 1. HĐ Thầu phụ — DangSoanThao (đang soạn)
        var c1 = await createDemoAsync(
            ContractType.HopDongThauPhu, ContractPhase.DangSoanThao,
            "Thi công móng + cột tầng 1 — FLOCK 01", 850_000_000m,
            "Bao gồm đào móng, đổ bê tông móng cột, lắp cốt thép.");
        await addThauPhuDetail(c1.Id, 1, "Đào móng công trình", "m3", 120, 250_000);
        await addThauPhuDetail(c1.Id, 2, "Đổ bê tông móng cột", "m3", 80, 1_800_000);
        await addThauPhuDetail(c1.Id, 3, "Lắp dựng cốt thép", "kg", 5000, 18_000);

        // 2. HĐ Giao khoán — DangGopY (đang góp ý)
        var c2 = await createDemoAsync(
            ContractType.HopDongGiaoKhoan, ContractPhase.DangGopY,
            "Khoán nhân công xây tường + trát + sơn — FLOCK 01", 320_000_000m,
            "Phần hoàn thiện tầng 2 + 3.");
        db.GiaoKhoanDetails.Add(new Domain.Contracts.Details.GiaoKhoanDetail
        {
            ContractId = c2.Id, Order = 1, MaCongViec = "XAY-TUONG", TenCongViec = "Xây tường gạch 110",
            DonViTinh = "m2", KhoiLuong = 800, DonGia = 180_000, ThanhTien = 144_000_000m,
        });
        db.GiaoKhoanDetails.Add(new Domain.Contracts.Details.GiaoKhoanDetail
        {
            ContractId = c2.Id, Order = 2, MaCongViec = "TRAT-TUONG", TenCongViec = "Trát tường 2 mặt",
            DonViTinh = "m2", KhoiLuong = 1600, DonGia = 80_000, ThanhTien = 128_000_000m,
        });
        db.GiaoKhoanDetails.Add(new Domain.Contracts.Details.GiaoKhoanDetail
        {
            ContractId = c2.Id, Order = 3, MaCongViec = "SON-NUOC", TenCongViec = "Sơn nước nội thất",
            DonViTinh = "m2", KhoiLuong = 1600, DonGia = 30_000, ThanhTien = 48_000_000m,
        });

        // 3. HĐ Nhà cung cấp — DangInKy (đang in ký)
        var c3 = await createDemoAsync(
            ContractType.HopDongNhaCungCap, ContractPhase.DangInKy,
            "Cung cấp xi măng + sắt thép Q2/2026", 1_200_000_000m,
            "Đợt 1: 200 tấn xi măng + 50 tấn thép.");
        await addNccDetail(c3.Id, 1, "XM-PCB40", "Xi măng PCB40 50kg", "tan", 200, 1_800_000);
        await addNccDetail(c3.Id, 2, "THEP-D14", "Thép cây phi 14", "kg", 30000, 18_500);
        await addNccDetail(c3.Id, 3, "THEP-D18", "Thép cây phi 18", "kg", 20000, 18_800);

        // 4. HĐ Dịch vụ — DangTrinhKy (đang trình ký BOD)
        var c4 = await createDemoAsync(
            ContractType.HopDongDichVu, ContractPhase.DangTrinhKy,
            "Thuê cẩu tháp 6 tháng — FLOCK 01", 540_000_000m,
            "Cẩu tháp Liebherr — Q2-Q3/2026.");
        await addDichVuDetail(c4.Id, 1, "VC-CAN-TRUC", "Cẩu tháp Liebherr 320 EC-H", "thang", 6, 90_000_000);

        // 5. HĐ Mua bán — DaPhatHanh (đã phát hành — full flow)
        var c5 = await createDemoAsync(
            ContractType.HopDongMuaBan, ContractPhase.DaPhatHanh,
            "Mua máy phát điện 250kVA", 850_000_000m,
            "1 máy phát điện chính + 1 dự phòng cho công trường.");
        await addMuaBanDetail(c5.Id, 1, "MP-250KVA", "Máy phát điện Cummins 250kVA", "cai", 1, 720_000_000, 10);
        await addMuaBanDetail(c5.Id, 2, "MP-100KVA", "Máy phát điện dự phòng 100kVA", "cai", 1, 50_000_000, 10);

        // 6. HĐ Nguyên tắc NCC — DangGopY (framework agreement)
        var c6 = await createDemoAsync(
            ContractType.HopDongNguyenTacNCC, ContractPhase.DangGopY,
            "HĐ nguyên tắc cung cấp vật tư xây dựng 2026", 0m,
            "Khung giá tham chiếu — đặt mua qua PO theo từng đợt.");
        db.NguyenTacNccDetails.Add(new Domain.Contracts.Details.NguyenTacNccDetail
        {
            ContractId = c6.Id, Order = 1, NhomSP = "Xi măng",
            TenSP = "Xi măng PCB40 50kg", DonViTinh = "tan",
            DonGiaToiThieu = 1_700_000, DonGiaToiDa = 1_900_000,
            DieuKienThanhToan = "Net 30 — chuyển khoản",
        });
        db.NguyenTacNccDetails.Add(new Domain.Contracts.Details.NguyenTacNccDetail
        {
            ContractId = c6.Id, Order = 2, NhomSP = "Sắt thép",
            TenSP = "Thép cây phi 10-32", DonViTinh = "kg",
            DonGiaToiThieu = 17_500, DonGiaToiDa = 19_500,
            DieuKienThanhToan = "Net 30 — chuyển khoản",
        });

        // 7. HĐ Nguyên tắc Dịch vụ — DangSoanThao
        var c7 = await createDemoAsync(
            ContractType.HopDongNguyenTacDichVu, ContractPhase.DangSoanThao,
            "HĐ nguyên tắc bảo trì thiết bị 2026", 0m,
            "Khung dịch vụ bảo trì máy móc — gọi theo nhu cầu.");
        db.NguyenTacDvDetails.Add(new Domain.Contracts.Details.NguyenTacDvDetail
        {
            ContractId = c7.Id, Order = 1, LoaiDichVu = "Bảo trì",
            TenDichVu = "Bảo trì máy phát điện", DonViTinh = "lan",
            DonGiaToiThieu = 5_000_000, DonGiaToiDa = 8_000_000,
            SLA = "Phản hồi 24h, hoàn thành 72h",
        });

        // Comments demo cho HĐ #2 (DangGopY)
        if (ccmTran is not null)
        {
            db.ContractComments.Add(new ContractComment
            {
                ContractId = c2.Id, UserId = ccmTran.Id, Phase = ContractPhase.DangGopY,
                Content = "[DEMO] CCM góp ý: Đề nghị bổ sung điều khoản phạt chậm tiến độ + bảo hành 12 tháng.",
            });
        }
        if (qsHoang is not null)
        {
            db.ContractComments.Add(new ContractComment
            {
                ContractId = c2.Id, UserId = qsHoang.Id, Phase = ContractPhase.DangGopY,
                Content = "[DEMO] Drafter phản hồi: Đã ghi nhận, sẽ cập nhật bản v2.",
            });
        }

        await db.SaveChangesAsync();
        logger.LogInformation("Seed 7 demo contracts: {C1} {C2} {C3} {C4} {C5} {C6} {C7}",
            c1.MaHopDong, c2.MaHopDong, c3.MaHopDong, c4.MaHopDong, c5.MaHopDong, c6.MaHopDong, c7.MaHopDong);
    }

    // Seed 4 demo PE phiếu — varied Type (A/B) × Phase (DangSoanThao / mid /
    // ChoCEODuyetNCC / DaDuyet). Idempotent: skip nếu MaPhieu prefix `[DEMO]`
    // đã tồn tại (KHÔNG backfill — vì Quotes/Approvals nested khó merge).
    //
    // Mỗi phiếu có:
    //   - 2-3 NCC tham gia thầu (Suppliers)
    //   - 2-4 hạng mục (Details — group A.I/A.II/A.III)
    //   - N×M Quotes (báo giá per supplier × hạng mục)
    //   - Approval history theo phase đã đi qua
    //   - Phiếu DaDuyet có SelectedSupplierId (winner)
    //
    // MaPhieu format tạm hardcode `[DEMO]-A-001` etc. — sau này MaPhieu atomic
    // sequence (migration 13) sẽ replace.
    private static async Task SeedDemoPurchaseEvaluationsAsync(
        ApplicationDbContext db, UserManager<User> userManager, ILogger logger)
    {
        // Skip nếu đã có demo PE
        if (await db.PurchaseEvaluations.AnyAsync(p => p.MaPhieu != null && p.MaPhieu!.StartsWith("[DEMO]")))
        {
            logger.LogInformation("SeedDemoPurchaseEvaluationsAsync: skip — đã có [DEMO] PE.");
            return;
        }

        // Lookup masters
        var suppliersByCode = await db.Suppliers.ToDictionaryAsync(s => s.Code, s => s);
        var projectsByCode = await db.Projects.ToDictionaryAsync(p => p.Code, p => p);
        if (suppliersByCode.Count < 3 || projectsByCode.Count == 0)
        {
            logger.LogWarning("SeedDemoPurchaseEvaluationsAsync: skip — thiếu Supplier/Project master.");
            return;
        }

        // Lookup actor users (per role)
        var qsHoang = await userManager.FindByEmailAsync("qs.hoang@solutions.com.vn");
        var proPham = await userManager.FindByEmailAsync("pro.pham@solutions.com.vn");
        var ccmTran = await userManager.FindByEmailAsync("ccm.tran@solutions.com.vn");
        var pmNguyen = await userManager.FindByEmailAsync("pm.nguyen@solutions.com.vn");
        var bodHuynh = await userManager.FindByEmailAsync("bod.huynh@solutions.com.vn");
        var qsDeptId = (await db.Departments.FirstOrDefaultAsync(d => d.Code == "QS"))?.Id;
        var nowUtc = DateTime.UtcNow;

        // Helper: build PE phiếu với Suppliers + Details + Quotes + Approvals
        async Task<PurchaseEvaluation> createDemoPeAsync(
            string maPhieu,
            PurchaseEvaluationType type,
            PurchaseEvaluationPhase finalPhase,
            string tenGoiThau,
            string projectCode,
            string? diaDiem,
            string? moTa,
            string[] supplierCodes,         // 2-3 supplier codes
            (string GroupCode, string GroupName, string ItemCode, string NoiDung, string Dvt, decimal KlNs, decimal KlTc, decimal DgNs)[] detailRows,
            // Quotes: index by [detailIdx][supplierIdx] → (BgVat, ChuaVat, IsSelected)
            (decimal BgVat, decimal ChuaVat, bool IsSelected)[][] quotesGrid,
            string? winnerSupplierCode = null,
            string? paymentTermsJson = null)
        {
            var activeWfId = await db.PurchaseEvaluationWorkflowDefinitions.AsNoTracking()
                .Where(w => w.EvaluationType == type && w.IsActive)
                .Select(w => (Guid?)w.Id)
                .FirstOrDefaultAsync();

            var project = projectsByCode.TryGetValue(projectCode, out var p) ? p : projectsByCode.Values.First();

            var pe = new PurchaseEvaluation
            {
                MaPhieu = $"[DEMO]-{maPhieu}",
                Type = type,
                Phase = PurchaseEvaluationPhase.DangSoanThao,   // tạo ở phase 1, transition sau
                TenGoiThau = $"[DEMO] {tenGoiThau}",
                ProjectId = project.Id,
                DepartmentId = qsDeptId,
                DrafterUserId = qsHoang?.Id,
                DiaDiem = diaDiem,
                MoTa = moTa,
                WorkflowDefinitionId = activeWfId,
                SlaDeadline = nowUtc.AddDays(7),
                PaymentTerms = paymentTermsJson,
            };
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync();

            // Suppliers
            var supplierEntities = new List<PurchaseEvaluationSupplier>();
            for (int i = 0; i < supplierCodes.Length; i++)
            {
                var sCode = supplierCodes[i];
                if (!suppliersByCode.TryGetValue(sCode, out var supplier)) continue;
                var pes = new PurchaseEvaluationSupplier
                {
                    PurchaseEvaluationId = pe.Id,
                    SupplierId = supplier.Id,
                    DisplayName = supplier.Name,
                    ContactName = supplier.ContactPerson,
                    ContactEmail = supplier.Email,
                    ContactPhone = supplier.Phone,
                    PaymentTermText = i == 0 ? "TGN-30 ngày" : i == 1 ? "TGN-45 ngày" : "Tiến độ",
                    Note = i == 0 ? "ĐÃ CHỐT SO SÁNH LẦN 1" : null,
                    Order = i + 1,
                };
                db.PurchaseEvaluationSuppliers.Add(pes);
                supplierEntities.Add(pes);
            }
            await db.SaveChangesAsync();

            // Details
            var detailEntities = new List<PurchaseEvaluationDetail>();
            for (int i = 0; i < detailRows.Length; i++)
            {
                var (gc, gn, ic, nd, dvt, klNs, klTc, dgNs) = detailRows[i];
                var det = new PurchaseEvaluationDetail
                {
                    PurchaseEvaluationId = pe.Id,
                    GroupCode = gc, GroupName = gn,
                    ItemCode = ic, NoiDung = nd, DonViTinh = dvt,
                    KhoiLuongNganSach = klNs,
                    KhoiLuongThiCong = klTc,
                    DonGiaNganSach = dgNs,
                    ThanhTienNganSach = klNs * dgNs,
                    Order = i + 1,
                };
                db.PurchaseEvaluationDetails.Add(det);
                detailEntities.Add(det);
            }
            await db.SaveChangesAsync();

            // Quotes: detailIdx × supplierIdx
            for (int di = 0; di < detailEntities.Count; di++)
            {
                if (di >= quotesGrid.Length) break;
                var row = quotesGrid[di];
                for (int si = 0; si < supplierEntities.Count; si++)
                {
                    if (si >= row.Length) break;
                    var (bgVat, chuaVat, isSelected) = row[si];
                    db.PurchaseEvaluationQuotes.Add(new PurchaseEvaluationQuote
                    {
                        PurchaseEvaluationDetailId = detailEntities[di].Id,
                        PurchaseEvaluationSupplierId = supplierEntities[si].Id,
                        BgVat = bgVat,
                        ChuaVat = chuaVat,
                        ThanhTien = detailEntities[di].KhoiLuongNganSach * chuaVat,
                        IsSelected = isSelected,
                    });
                }
            }
            await db.SaveChangesAsync();

            // Approval history theo flow của type
            PurchaseEvaluationPhase[] flow = type == PurchaseEvaluationType.DuyetNcc
                ? [PurchaseEvaluationPhase.ChoPurchasing, PurchaseEvaluationPhase.ChoCCM,
                   PurchaseEvaluationPhase.ChoCEODuyetNCC, PurchaseEvaluationPhase.DaDuyet]
                : [PurchaseEvaluationPhase.ChoPurchasing, PurchaseEvaluationPhase.ChoDuAn,
                   PurchaseEvaluationPhase.ChoCCM, PurchaseEvaluationPhase.ChoCEODuyetPA,
                   PurchaseEvaluationPhase.ChoCEODuyetNCC, PurchaseEvaluationPhase.DaDuyet];

            var current = PurchaseEvaluationPhase.DangSoanThao;
            foreach (var next in flow)
            {
                if (next > finalPhase && finalPhase != PurchaseEvaluationPhase.DaDuyet) break;
                if (next > finalPhase) break;
                Guid? actorId = next switch
                {
                    PurchaseEvaluationPhase.ChoPurchasing => qsHoang?.Id,    // Drafter submit
                    PurchaseEvaluationPhase.ChoDuAn => proPham?.Id,           // Procurement chuyển sang
                    PurchaseEvaluationPhase.ChoCCM => type == PurchaseEvaluationType.DuyetNcc
                        ? proPham?.Id                                          // A: Procurement
                        : pmNguyen?.Id,                                        // B: PM
                    PurchaseEvaluationPhase.ChoCEODuyetPA => ccmTran?.Id,    // CCM
                    PurchaseEvaluationPhase.ChoCEODuyetNCC => type == PurchaseEvaluationType.DuyetNcc
                        ? ccmTran?.Id                                          // A: CCM
                        : bodHuynh?.Id,                                        // B: CEO duyệt PA xong
                    PurchaseEvaluationPhase.DaDuyet => bodHuynh?.Id,         // CEO chốt
                    _ => qsHoang?.Id,
                };
                db.PurchaseEvaluationApprovals.Add(new PurchaseEvaluationApproval
                {
                    PurchaseEvaluationId = pe.Id,
                    FromPhase = current,
                    ToPhase = next,
                    ApproverUserId = actorId,
                    Decision = ApprovalDecision.Approve,
                    Comment = next switch
                    {
                        PurchaseEvaluationPhase.ChoPurchasing => "Demo seed — Drafter submit phiếu",
                        PurchaseEvaluationPhase.ChoCCM => "Demo seed — chuyển CCM kiểm tra ngân sách",
                        PurchaseEvaluationPhase.DaDuyet => "Demo seed — CEO duyệt chọn NCC",
                        _ => null,
                    },
                    ApprovedAt = nowUtc.AddMinutes(-(flow.Length - Array.IndexOf(flow, next)) * 30),
                });
                current = next;
            }
            pe.Phase = current;

            // Set winner nếu DaDuyet
            if (current == PurchaseEvaluationPhase.DaDuyet
                && winnerSupplierCode is not null
                && suppliersByCode.TryGetValue(winnerSupplierCode, out var winner))
            {
                pe.SelectedSupplierId = winner.Id;
            }

            return pe;
        }

        // ===========================================================
        // 1. Phiếu A — DangSoanThao (Drafter còn đang soạn)
        // ===========================================================
        var pe1 = await createDemoPeAsync(
            maPhieu: "A-001",
            type: PurchaseEvaluationType.DuyetNcc,
            finalPhase: PurchaseEvaluationPhase.DangSoanThao,
            tenGoiThau: "Cung cấp xi măng + sắt thép Q3/2026 — FLOCK 01",
            projectCode: "FLOCK01",
            diaDiem: "Lô K, KCN Lộc An",
            moTa: "Đợt 1: 200 tấn xi măng + 30 tấn thép cho phần thân.",
            supplierCodes: ["NCC-XIMANG", "NCC-THEP", "NCC-DIEN"],
            detailRows:
            [
                ("A.I", "Xi măng", "XM-001", "Xi măng PCB40 50kg", "tan", 200, 200, 1_800_000),
                ("A.I", "Xi măng", "XM-002", "Xi măng PCB30 50kg", "tan", 50, 50, 1_650_000),
                ("A.II", "Sắt thép", "TH-014", "Thép cây phi 14", "kg", 15000, 15000, 18_500),
                ("A.II", "Sắt thép", "TH-018", "Thép cây phi 18", "kg", 15000, 15000, 18_800),
            ],
            quotesGrid:
            [
                // Hạng 1 (xi măng PCB40) — chưa nhập (Drafter mới mở phiếu)
                [(0, 0, false), (0, 0, false), (0, 0, false)],
                [(0, 0, false), (0, 0, false), (0, 0, false)],
                [(0, 0, false), (0, 0, false), (0, 0, false)],
                [(0, 0, false), (0, 0, false), (0, 0, false)],
            ]);

        // ===========================================================
        // 2. Phiếu A — ChoCEODuyetNCC (sắp duyệt — đã có quotes + winner đề xuất)
        // ===========================================================
        var pe2 = await createDemoPeAsync(
            maPhieu: "A-002",
            type: PurchaseEvaluationType.DuyetNcc,
            finalPhase: PurchaseEvaluationPhase.ChoCEODuyetNCC,
            tenGoiThau: "Cung cấp gạch xây + cát đá Q2/2026 — Vinhomes Ocean Park",
            projectCode: "VHOMES-OP",
            diaDiem: "Vinhomes Ocean Park, Gia Lâm, HN",
            moTa: "Vật tư xây dựng đợt 2 cho block A1-A5.",
            supplierCodes: ["NCC-XIMANG", "NCC-THEP", "NCC-DIEN"],
            detailRows:
            [
                ("A.I", "Gạch", "GACH-001", "Gạch xây 4 lỗ 8x8x18", "vien", 50000, 50000, 1_500),
                ("A.II", "Cát đá", "CAT-001", "Cát vàng đổ bê tông", "m3", 200, 200, 350_000),
                ("A.II", "Cát đá", "DA-001", "Đá 1x2", "m3", 150, 150, 280_000),
            ],
            quotesGrid:
            [
                // Hạng 1: Gạch — supplier 1 thắng (báo giá thấp + cam kết)
                [(1_700, 1_550, true), (1_750, 1_590, false), (1_780, 1_618, false)],
                // Hạng 2: Cát vàng — supplier 1 thắng
                [(385_000, 350_000, true), (390_000, 354_000, false), (395_000, 359_000, false)],
                // Hạng 3: Đá 1x2 — supplier 1 thắng
                [(308_000, 280_000, true), (315_000, 286_000, false), (320_000, 290_000, false)],
            ]);

        // ===========================================================
        // 3. Phiếu A — DaDuyet (đã duyệt — chưa tạo HĐ — demo "Tạo HĐ từ phiếu")
        // ===========================================================
        var pe3 = await createDemoPeAsync(
            maPhieu: "A-003",
            type: PurchaseEvaluationType.DuyetNcc,
            finalPhase: PurchaseEvaluationPhase.DaDuyet,
            tenGoiThau: "Cung cấp ống PVC + phụ kiện cấp thoát nước — Resort Phú Quốc",
            projectCode: "RESORT-PQ",
            diaDiem: "Bãi Trường, Phú Quốc, Kiên Giang",
            moTa: "Hệ thống cấp thoát nước villa + biệt thự ven biển.",
            supplierCodes: ["NCC-XIMANG", "NCC-THEP"],
            detailRows:
            [
                ("A.I", "Ống nhựa", "ONG-090", "Ống PVC phi 90", "m", 2000, 2000, 65_000),
                ("A.I", "Ống nhựa", "ONG-110", "Ống PVC phi 110", "m", 1500, 1500, 95_000),
            ],
            quotesGrid:
            [
                [(71_500, 65_000, true), (72_600, 66_000, false)],
                [(104_500, 95_000, true), (106_700, 97_000, false)],
            ],
            winnerSupplierCode: "NCC-XIMANG",
            paymentTermsJson: """{"tamUng":20,"thanhToanTam":70,"quyetToan":10,"baoHanh":12,"hanMucCongNo":500000000,"danhGia":"Đối tác chiến lược 3 năm — uy tín cao."}""");

        // ===========================================================
        // 4. Phiếu B — ChoDuAn (5-step giữa chừng — Procurement đã chuyển)
        // ===========================================================
        var pe4 = await createDemoPeAsync(
            maPhieu: "B-001",
            type: PurchaseEvaluationType.DuyetNccPhuongAn,
            finalPhase: PurchaseEvaluationPhase.ChoDuAn,
            tenGoiThau: "Cung cấp + lắp đặt cẩu tháp 6 tháng — FLOCK 01",
            projectCode: "FLOCK01",
            diaDiem: "Lô K, KCN Lộc An",
            moTa: "Phương án A: thuê cẩu Liebherr 320 EC-H. Phương án B: thuê cẩu Potain MD 365.",
            supplierCodes: ["DV-VANCHUYEN", "DV-CLEAN", "NTP-XD"],
            detailRows:
            [
                ("A.I", "Cẩu tháp", "CT-001", "Cẩu tháp Liebherr 320 EC-H — phương án A", "thang", 6, 6, 90_000_000),
                ("A.II", "Vận chuyển", "VC-001", "Vận chuyển + lắp đặt cẩu tháp", "lan", 1, 1, 80_000_000),
                ("A.III", "Bảo trì", "BT-001", "Bảo trì cẩu tháp định kỳ", "lan", 6, 6, 5_000_000),
            ],
            quotesGrid:
            [
                [(99_000_000, 90_000_000, false), (104_500_000, 95_000_000, false), (96_800_000, 88_000_000, false)],
                [(88_000_000, 80_000_000, false), (93_500_000, 85_000_000, false), (82_500_000, 75_000_000, false)],
                [(5_500_000, 5_000_000, false), (5_775_000, 5_250_000, false), (5_280_000, 4_800_000, false)],
            ]);

        await db.SaveChangesAsync();
        logger.LogInformation("Seed 4 demo PE phiếu: {P1} {P2} {P3} {P4}",
            pe1.MaPhieu, pe2.MaPhieu, pe3.MaPhieu, pe4.MaPhieu);
    }

    // Phase 5.1 security: log warning nếu admin vẫn dùng password mặc định sau deploy production.
    private static async Task WarnDefaultAdminPasswordAsync(UserManager<User> userManager, ILogger logger)
    {
        var admin = await userManager.FindByEmailAsync(AdminEmail);
        if (admin is null) return;
        if (await userManager.CheckPasswordAsync(admin, AdminPassword))
        {
            logger.LogWarning(
                "⚠️  Admin user '{Email}' vẫn dùng password mặc định. ĐỔI NGAY trong production! " +
                "Xem docs/guides/security-checklist.md.", AdminEmail);
        }
    }

    // 12 role với ShortName (Vietnamese abbreviation) + Description (Vietnamese
    // full name). Identity Name = code English (kept cho FK + [Authorize] attr).
    // Idempotent: tạo role mới nếu chưa có, backfill ShortName/Description nếu null.
    private static readonly Dictionary<string, (string ShortName, string Description)> RoleLabels = new()
    {
        [AppRoles.Admin]            = ("QTV",   "Quản trị viên hệ thống"),
        [AppRoles.Drafter]          = ("NV.PB", "Nhân viên phòng ban (soạn thảo HĐ)"),
        [AppRoles.DeptManager]      = ("TPB",   "Trưởng phòng ban"),
        [AppRoles.ProjectManager]   = ("PM",    "Giám đốc dự án"),
        [AppRoles.Procurement]      = ("PRO",   "Phòng Cung ứng"),
        [AppRoles.CostControl]      = ("CCM",   "Phòng Kiểm soát chi phí"),
        [AppRoles.Finance]          = ("FIN",   "Phòng Tài chính"),
        [AppRoles.Accounting]       = ("ACT",   "Phòng Kế toán"),
        [AppRoles.Equipment]        = ("EQU",   "Phòng Thiết bị"),
        [AppRoles.Director]         = ("BOD",   "Ban Giám đốc"),
        [AppRoles.AuthorizedSigner] = ("NĐUQ",  "Người được Ủy quyền ký HĐ"),
        [AppRoles.HrAdmin]          = ("HRA",   "Phòng Nhân sự - Hành chính"),
    };

    private static async Task SeedRolesAsync(RoleManager<Role> roleManager, ILogger logger)
    {
        foreach (var roleName in AppRoles.All)
        {
            var (shortName, desc) = RoleLabels.TryGetValue(roleName, out var v)
                ? v : (roleName, roleName);

            var existing = await roleManager.FindByNameAsync(roleName);
            if (existing is null)
            {
                await roleManager.CreateAsync(new Role
                {
                    Name = roleName,
                    ShortName = shortName,
                    Description = desc,
                    CreatedAt = DateTime.UtcNow,
                });
                logger.LogInformation("Created role {Role} ({Short})", roleName, shortName);
            }
            else if (existing.ShortName != shortName || existing.Description != desc)
            {
                // Backfill labels for existing roles (post-migration)
                existing.ShortName = shortName;
                existing.Description = desc;
                await roleManager.UpdateAsync(existing);
                logger.LogInformation("Backfilled role {Role} labels", roleName);
            }
        }
    }

    private static async Task SeedAdminAsync(UserManager<User> userManager, ILogger logger)
    {
        var admin = await userManager.FindByEmailAsync(AdminEmail);
        if (admin is null)
        {
            admin = new User
            {
                UserName = AdminEmail,
                Email = AdminEmail,
                FullName = "Administrator",
                EmailConfirmed = true,
                IsActive = true,
                CreatedAt = DateTime.UtcNow,
            };
            var result = await userManager.CreateAsync(admin, AdminPassword);
            if (!result.Succeeded)
            {
                logger.LogError("Failed to seed admin: {Errors}", string.Join("; ", result.Errors.Select(e => e.Description)));
                return;
            }
            await userManager.AddToRoleAsync(admin, AppRoles.Admin);
            logger.LogInformation("Seeded admin user {Email}", AdminEmail);
        }
    }

    // 16 demo users covering full org chart — RECONCILE PATTERN (idempotent +
    // self-healing). Mỗi run:
    //   - Nếu user chưa có → create + assign roles + dept + position
    //   - Nếu user đã có → verify dept + position + roles, fix nếu drift
    //   - Try-catch per-user → 1 fail không abort 12 còn lại
    //   - Log chi tiết success/fixed/created/failed counts
    // Default password: User@123456 (warn log để rotate prod).
    private const string DemoUserPassword = "User@123456";

    private static async Task SeedDemoUsersAsync(
        ApplicationDbContext db, UserManager<User> userManager, ILogger logger)
    {
        // Lookup department Id theo Code
        var depts = await db.Departments.ToDictionaryAsync(d => d.Code, d => d.Id);
        if (depts.Count == 0)
        {
            logger.LogWarning("SeedDemoUsersAsync: skip — no departments seeded yet (run SeedDepartmentsAsync first).");
            return;
        }

        Guid? deptId(string code) => depts.TryGetValue(code, out var id) ? id : null;

        var demoUsers = new[]
        {
            // (Email, FullName, DeptCode, Position, RoleNames[])
            // BOD (3) — director + signer + admin assistant
            ("bod.huynh@solutions.com.vn",  "Huỳnh Văn Hùng",       "BOD", "Tổng Giám đốc",                       new[] { AppRoles.Director }),
            ("bod.le@solutions.com.vn",     "Lê Thị Mai",           "BOD", "Phó Giám đốc (NĐUQ)",                 new[] { AppRoles.AuthorizedSigner }),
            ("bod.tran@solutions.com.vn",   "Trần Quốc Bảo",        "BOD", "Phó Giám đốc Kỹ thuật (NĐUQ)",        new[] { AppRoles.AuthorizedSigner }),

            // PM (2) — giám đốc dự án
            ("pm.nguyen@solutions.com.vn",  "Nguyễn Quốc Cường",    "PM",  "Giám đốc Dự án FLOCK 01",             new[] { AppRoles.ProjectManager }),
            ("pm.le@solutions.com.vn",      "Lê Thanh Tùng",        "PM",  "Giám đốc Dự án Vinhomes Ocean Park",  new[] { AppRoles.ProjectManager }),

            // CCM/PRO/FIN/ACT/EQU/HRA — Trưởng phòng các phòng ban (1 mỗi)
            ("ccm.tran@solutions.com.vn",   "Trần Văn Bình",        "CCM", "Trưởng phòng Kiểm soát chi phí",      new[] { AppRoles.CostControl, AppRoles.DeptManager }),
            ("pro.pham@solutions.com.vn",   "Phạm Thị Hồng",        "PRO", "Trưởng phòng Cung ứng",               new[] { AppRoles.Procurement, AppRoles.DeptManager }),
            ("fin.do@solutions.com.vn",     "Đỗ Minh Tuấn",         "FIN", "Trưởng phòng Tài chính",              new[] { AppRoles.Finance, AppRoles.DeptManager }),
            ("act.vu@solutions.com.vn",     "Vũ Thị Lan",           "ACT", "Kế toán trưởng",                      new[] { AppRoles.Accounting, AppRoles.DeptManager }),
            ("equ.bui@solutions.com.vn",    "Bùi Văn Khánh",        "EQU", "Trưởng phòng Thiết bị",               new[] { AppRoles.Equipment, AppRoles.DeptManager }),
            ("hra.dang@solutions.com.vn",   "Đặng Thị Thanh",       "HRA", "Trưởng phòng Nhân sự - Hành chính",   new[] { AppRoles.HrAdmin, AppRoles.DeptManager }),

            // Drafter (5) — soạn thảo HĐ ở các phòng khác nhau
            ("qs.hoang@solutions.com.vn",   "Hoàng Văn Đức",        "QS",  "QS công trường — soạn thảo HĐ",       new[] { AppRoles.Drafter }),
            ("qs.ngo@solutions.com.vn",     "Ngô Thị Hà",           "QS",  "QS dự án FLOCK 01",                   new[] { AppRoles.Drafter }),
            ("nv.cao@solutions.com.vn",     "Cao Văn Long",         "PRO", "Nhân viên Cung ứng",                  new[] { AppRoles.Drafter }),
            ("nv.dinh@solutions.com.vn",    "Đinh Thị Yến",         "FIN", "Nhân viên Tài chính",                 new[] { AppRoles.Drafter }),
            ("nv.truong@solutions.com.vn",  "Trương Minh Quân",     "CCM", "Nhân viên Kiểm soát chi phí",         new[] { AppRoles.Drafter }),

            // ===== Solutions company users (real names — added 2026-04-25) =====
            // PRO — Phòng Cung ứng (5 user)
            ("tra.bui@solutions.com.vn",        "Bùi Lê Thủy Trà",         "PRO", "TP Cung ứng",                        new[] { AppRoles.Procurement, AppRoles.DeptManager }),
            ("phuong.nguyen@solutions.com.vn",  "Nguyễn Thị Bích Phượng",  "PRO", "Nhân viên Cung ứng",                 new[] { AppRoles.Procurement, AppRoles.Drafter }),
            ("thanh.lethanh@solutions.com.vn",  "Lê Thanh Bình",           "PRO", "Nhân viên Cung ứng",                 new[] { AppRoles.Procurement, AppRoles.Drafter }),
            ("long.chau@solutions.com.vn",      "Châu Tạ Kim Long",        "PRO", "Nhân viên Cung ứng",                 new[] { AppRoles.Procurement, AppRoles.Drafter }),
            ("duy.nguyen@solutions.com.vn",     "Nguyễn Văn Duy",          "PRO", "Nhân viên Cung ứng",                 new[] { AppRoles.Procurement, AppRoles.Drafter }),

            // CCM — Phòng Kiểm soát Chi phí (7 user)
            ("chuong.phan@solutions.com.vn",    "Phan Văn Chương",         "CCM", "TP Kiểm soát Chi phí",               new[] { AppRoles.CostControl, AppRoles.DeptManager }),
            ("binh.le@solutions.com.vn",        "Lê Văn Bình",             "CCM", "Nhân viên Kiểm soát Chi phí",        new[] { AppRoles.CostControl }),
            ("luu.tran@solutions.com.vn",       "Trần Xuân Lưu",           "CCM", "Nhân viên Kiểm soát Chi phí",        new[] { AppRoles.CostControl }),
            ("nguyen.ho@solutions.com.vn",      "Hồ Thị Nữ Nguyên",        "CCM", "Nhân viên Kiểm soát Chi phí",        new[] { AppRoles.CostControl }),
            ("anh.nguyen@solutions.com.vn",     "Nguyễn Thị Kim Ánh",      "CCM", "Nhân viên Kiểm soát Chi phí",        new[] { AppRoles.CostControl }),
            ("tring.le@solutions.com.vn",       "Lê Tự Đăng Trình",        "CCM", "Nhân viên Kiểm soát Chi phí",        new[] { AppRoles.CostControl }),
            ("truong.le@solutions.com.vn",      "Lê Trần Đăng Trường",     "CCM", "Nhân viên Kiểm soát Chi phí",        new[] { AppRoles.CostControl }),

            // ISO — đóng dấu HĐ (1 user, dept HRA)
            ("long.nguyen@solutions.com.vn",    "Nguyễn Hoàng Chánh Long", "HRA", "ISO — Đóng dấu HĐ",                   new[] { AppRoles.HrAdmin }),

            // CEO — Tổng Giám đốc (1 user, dept BOD)
            ("truong.nguyen@solutions.com.vn",  "Nguyễn Văn Trường",       "BOD", "Tổng Giám đốc (CEO)",                 new[] { AppRoles.Director }),
        };

        int created = 0, fixedExisting = 0, failed = 0;
        foreach (var (email, fullName, deptCode, position, roles) in demoUsers)
        {
            try
            {
                var existing = await userManager.FindByEmailAsync(email);
                if (existing is null)
                {
                    var user = new User
                    {
                        UserName = email,
                        Email = email,
                        FullName = fullName,
                        EmailConfirmed = true,
                        IsActive = true,
                        DepartmentId = deptId(deptCode),
                        Position = position,
                        CreatedAt = DateTime.UtcNow,
                    };
                    var result = await userManager.CreateAsync(user, DemoUserPassword);
                    if (!result.Succeeded)
                    {
                        logger.LogWarning("Demo user {Email} CREATE fail: {Err}",
                            email, string.Join("; ", result.Errors.Select(e => e.Description)));
                        failed++;
                        continue;
                    }
                    foreach (var role in roles)
                    {
                        var roleResult = await userManager.AddToRoleAsync(user, role);
                        if (!roleResult.Succeeded)
                            logger.LogWarning("Demo user {Email} AddRole {Role} fail: {Err}",
                                email, role, string.Join("; ", roleResult.Errors.Select(e => e.Description)));
                    }
                    created++;
                }
                else
                {
                    // Reconcile — verify dept + position + roles, fix nếu drift
                    bool changed = false;
                    var targetDeptId = deptId(deptCode);
                    if (existing.DepartmentId != targetDeptId)
                    {
                        existing.DepartmentId = targetDeptId; changed = true;
                    }
                    if (existing.Position != position)
                    {
                        existing.Position = position; changed = true;
                    }
                    if (existing.FullName != fullName)
                    {
                        existing.FullName = fullName; changed = true;
                    }
                    if (!existing.IsActive)
                    {
                        existing.IsActive = true; changed = true;
                    }
                    if (changed)
                    {
                        existing.UpdatedAt = DateTime.UtcNow;
                        await userManager.UpdateAsync(existing);
                    }

                    // Reconcile roles: ensure target roles có trong assigned set
                    var currentRoles = await userManager.GetRolesAsync(existing);
                    var missingRoles = roles.Where(r => !currentRoles.Contains(r)).ToList();
                    foreach (var role in missingRoles)
                    {
                        var addResult = await userManager.AddToRoleAsync(existing, role);
                        if (!addResult.Succeeded)
                            logger.LogWarning("Demo user {Email} AddRole {Role} fail: {Err}",
                                email, role, string.Join("; ", addResult.Errors.Select(e => e.Description)));
                        changed = true;
                    }

                    if (changed) fixedExisting++;
                }
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "SeedDemoUsersAsync: unhandled exception cho user {Email}", email);
                failed++;
            }
        }

        logger.LogInformation(
            "Demo users reconcile xong: {Created} created, {Fixed} fixed (drift), {Failed} failed. (Total demo: {Total}, password={Pwd} — ⚠ rotate prod)",
            created, fixedExisting, failed, demoUsers.Length, DemoUserPassword);
    }

    private static async Task SeedMenuTreeAsync(ApplicationDbContext db, ILogger logger)
    {
        var typeLabels = new Dictionary<string, string>
        {
            ["ThauPhu"] = "HĐ Thầu phụ",
            ["GiaoKhoan"] = "HĐ Giao khoán",
            ["NhaCungCap"] = "HĐ Nhà cung cấp",
            ["DichVu"] = "HĐ Dịch vụ",
            ["MuaBan"] = "HĐ Mua bán",
            ["NguyenTacNcc"] = "HĐ Nguyên tắc NCC",
            ["NguyenTacDv"] = "HĐ Nguyên tắc Dịch vụ",
        };

        var tree = new List<(string Key, string Label, string? Parent, int Order, string Icon)>
        {
            (MenuKeys.Dashboard,   "Tổng quan",        null,             10, "LayoutDashboard"),
            (MenuKeys.Master,      "Danh mục",         null,             20, "Database"),
            (MenuKeys.Suppliers,   "Nhà cung cấp",     MenuKeys.Master,  21, "Building2"),
            (MenuKeys.Projects,    "Dự án",            MenuKeys.Master,  22, "FolderKanban"),
            (MenuKeys.Departments, "Phòng ban",        MenuKeys.Master,  23, "Users"),
            // 4 master catalog cho Details add form (autocomplete suggestion)
            (MenuKeys.Catalogs,        "Danh mục chi tiết", MenuKeys.Master, 24, "Library"),
            (MenuKeys.CatalogUnits,    "Đơn vị tính",      MenuKeys.Catalogs, 1, "Ruler"),
            (MenuKeys.CatalogMaterials,"Vật tư / SP",      MenuKeys.Catalogs, 2, "Package"),
            (MenuKeys.CatalogServices, "Dịch vụ",          MenuKeys.Catalogs, 3, "Wrench"),
            (MenuKeys.CatalogWorkItems,"Hạng mục công việc", MenuKeys.Catalogs, 4, "ListChecks"),
            (MenuKeys.Contracts,   "Hợp đồng",         null,             30, "FileText"),
            (MenuKeys.Forms,       "Biểu mẫu",         null,             40, "FileSpreadsheet"),
            (MenuKeys.Reports,     "Báo cáo",          null,             50, "BarChart3"),
            (MenuKeys.System,      "Hệ thống",         null,             90, "Settings"),
            (MenuKeys.Users,       "Người dùng",       MenuKeys.System,  91, "User"),
            (MenuKeys.Roles,       "Vai trò",          MenuKeys.System,  92, "Shield"),
            (MenuKeys.Permissions, "Phân quyền",       MenuKeys.System,  93, "KeyRound"),
            (MenuKeys.Workflows,   "Quy trình HĐ",     MenuKeys.System,  94, "GitBranch"),
            // Module Duyệt NCC (tiền-HĐ)
            (MenuKeys.PurchaseEvaluations, "Quy trình chọn Thầu phụ - NCC", null, 25, "ClipboardCheck"),
            (MenuKeys.PeWorkflows,         "Quy trình Duyệt NCC",           MenuKeys.System,  95, "GitCompareArrows"),
            // Module Ngân sách (Phase 7)
            (MenuKeys.Budgets,       "Ngân sách",           null,            27, "Wallet"),
            (MenuKeys.BudgetList,    "Danh sách",           MenuKeys.Budgets, 1, "List"),
            (MenuKeys.BudgetCreate,  "Thao tác",            MenuKeys.Budgets, 2, "Plus"),
            (MenuKeys.BudgetPending, "Duyệt",               MenuKeys.Budgets, 3, "CheckCircle2"),
        };

        // Per-type sub-menu under Contracts: 1 group + 3 leaves each
        // (Danh sách / Thao tác / Duyệt).
        var order = 31;
        foreach (var code in MenuKeys.ContractTypeCodes)
        {
            var label = typeLabels.GetValueOrDefault(code, code);
            tree.Add((MenuKeys.ContractTypeGroup(code),  label,       MenuKeys.Contracts,                order++, "FileText"));
            tree.Add((MenuKeys.ContractTypeList(code),   "Danh sách", MenuKeys.ContractTypeGroup(code),  order++, "List"));
            tree.Add((MenuKeys.ContractTypeCreate(code), "Thao tác",  MenuKeys.ContractTypeGroup(code),  order++, "Plus"));
            tree.Add((MenuKeys.ContractTypePending(code),"Duyệt",     MenuKeys.ContractTypeGroup(code),  order++, "CheckCircle2"));
        }

        // Per-type workflow admin leaves dưới `Workflows` — mỗi loại là 1
        // menu item, click vào → mở designer scoped cho loại đó.
        var wfOrder = 95;
        foreach (var code in MenuKeys.ContractTypeCodes)
        {
            var label = typeLabels.GetValueOrDefault(code, code);
            tree.Add((MenuKeys.WorkflowTypeLeaf(code), label, MenuKeys.Workflows, wfOrder++, "FileText"));
        }

        // Pe_* group per PurchaseEvaluationType + 3 action leaves each
        var peTypeLabels = new Dictionary<string, string>
        {
            ["DuyetNcc"] = "Duyệt NCC",
            ["DuyetNccPhuongAn"] = "Duyệt NCC và Giải pháp",
        };
        var peOrder = 1;
        foreach (var code in MenuKeys.PurchaseEvaluationTypeCodes)
        {
            var label = peTypeLabels.GetValueOrDefault(code, code);
            tree.Add((MenuKeys.PurchaseEvaluationGroup(code),  label,       MenuKeys.PurchaseEvaluations, peOrder++, "FileCheck"));
            tree.Add((MenuKeys.PurchaseEvaluationList(code),   "Danh sách", MenuKeys.PurchaseEvaluationGroup(code), peOrder++, "List"));
            tree.Add((MenuKeys.PurchaseEvaluationCreate(code), "Thao tác",  MenuKeys.PurchaseEvaluationGroup(code), peOrder++, "Plus"));
            tree.Add((MenuKeys.PurchaseEvaluationPending(code),"Duyệt",     MenuKeys.PurchaseEvaluationGroup(code), peOrder++, "CheckCircle2"));
        }

        // PE workflow admin leaves dưới `PeWorkflows`
        var peWfOrder = 96;
        foreach (var code in MenuKeys.PurchaseEvaluationTypeCodes)
        {
            var label = peTypeLabels.GetValueOrDefault(code, code);
            tree.Add((MenuKeys.PeWorkflowTypeLeaf(code), label, MenuKeys.PeWorkflows, peWfOrder++, "FileCheck"));
        }

        var existingKeys = await db.MenuItems.Select(m => m.Key).ToListAsync();
        var added = 0;
        foreach (var (key, label, parent, o, icon) in tree)
        {
            if (existingKeys.Contains(key)) continue;
            db.MenuItems.Add(new MenuItem { Key = key, Label = label, ParentKey = parent, Order = o, Icon = icon });
            added++;
        }
        if (added > 0)
        {
            await db.SaveChangesAsync();
            logger.LogInformation("Seeded {Count} menu items", added);
        }

        // Backfill label updates for menu items renamed via business changes.
        // Add new entries here khi có rename — key → expected label. Idempotent.
        var labelBackfill = new Dictionary<string, string>
        {
            [MenuKeys.PurchaseEvaluationGroup("DuyetNcc")]         = "Duyệt NCC",
            [MenuKeys.PurchaseEvaluationGroup("DuyetNccPhuongAn")] = "Duyệt NCC và Giải pháp",
            [MenuKeys.PeWorkflowTypeLeaf("DuyetNcc")]              = "Duyệt NCC",
            [MenuKeys.PeWorkflowTypeLeaf("DuyetNccPhuongAn")]      = "Duyệt NCC và Giải pháp",
        };
        var updatedLabels = 0;
        foreach (var (key, expectedLabel) in labelBackfill)
        {
            var item = await db.MenuItems.FirstOrDefaultAsync(m => m.Key == key);
            if (item != null && item.Label != expectedLabel)
            {
                item.Label = expectedLabel;
                updatedLabels++;
            }
        }
        if (updatedLabels > 0)
        {
            await db.SaveChangesAsync();
            logger.LogInformation("Backfilled {Count} menu labels", updatedLabels);
        }

        // Backfill WorkflowDefinition name cho B (Phương Án → Giải pháp rename).
        var wfB = await db.PurchaseEvaluationWorkflowDefinitions
            .FirstOrDefaultAsync(w => w.Code == "QT-DN-B" && w.Version == 1);
        if (wfB != null && wfB.Name.Contains("Phương Án"))
        {
            wfB.Name = wfB.Name.Replace("Quy trình Duyệt NCC - Phương Án", "Quy trình Duyệt NCC và Giải pháp");
            await db.SaveChangesAsync();
            logger.LogInformation("Backfilled WorkflowDefinition QT-DN-B name");
        }
    }

    private static async Task SeedAdminPermissionsAsync(ApplicationDbContext db, RoleManager<Role> roleManager, ILogger logger)
    {
        var adminRole = await roleManager.FindByNameAsync(AppRoles.Admin);
        if (adminRole is null) return;

        var existingMenuKeys = await db.Permissions
            .Where(p => p.RoleId == adminRole.Id)
            .Select(p => p.MenuKey)
            .ToListAsync();

        var added = 0;
        foreach (var menuKey in MenuKeys.All)
        {
            if (existingMenuKeys.Contains(menuKey)) continue;
            db.Permissions.Add(new Permission
            {
                RoleId = adminRole.Id,
                MenuKey = menuKey,
                CanRead = true, CanCreate = true, CanUpdate = true, CanDelete = true,
            });
            added++;
        }
        if (added > 0)
        {
            await db.SaveChangesAsync();
            logger.LogInformation("Seeded {Count} admin permissions", added);
        }

        // Defaults cho Pe_* permissions per role — mỗi role business liên quan
        // PE module được grant CRUD level phù hợp lên 2 type (DuyetNcc /
        // DuyetNccPhuongAn).
        // Idempotent: skip per-(role,menuKey) đã có, chỉ add row mới.
        await SeedPurchaseEvaluationPermissionDefaultsAsync(db, roleManager, logger);
    }

    // Permission defaults cho module Duyệt NCC (Pe_*). Strategy:
    //   - Drafter / DeptManager / Procurement / CostControl / Director / AuthorizedSigner / ProjectManager
    //     đều có Read + Create + Update trên CẢ 2 type (xem được, soạn / chỉnh).
    //   - Workflow admin (PeWf_*) chỉ Admin (đã grant qua MenuKeys.All) — không cấp non-admin.
    //   - Idempotent: skip per-(roleId × menuKey) đã có row.
    private static async Task SeedPurchaseEvaluationPermissionDefaultsAsync(
        ApplicationDbContext db, RoleManager<Role> roleManager, ILogger logger)
    {
        // Roles cần access PE module
        var roleNames = new[]
        {
            AppRoles.Drafter, AppRoles.DeptManager,
            AppRoles.Procurement, AppRoles.CostControl,
            AppRoles.ProjectManager,
            AppRoles.Director, AppRoles.AuthorizedSigner,
        };

        // Menu keys cần grant: PurchaseEvaluations root + per-type group + 3 leaf
        var menuKeys = new List<string> { MenuKeys.PurchaseEvaluations };
        foreach (var typeCode in MenuKeys.PurchaseEvaluationTypeCodes)
        {
            menuKeys.Add(MenuKeys.PurchaseEvaluationGroup(typeCode));
            menuKeys.Add(MenuKeys.PurchaseEvaluationList(typeCode));
            menuKeys.Add(MenuKeys.PurchaseEvaluationCreate(typeCode));
            menuKeys.Add(MenuKeys.PurchaseEvaluationPending(typeCode));
        }

        int added = 0;
        foreach (var roleName in roleNames)
        {
            var role = await roleManager.FindByNameAsync(roleName);
            if (role is null) continue;

            var existing = await db.Permissions
                .Where(p => p.RoleId == role.Id && menuKeys.Contains(p.MenuKey))
                .Select(p => p.MenuKey)
                .ToListAsync();

            // Drafter có quyền Create + Update (soạn phiếu); Director chỉ Read +
            // Update (duyệt — xem + nhấn nút). Map default rõ ràng:
            bool canCreate = roleName is AppRoles.Drafter or AppRoles.DeptManager
                or AppRoles.Procurement;
            bool canUpdate = true;       // mọi role được transition (workflow check tách)
            bool canDelete = roleName == AppRoles.DeptManager;   // chỉ TPB xóa nháp

            foreach (var menuKey in menuKeys)
            {
                if (existing.Contains(menuKey)) continue;
                db.Permissions.Add(new Permission
                {
                    RoleId = role.Id,
                    MenuKey = menuKey,
                    CanRead = true,
                    CanCreate = canCreate,
                    CanUpdate = canUpdate,
                    CanDelete = canDelete,
                });
                added++;
            }
        }
        if (added > 0)
        {
            await db.SaveChangesAsync();
            logger.LogInformation("Seeded {Count} PE permission defaults across {Roles} roles", added, roleNames.Length);
        }
    }

    // 9 departments từ QT-TP-NCC.docx — reference data, không phải demo.
    // Mỗi role-based check trên workflow tham chiếu phòng ban này.
    private static async Task SeedDepartmentsAsync(ApplicationDbContext db, ILogger logger)
    {
        var departments = new (string Code, string Name, string? Note)[]
        {
            ("PM",  "Ban Quản lý Dự án",            "PM/PD: Giám đốc Dự án, Giám đốc Thi công"),
            ("QS",  "Phòng Quantity Surveyor",      "QS công trường — tính khối lượng + soạn HĐ"),
            ("CCM", "Phòng Kiểm soát Chi phí",      "Cost Control Management — review HĐ trước khi BOD ký"),
            ("PRO", "Phòng Cung ứng",               "Procurement — NCC vật tư / thầu phụ"),
            ("FIN", "Phòng Tài chính",              "Financial — điều khoản thanh toán, bảo lãnh"),
            ("ACT", "Phòng Kế toán",                "Accounting — thuế, hóa đơn"),
            ("EQU", "Phòng Thiết bị",               "Equipment — thuê/mua máy móc"),
            ("HRA", "Phòng Nhân sự - Hành chính",   "HRA/ISO — đóng dấu HĐ sau BOD ký"),
            ("BOD", "Ban Giám đốc",                 "Board of Directors — ký duyệt HĐ"),
        };

        var existingCodes = await db.Departments.Select(d => d.Code).ToListAsync();
        var added = 0;
        foreach (var (code, name, note) in departments)
        {
            if (existingCodes.Contains(code)) continue;
            db.Departments.Add(new Department { Code = code, Name = name, Note = note });
            added++;
        }
        if (added > 0)
        {
            await db.SaveChangesAsync();
            logger.LogInformation("Seeded {Count} departments", added);
        }
    }

    // Sample master data for UAT/demo — per-code idempotent (skip Code đã có,
    // add Code mới). Cho phép admin add/sửa supplier/project mà restart không
    // clobber, đồng thời cho phép expand seed list theo thời gian.
    private static async Task SeedDemoMasterDataAsync(ApplicationDbContext db, ILogger logger)
    {
        var now = DateTime.UtcNow;

        // 15 NCC covering 5 SupplierType (3 each)
        var suppliersToSeed = new[]
        {
            // NhaCungCap (5) — vật tư xây dựng
            new Supplier { Code = "NCC-VLXD", Name = "Công ty TNHH Vật liệu Xây dựng ABC", Type = SupplierType.NhaCungCap, TaxCode = "0100000001", Phone = "024 3888 1111", Email = "contact@vlxd-abc.vn", ContactPerson = "Nguyễn Văn An", Address = "123 Láng Hạ, Đống Đa, Hà Nội" },
            new Supplier { Code = "NCC-XIMANG", Name = "Công ty CP Xi măng Hà Tiên", Type = SupplierType.NhaCungCap, TaxCode = "0100000005", Phone = "028 3829 5555", Email = "sales@hatien.vn", ContactPerson = "Lê Minh Tuấn", Address = "82 Lê Lợi, Q1, TP.HCM" },
            new Supplier { Code = "NCC-THEP", Name = "Công ty CP Thép Hòa Phát", Type = SupplierType.NhaCungCap, TaxCode = "0100000006", Phone = "024 6266 4666", Email = "sales@hoaphat.com.vn", ContactPerson = "Trần Quốc Đạt", Address = "643 Phạm Văn Đồng, Bắc Từ Liêm, Hà Nội" },
            new Supplier { Code = "NCC-DIEN", Name = "Công ty TNHH Thiết bị Điện Cadivi", Type = SupplierType.NhaCungCap, TaxCode = "0100000007", Phone = "028 3925 9889", Email = "info@cadivi.com.vn", ContactPerson = "Hoàng Thị Lan", Address = "70 Nguyễn Tri Phương, Q5, TP.HCM" },
            new Supplier { Code = "NCC-NUOC", Name = "Công ty TNHH Vật tư Cấp thoát nước Tiền Phong", Type = SupplierType.NhaCungCap, TaxCode = "0100000008", Phone = "0225 3876 666", Email = "sales@tienphong-pvc.vn", ContactPerson = "Vũ Văn Bình", Address = "KCN Đình Vũ, Hải Phòng" },

            // NhaThauPhu (4) — thầu phụ thi công
            new Supplier { Code = "NTP-XD", Name = "Công ty CP Xây dựng Thăng Long", Type = SupplierType.NhaThauPhu, TaxCode = "0100000002", Phone = "024 3888 2222", Email = "info@thanglong-xd.vn", ContactPerson = "Trần Thị Bình", Address = "45 Nguyễn Chí Thanh, Đống Đa, Hà Nội" },
            new Supplier { Code = "NTP-COCONG", Name = "Công ty CP Xây dựng & Cơ khí Cô Công", Type = SupplierType.NhaThauPhu, TaxCode = "0100000009", Phone = "024 3776 8888", Email = "info@cocong.com.vn", ContactPerson = "Đỗ Mạnh Hùng", Address = "Khu CN Quang Minh, Mê Linh, Hà Nội" },
            new Supplier { Code = "NTP-MEPCO", Name = "Công ty TNHH MEP Hà Nội", Type = SupplierType.NhaThauPhu, TaxCode = "0100000010", Phone = "024 3556 9999", Email = "contact@mep-hn.vn", ContactPerson = "Nguyễn Thị Hồng", Address = "Cầu Giấy, Hà Nội", Note = "Chuyên thầu phụ M&E" },
            new Supplier { Code = "NTP-NEXTAGE", Name = "Công ty CP Hoàn thiện Nội thất Next Stage", Type = SupplierType.NhaThauPhu, TaxCode = "0100000011", Phone = "024 6688 7777", Email = "sales@nextstage.vn", ContactPerson = "Phạm Văn Khoa", Address = "Hà Đông, Hà Nội" },

            // ToDoi (3) — tổ đội nhân công
            new Supplier { Code = "TD-NEHOANG", Name = "Tổ đội Hoàng Nam", Type = SupplierType.ToDoi, Phone = "098 111 2233", ContactPerson = "Phạm Hoàng Nam", Address = "Cầu Giấy, Hà Nội", Note = "Tổ đội cốp pha 15 người" },
            new Supplier { Code = "TD-XAYTRAT", Name = "Tổ đội Xây trát Bắc Ninh", Type = SupplierType.ToDoi, Phone = "098 444 5566", ContactPerson = "Nguyễn Văn Khánh", Address = "Yên Phong, Bắc Ninh", Note = "Tổ đội xây trát 25 người" },
            new Supplier { Code = "TD-OPLAT", Name = "Tổ đội Ốp lát Hà Trang", Type = SupplierType.ToDoi, Phone = "097 222 8899", ContactPerson = "Trần Thị Hà", Address = "Hà Đông, Hà Nội", Note = "Tổ đội ốp lát + sơn bả 12 người" },

            // DonViDichVu (3) — dịch vụ
            new Supplier { Code = "DV-CLEAN", Name = "Công ty TNHH Vệ sinh Công nghiệp Clean Pro", Type = SupplierType.DonViDichVu, TaxCode = "0100000004", Phone = "024 3888 4444", Email = "sales@cleanpro.vn", Address = "Trung Hòa, Cầu Giấy, Hà Nội" },
            new Supplier { Code = "DV-VANCHUYEN", Name = "Công ty CP Vận chuyển Hồng Phát", Type = SupplierType.DonViDichVu, TaxCode = "0100000012", Phone = "024 3776 5555", Email = "info@hongphat-vc.vn", ContactPerson = "Lê Văn Dũng", Address = "Long Biên, Hà Nội", Note = "Vận chuyển vật tư + xe cẩu" },
            new Supplier { Code = "DV-BAOVE", Name = "Công ty Bảo vệ Long An", Type = SupplierType.DonViDichVu, TaxCode = "0100000013", Phone = "024 3556 6666", Email = "service@longan-bv.vn", Address = "Thanh Xuân, Hà Nội" },

            // ChuDauTu (3) — chủ đầu tư (bypass CCM)
            new Supplier { Code = "CDT-VIN", Name = "Tập đoàn Vingroup", Type = SupplierType.ChuDauTu, TaxCode = "0100109106", Phone = "024 3974 9999", Email = "contact@vingroup.net", Address = "7 Bằng Lăng 1, Vinhomes Riverside, Long Biên, Hà Nội", Note = "Chủ đầu tư - bypass CCM" },
            new Supplier { Code = "CDT-SUNGROUP", Name = "Tập đoàn Sun Group", Type = SupplierType.ChuDauTu, TaxCode = "0100000014", Phone = "024 3939 8888", Email = "contact@sungroup.com.vn", Address = "Hoàn Kiếm, Hà Nội", Note = "Chủ đầu tư - bypass CCM" },
            new Supplier { Code = "CDT-MASTERISE", Name = "Masterise Homes", Type = SupplierType.ChuDauTu, TaxCode = "0100000015", Phone = "028 3939 7777", Email = "contact@masterisehomes.com", Address = "Q2, TP.HCM", Note = "Chủ đầu tư - bypass CCM" },
        };

        var existingSupplierCodes = await db.Suppliers.Select(s => s.Code).ToListAsync();
        int addedSuppliers = 0;
        foreach (var s in suppliersToSeed)
        {
            if (existingSupplierCodes.Contains(s.Code)) continue;
            db.Suppliers.Add(s);
            addedSuppliers++;
        }
        if (addedSuppliers > 0) logger.LogInformation("Seeded {Count} demo suppliers", addedSuppliers);

        // 8 dự án (đa dạng quy mô + giai đoạn)
        var projectsToSeed = new[]
        {
            new Project { Code = "FLOCK01",     Name = "Dự án FLOCK 01 — Khu đô thị Mỹ Đình",       StartDate = now.AddMonths(-3),  EndDate = now.AddMonths(18), BudgetTotal = 120_000_000_000m, Note = "Dự án mẫu — demo" },
            new Project { Code = "SKYGARDEN",   Name = "Sky Garden Residence",                        StartDate = now.AddMonths(-6),  EndDate = now.AddMonths(12), BudgetTotal = 85_000_000_000m,  Note = "Dự án mẫu — demo" },
            new Project { Code = "INDUSTRIAL",  Name = "Nhà máy Công nghiệp Yên Phong",               StartDate = now.AddMonths(-1),  EndDate = now.AddMonths(9),  BudgetTotal = 45_000_000_000m,  Note = "Dự án mẫu — demo" },
            new Project { Code = "VHOMES-OP",   Name = "Vinhomes Ocean Park 3 — Khu Sapphire",        StartDate = now.AddMonths(-12), EndDate = now.AddMonths(24), BudgetTotal = 350_000_000_000m, Note = "Khu đô thị quy mô lớn" },
            new Project { Code = "ECOPARK-VL",  Name = "Ecopark Văn Lâm — Tổ hợp biệt thự",           StartDate = now.AddMonths(-4),  EndDate = now.AddMonths(20), BudgetTotal = 180_000_000_000m, Note = "Biệt thự + nhà phố" },
            new Project { Code = "BWTOWER",     Name = "Tòa nhà Văn phòng BW Tower — Cầu Giấy",       StartDate = now.AddMonths(2),   EndDate = now.AddMonths(28), BudgetTotal = 95_000_000_000m,  Note = "Văn phòng cho thuê 25 tầng" },
            new Project { Code = "WAREHOUSE-LB",Name = "Kho vận Long Biên — Phase 1",                 StartDate = now.AddMonths(-2),  EndDate = now.AddMonths(8),  BudgetTotal = 32_000_000_000m,  Note = "Kho lạnh + kho thường" },
            new Project { Code = "RESORT-PQ",   Name = "Resort 5* Phú Quốc — Bãi Trường",             StartDate = now.AddMonths(-8),  EndDate = now.AddMonths(16), BudgetTotal = 220_000_000_000m, Note = "Resort 250 phòng + biệt thự" },
        };

        var existingProjectCodes = await db.Projects.Select(p => p.Code).ToListAsync();
        int addedProjects = 0;
        foreach (var p in projectsToSeed)
        {
            if (existingProjectCodes.Contains(p.Code)) continue;
            db.Projects.Add(p);
            addedProjects++;
        }
        if (addedProjects > 0) logger.LogInformation("Seeded {Count} demo projects", addedProjects);

        if (addedSuppliers + addedProjects > 0)
            await db.SaveChangesAsync();
    }

    private static async Task SeedContractTemplatesAsync(ApplicationDbContext db, ILogger logger)
    {
        // Chỉ IsActive=true nếu file thực tế tồn tại trong wwwroot/templates/.
        // 3 file .doc (FO-002.02/03/06) chưa convert sẽ IsActive=false.
        var templates = new (string FormCode, string Name, ContractType? Type, string FileName, string Format, string? Description)[]
        {
            ("SOL-CCM-FO-002.01", "Bảng kiểm tra hợp đồng", null,
                "SOL-CCM-FO-002.01.v01 Bang kiem tra hop dong.docx", "docx",
                "Checklist duyệt HĐ theo 4 bộ phận: Đề xuất / Cung ứng / CCM / Giám đốc"),
            ("SOL-CCM-FO-002.02", "Hợp đồng trọn gói — Nhân công + Vật tư", ContractType.HopDongThauPhu,
                "SOL-CCM-FO-002.02.v01 Hop dong tron goi nhan cong, vat tu.docx", "docx",
                "Template HĐ thầu phụ trọn gói (cần convert .doc → .docx qua Word COM)"),
            ("SOL-CCM-FO-002.03", "Hợp đồng trọn gói — Nhân công + Thiết bị", ContractType.HopDongThauPhu,
                "SOL-CCM-FO-002.03.v01 Hop dong trong goi nhan cong, thiet bi.docx", "docx",
                "Template HĐ thầu phụ trọn gói (cần convert .doc → .docx qua Word COM)"),
            ("SOL-CCM-FO-002.04", "Điều kiện chung hợp đồng trọn gói", null,
                "SOL-CCM-FO-002.04.v01 Dieu kien chung hop dong tron goi.docx", "docx",
                "Appendix điều khoản chung đính kèm HĐ trọn gói"),
            ("SOL-CCM-FO-002.05", "Hợp đồng Giao khoán", ContractType.HopDongGiaoKhoan,
                "SOL-CCM-FO-002.05.v01 Hop dong Giao khoan.docx", "docx",
                "Template HĐ giao khoán với Tổ đội / Nhân công"),
            ("SOL-CCM-FO-002.06", "Hợp đồng mua bán", ContractType.HopDongMuaBan,
                "SOL-CCM-FO-002.06.v01 Hop dong mua ban.docx", "docx",
                "Template HĐ mua bán NCC (cần convert .doc → .docx qua Word COM)"),
            ("SOL-CCM-FO-002.07", "Đơn đặt hàng (PO)", null,
                "SOL-CCM-FO-002.07.V01 Don dat hang.xlsx", "xlsx",
                "Template PO Excel — 3 sheet: so sánh giá, PO chính, PO NLT"),
            ("SOL-CCM-RG-001", "Quy định mã số hợp đồng", null,
                "SOL-CCM-RG-001.v02 QD ma so hop dong.docx", "docx",
                "Regulation mã HĐ + PO — reference doc"),
        };

        var existingCodes = await db.ContractTemplates.Select(t => t.FormCode).ToListAsync();
        var wwwroot = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
        var added = 0;
        foreach (var (formCode, name, type, fileName, format, desc) in templates)
        {
            if (existingCodes.Contains(formCode)) continue;

            var templatePath = $"templates/{fileName}";
            var absPath = Path.Combine(wwwroot, "templates", fileName);
            var fileExists = File.Exists(absPath);

            db.ContractTemplates.Add(new ContractTemplate
            {
                FormCode = formCode,
                Name = name,
                ContractType = type,
                FileName = fileName,
                StoragePath = templatePath,
                Format = format,
                Description = desc,
                IsActive = fileExists,
            });
            added++;
        }
        if (added > 0)
        {
            await db.SaveChangesAsync();
            logger.LogInformation("Seeded {Count} contract templates (active file check)", added);
        }
    }
}
