using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.PurchaseEvaluations;
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.PurchaseEvaluations;
using SolutionErp.Infrastructure.Tests.Common;
using SolutionErp.Infrastructure.Tests.Services;   // NoOpNotificationService (reuse internal helper)

namespace SolutionErp.Infrastructure.Tests.Application;

// ===== FEATURE A (S69 anh Kiệt FDC) — urgent-toggle authz =====
// SetPurchaseEvaluationUrgentCommandHandler (PurchaseEvaluationUrgentFeatures.cs).
// Test theo CODE đã land (S34 rule — KHÔNG touch production).
//
// [SPEC CHANGE — FINAL anh chốt, 2026-06-19] Gate BẤT ĐỐI XỨNG theo request.IsUrgent
// (KHÁC bản S77 trước đó vốn ĐỐI XỨNG yêu cầu DeptManager cho cả set + unset):
//
//   SET  (IsUrgent=true)  — "ai làm người đó gắn": CHỈ cần role chức năng.
//       • Procurement (KHÔNG cần DeptManager)  → set IsUrgentByPro  (cờ ĐỎ)
//       • CostControl (KHÔNG cần DeptManager)  → set IsUrgentByCcm  (cờ XANH)
//       • Admin                                 → set CẢ 2 cờ
//       • role khác                             → ForbiddenException ("*đánh dấu phiếu gấp*")
//
//   UNSET (IsUrgent=false) — "gỡ thì chỉ cho TP gỡ": role chức năng VÀ DeptManager.
//       • Procurement + DeptManager  → clear IsUrgentByPro
//       • CostControl + DeptManager  → clear IsUrgentByCcm
//       • Admin                       → clear CẢ 2 (KHÔNG cần DeptManager)
//       • plain Procurement (no DeptManager) → ForbiddenException + NO mutation
//       • plain CostControl (no DeptManager) → ForbiddenException + NO mutation
//       • role khác                            → ForbiddenException ("*GỠ cờ gấp*")
//
// [S85 anh Kiệt — NGƯỜI KHAI tự gỡ cờ của mình] UNSET branch mở thêm 1 lối:
//   chính người khai phiếu (actor.UserId == entity.DrafterUserId) được GỠ cờ — và gỡ
//   CẢ 2 cờ (un-urgent phiếu), GIỐNG Admin. SET branch KHÔNG đổi (drafter vẫn không tự
//   GẮN — chỉ role chức năng/Admin gắn). Production (PurchaseEvaluationUrgentFeatures.cs):
//     UNSET allow = isAdmin || (hasPro && isDeptManager) || (hasCcm && isDeptManager) || isDrafter
//     isDrafter ⇒ clear CẢ 2 (cùng nhánh với isAdmin, line 70).
//
// Production gate (SET — PurchaseEvaluationUrgentFeatures.cs:57):
//   if (!isAdmin && !hasPro && !hasCcm) throw Forbidden("*đánh dấu phiếu gấp*");
// Production gate (UNSET — line 66-69):
//   isProTp = hasPro && isDeptManager; isCcmTp = hasCcm && isDeptManager;
//   if (!isAdmin && !isProTp && !isCcmTp && !isDrafter) throw Forbidden("*GỠ cờ gấp*");
//
// Notify CEO (Director) là best-effort try/catch khi false→true → KHÔNG assert
//   notification ở đây (focus = flag-setting + authz). NoOpNotificationService nuốt
//   call → try block không throw. UserManager lấy từ IdentityFixture.
//
// Handler 4 dep: (IApplicationDbContext, ICurrentUser, UserManager<User>, INotificationService).
// FakeCurrentUser configurable Roles per scenario.
public class PeUrgentToggleAuthzTests
{
    // ICurrentUser stub — Roles set per test (giống FakeCurrentUser PeWorkItemGuardTests
    // nhưng configurable roles thay vì hardcode Drafter). UserId settable (init) để test
    // [S85] nhánh drafter (actor.UserId == entity.DrafterUserId) — default random khi
    // không quan tâm danh tính (mọi test cũ không phụ thuộc UserId cụ thể).
    private sealed class FakeCurrentUser(params string[] roles) : ICurrentUser
    {
        public Guid? UserId { get; init; } = Guid.NewGuid();
        public string? Email { get; } = "actor@test.local";
        public string? FullName { get; } = "Actor Test";
        public IReadOnlyList<string> Roles { get; } = roles ?? Array.Empty<string>();
        public bool IsAuthenticated => UserId is not null;
    }

    private static SetPurchaseEvaluationUrgentCommandHandler BuildHandler(
        TestApplicationDbContext db, UserManager<User> um, ICurrentUser currentUser)
        => new(db, currentUser, um, new NoOpNotificationService());

    // PE chưa gấp (cả 2 cờ false) — state mặc định để verify role nào set cờ nào.
    // [S85] drafterUserId tùy chọn: gán = actor.UserId để test nhánh "người khai tự gỡ".
    // Default random (khác mọi FakeCurrentUser.UserId) → các test role-only KHÔNG vô tình
    // dính nhánh drafter.
    private static async Task<PurchaseEvaluation> SeedPeAsync(
        TestApplicationDbContext db,
        bool urgentByPro = false,
        bool urgentByCcm = false,
        string code = "PE-URG-001",
        Guid? drafterUserId = null)
    {
        var pe = new PurchaseEvaluation
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = PurchaseEvaluationPhase.ChoDuyet,
            MaPhieu = code,
            TenGoiThau = "Gói thầu test urgent",
            ProjectId = Guid.NewGuid(),
            DrafterUserId = drafterUserId ?? Guid.NewGuid(),
            IsUrgentByPro = urgentByPro,
            IsUrgentByCcm = urgentByCcm,
        };
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);
        return pe;
    }

    // =====================================================================
    // 1. SET — plain Procurement (KHÔNG DeptManager) → OK, set CHỈ IsUrgentByPro=true,
    //    ByCcm KHÔNG đụng. "Ai làm người đó gắn" — không cần Trưởng phòng để bật.
    // =====================================================================
    [Fact]
    public async Task PlainProcurement_Set_SetsOnlyIsUrgentByPro_CcmUntouched()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var pe = await SeedPeAsync(db);
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Procurement));

        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByPro.Should().BeTrue("PRO set cờ ĐỎ (ByPro) khi bật — chỉ cần role Procurement");
        reloaded.IsUrgentByCcm.Should().BeFalse("PRO KHÔNG đụng cờ XANH (ByCcm)");
    }

    // =====================================================================
    // 2. SET — plain CostControl (KHÔNG DeptManager) → OK, set CHỈ IsUrgentByCcm=true,
    //    ByPro KHÔNG đụng. Đối xứng test 1 cho phía CCM.
    // =====================================================================
    [Fact]
    public async Task PlainCostControl_Set_SetsOnlyIsUrgentByCcm_ProUntouched()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var pe = await SeedPeAsync(db, code: "PE-URG-002");
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.CostControl));

        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByCcm.Should().BeTrue("CCM set cờ XANH (ByCcm) khi bật — chỉ cần role CostControl");
        reloaded.IsUrgentByPro.Should().BeFalse("CCM KHÔNG đụng cờ ĐỎ (ByPro)");
    }

    // =====================================================================
    // 3. SET — Admin → set CẢ 2 cờ true (KHÔNG cần DeptManager — superuser).
    // =====================================================================
    [Fact]
    public async Task Admin_Set_SetsBothFlags()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var pe = await SeedPeAsync(db, code: "PE-URG-003");
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Admin));

        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByPro.Should().BeTrue("Admin set cả ĐỎ");
        reloaded.IsUrgentByCcm.Should().BeTrue("Admin set cả XANH");
    }

    // =====================================================================
    // 4. UNSET — plain Procurement (KHÔNG DeptManager) → ForbiddenException + NO mutation.
    //    "Gỡ thì chỉ cho TP gỡ": NV PRO thường KHÔNG được gỡ cờ → cờ ĐỎ giữ true.
    // =====================================================================
    [Fact]
    public async Task PlainProcurement_Unset_ThrowsForbidden_FlagStillTrue()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        // Pre-seed cờ ĐỎ đang bật để chứng minh GỠ bị chặn KHÔNG mutate.
        var pe = await SeedPeAsync(db, urgentByPro: true, code: "PE-URG-004");
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Procurement));

        var act = async () => await handler.Handle(
            new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: false), CancellationToken.None);

        await act.Should().ThrowAsync<ForbiddenException>()
            .WithMessage("*GỠ cờ gấp*");

        // Guard throw TRƯỚC mutate → cờ ĐỎ giữ nguyên true (NV thường không gỡ được).
        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByPro.Should().BeTrue("PRO thường bị chặn gỡ → cờ ĐỎ giữ nguyên");
        reloaded.IsUrgentByCcm.Should().BeFalse();
    }

    // =====================================================================
    // 5. UNSET — plain CostControl (KHÔNG DeptManager) → ForbiddenException + NO mutation.
    //    Đối xứng test 4 cho phía CCM (cờ XANH giữ true).
    // =====================================================================
    [Fact]
    public async Task PlainCostControl_Unset_ThrowsForbidden_FlagStillTrue()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var pe = await SeedPeAsync(db, urgentByCcm: true, code: "PE-URG-005");
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.CostControl));

        var act = async () => await handler.Handle(
            new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: false), CancellationToken.None);

        await act.Should().ThrowAsync<ForbiddenException>()
            .WithMessage("*GỠ cờ gấp*");

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByCcm.Should().BeTrue("CCM thường bị chặn gỡ → cờ XANH giữ nguyên");
        reloaded.IsUrgentByPro.Should().BeFalse();
    }

    // =====================================================================
    // 6. UNSET — Procurement + DeptManager → OK, clear IsUrgentByPro=false.
    //    Trưởng phòng PRO ĐƯỢC gỡ cờ ĐỎ. ByCcm KHÔNG đụng (vd CCM bật trước đó vẫn giữ).
    // =====================================================================
    [Fact]
    public async Task ProcurementDeptManager_Unset_ClearsOnlyProFlag_CcmPreserved()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        // State: cả 2 cờ đang bật (PRO ĐỎ + CCM XANH).
        var pe = await SeedPeAsync(db, urgentByPro: true, urgentByCcm: true, code: "PE-URG-006");
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Procurement, AppRoles.DeptManager));

        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: false), CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByPro.Should().BeFalse("Trưởng phòng PRO gỡ cờ ĐỎ của mình");
        reloaded.IsUrgentByCcm.Should().BeTrue("cờ XANH của CCM giữ nguyên — PRO không đụng");
    }

    // =====================================================================
    // 7. UNSET — CostControl + DeptManager → OK, clear IsUrgentByCcm=false.
    //    Đối xứng test 6 cho phía CCM. ByPro giữ nguyên.
    // =====================================================================
    [Fact]
    public async Task CostControlDeptManager_Unset_ClearsOnlyCcmFlag_ProPreserved()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var pe = await SeedPeAsync(db, urgentByPro: true, urgentByCcm: true, code: "PE-URG-007");
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.CostControl, AppRoles.DeptManager));

        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: false), CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByCcm.Should().BeFalse("Trưởng phòng CCM gỡ cờ XANH của mình");
        reloaded.IsUrgentByPro.Should().BeTrue("cờ ĐỎ của PRO giữ nguyên — CCM không đụng");
    }

    // =====================================================================
    // 8. UNSET — Admin → clear CẢ 2 cờ (KHÔNG cần DeptManager — superuser).
    // =====================================================================
    [Fact]
    public async Task Admin_Unset_ClearsBothFlags()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var pe = await SeedPeAsync(db, urgentByPro: true, urgentByCcm: true, code: "PE-URG-008");
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Admin));

        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: false), CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByPro.Should().BeFalse("Admin gỡ cả ĐỎ");
        reloaded.IsUrgentByCcm.Should().BeFalse("Admin gỡ cả XANH");
    }

    // =====================================================================
    // 9a. SET — role ngoài allow-list (Drafter) → ForbiddenException, KHÔNG lưu gì.
    //     Message nhánh SET ("*đánh dấu phiếu gấp*").
    // =====================================================================
    [Fact]
    public async Task Drafter_Set_ThrowsForbidden_NoFlagSet()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var pe = await SeedPeAsync(db, code: "PE-URG-009A");
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Drafter));

        var act = async () => await handler.Handle(
            new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        await act.Should().ThrowAsync<ForbiddenException>()
            .WithMessage("*đánh dấu phiếu gấp*");

        // Side-effect: guard throw TRƯỚC mutate → cả 2 cờ giữ false.
        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByPro.Should().BeFalse("Drafter bị chặn → không set cờ");
        reloaded.IsUrgentByCcm.Should().BeFalse();
    }

    // =====================================================================
    // 9b. SET — Finance (role thường khác) → ForbiddenException. Chắc chắn KHÔNG chỉ
    //     Drafter bị chặn — mọi role ngoài {Procurement, CostControl, Admin} đều bị chặn.
    // =====================================================================
    [Fact]
    public async Task Finance_Set_ThrowsForbidden()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var pe = await SeedPeAsync(db, code: "PE-URG-009B");
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Finance));

        var act = async () => await handler.Handle(
            new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        await act.Should().ThrowAsync<ForbiddenException>()
            .WithMessage("*đánh dấu phiếu gấp*");
    }

    // =====================================================================
    // 9c. PE không tồn tại → NotFoundException (existence check đầu handler, TRƯỚC authz).
    //     Dùng plain Procurement để chắc NotFound bắn trước cả khi role hợp lệ.
    // =====================================================================
    [Fact]
    public async Task UnknownPe_ThrowsNotFound()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Procurement));

        var act = async () => await handler.Handle(
            new SetPurchaseEvaluationUrgentCommand(Guid.NewGuid(), IsUrgent: true), CancellationToken.None);

        await act.Should().ThrowAsync<NotFoundException>();
    }

    // =====================================================================
    // [S85 anh Kiệt — NGƯỜI KHAI tự gỡ cờ của mình] Nhánh drafter mới ở UNSET.
    // =====================================================================

    // ---------------------------------------------------------------------
    // 10. UNSET — Drafter (người KHAI phiếu) role THƯỜNG (KHÔNG PRO/CCM/TP/Admin) →
    //     OK gỡ, clear CẢ 2 cờ. Trục chính S85: actor.UserId == DrafterUserId mở lối gỡ,
    //     và gỡ giống Admin (un-urgent toàn phiếu) dù không có role chức năng nào.
    //     Pre-seed cờ ĐỎ (ByPro) đang bật để chứng minh CLEAR thật, không phải vốn-dĩ false.
    // ---------------------------------------------------------------------
    [Fact]
    public async Task Drafter_Unset_ClearsBothFlags_NoForbidden()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var drafterId = Guid.NewGuid();
        // Phiếu do drafterId khai, cờ ĐỎ đang bật (vd PRO bật trước đó).
        var pe = await SeedPeAsync(db, urgentByPro: true, code: "PE-URG-010", drafterUserId: drafterId);
        // Actor = chính drafter, role THƯỜNG (Drafter — KHÔNG Procurement/CostControl/DeptManager/Admin),
        // UserId trùng DrafterUserId của phiếu.
        var actor = new FakeCurrentUser(AppRoles.Drafter) { UserId = drafterId };
        var handler = BuildHandler(db, um, actor);

        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: false), CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByPro.Should().BeFalse("người khai gỡ cờ → clear ĐỎ");
        reloaded.IsUrgentByCcm.Should().BeFalse("người khai gỡ → clear CẢ 2 (giống Admin)");
    }

    // ---------------------------------------------------------------------
    // 11. UNSET — non-drafter + non-TP + non-admin (Finance, role trung tính) →
    //     ForbiddenException, cờ KHÔNG đổi. Chốt: chỉ MỞ lối gỡ cho đúng người khai —
    //     người KHÁC role thường vẫn bị chặn (UserId ngẫu nhiên ≠ DrafterUserId).
    // ---------------------------------------------------------------------
    [Fact]
    public async Task NonDrafterPlainRole_Unset_ThrowsForbidden_NoMutation()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        // Phiếu của 1 drafter KHÁC; cả 2 cờ đang bật.
        var pe = await SeedPeAsync(db, urgentByPro: true, urgentByCcm: true,
            code: "PE-URG-011", drafterUserId: Guid.NewGuid());
        // Actor = Finance (role thường), UserId random → KHÔNG phải drafter, KHÔNG TP.
        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Finance));

        var act = async () => await handler.Handle(
            new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: false), CancellationToken.None);

        await act.Should().ThrowAsync<ForbiddenException>()
            .WithMessage("*GỠ cờ gấp*");

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByPro.Should().BeTrue("guard throw TRƯỚC mutate → cờ ĐỎ giữ nguyên");
        reloaded.IsUrgentByCcm.Should().BeTrue("cờ XANH giữ nguyên — người ngoài không gỡ được");
    }

    // ---------------------------------------------------------------------
    // 12. SET — Drafter (người khai) role THƯỜNG cố GẮN cờ → ForbiddenException.
    //     Drafter CHỈ được GỠ, KHÔNG được GẮN (SET branch vẫn role-only, không xét drafter).
    //     Message nhánh SET ("*đánh dấu phiếu gấp*"). Cờ giữ false (không set).
    // ---------------------------------------------------------------------
    [Fact]
    public async Task Drafter_Set_ThrowsForbidden_DrafterCannotMark()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var drafterId = Guid.NewGuid();
        var pe = await SeedPeAsync(db, code: "PE-URG-012", drafterUserId: drafterId);
        // Actor = chính người khai nhưng role thường + IsUrgent=true (GẮN).
        var actor = new FakeCurrentUser(AppRoles.Drafter) { UserId = drafterId };
        var handler = BuildHandler(db, um, actor);

        var act = async () => await handler.Handle(
            new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        await act.Should().ThrowAsync<ForbiddenException>()
            .WithMessage("*đánh dấu phiếu gấp*");

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByPro.Should().BeFalse("drafter chỉ GỠ, không GẮN → không set cờ");
        reloaded.IsUrgentByCcm.Should().BeFalse();
    }

    // ---------------------------------------------------------------------
    // 13. UNSET — Drafter gỡ khi CHỈ cờ XANH (ByCcm) đang bật → vẫn clear CẢ 2.
    //     Chứng minh drafter-clear KHÔNG phụ thuộc cờ nào đang bật (un-urgent toàn phiếu).
    // ---------------------------------------------------------------------
    [Fact]
    public async Task Drafter_Unset_OnlyCcmFlagSet_ClearsBoth()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var drafterId = Guid.NewGuid();
        var pe = await SeedPeAsync(db, urgentByCcm: true, code: "PE-URG-013", drafterUserId: drafterId);
        var actor = new FakeCurrentUser(AppRoles.Drafter) { UserId = drafterId };
        var handler = BuildHandler(db, um, actor);

        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: false), CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByCcm.Should().BeFalse("người khai gỡ cờ XANH");
        reloaded.IsUrgentByPro.Should().BeFalse("vốn-dĩ false, vẫn false sau clear");
    }

    // ---------------------------------------------------------------------
    // 14. UNSET — Drafter ĐỒNG THỜI có role Procurement nhưng KHÔNG DeptManager → gỡ
    //     vẫn clear CẢ 2 qua nhánh drafter (isDrafter ưu tiên/độc lập với isProTp).
    //     Đối chứng test 6 (TP-PRO chỉ clear cờ vai): cùng có role PRO nhưng vì là người
    //     khai → gỡ TOÀN phiếu, KHÔNG bị giới hạn role-keyed.
    // ---------------------------------------------------------------------
    [Fact]
    public async Task DrafterWithProcurementButNoDeptManager_Unset_ClearsBoth()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var drafterId = Guid.NewGuid();
        // Cả 2 cờ bật; nếu đi nhánh role-keyed (isProTp) sẽ CHỈ clear ByPro, ByCcm còn true.
        // Vì là drafter → phải clear CẢ 2.
        var pe = await SeedPeAsync(db, urgentByPro: true, urgentByCcm: true,
            code: "PE-URG-014", drafterUserId: drafterId);
        // role Procurement nhưng KHÔNG DeptManager (nên isProTp=false) + là người khai.
        var actor = new FakeCurrentUser(AppRoles.Procurement) { UserId = drafterId };
        var handler = BuildHandler(db, um, actor);

        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: false), CancellationToken.None);

        var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloaded.IsUrgentByPro.Should().BeFalse("drafter gỡ → clear ĐỎ");
        reloaded.IsUrgentByCcm.Should().BeFalse("drafter gỡ CẢ 2 — KHÔNG bị giới hạn role-keyed như TP");
    }
}
