using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Domain.ApprovalWorkflowsV2;
using SolutionErp.Domain.Common;
using SolutionErp.Domain.Contracts;
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.Master;
using SolutionErp.Infrastructure.Services;
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Services;

// Plan C B-Wrap (S33) — Contract V2 ApproveV2Async cookie-cutter mirror PE
// PurchaseEvaluationWorkflowServiceReturnModeTests (S23 t5). 5 [Fact] cover
// happy/terminal/skip/outsider-guard/V1-fallback.
//
// Service wire 6 deps (mirror prod):
//   ContractWorkflowService(db, ContractCodeGenerator, FixedDateTime,
//                            NoOpNotificationService, ChangelogService(TestCurrentUser, um),
//                            UserManager<User>)
// Lý do dùng ChangelogService thật + TestCurrentUser stub: BW1+BW3 cần assert
// ContractChangelog row được log (summary + ContextNote). Mock service phá assertion này.
public class ContractWorkflowServiceApproveV2Tests
{
    private static (ContractWorkflowService svc, IdentityFixture fix, TestApplicationDbContext db, FixedDateTime dt, TestCurrentUser currentUser)
        CreateService(Guid? actorUserId = null, params string[] actorRoles)
    {
        var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var dt = new FixedDateTime(new DateTime(2026, 5, 26, 0, 0, 0, DateTimeKind.Utc));
        var notify = new NoOpNotificationService();
        var currentUser = new TestCurrentUser
        {
            UserId = actorUserId,
            Roles = actorRoles ?? Array.Empty<string>(),
        };
        var changelog = new ChangelogService(db, currentUser, um);
        var codeGen = new ContractCodeGenerator(db, dt);
        var svc = new ContractWorkflowService(db, codeGen, dt, notify, changelog, um);
        return (svc, fix, db, dt, currentUser);
    }

    // Workflow setup: 1 Bước (1 Step) — 2 Cấp (2 Levels), mỗi Cấp 1 NV.
    // Default Allow* = false trên Level (admin opt-in pattern Mig 29).
    private static async Task<(ApprovalWorkflow wf, ApprovalWorkflowStep step, ApprovalWorkflowLevel l1, ApprovalWorkflowLevel l2)>
        SeedWorkflowAsync(
            TestApplicationDbContext db,
            Guid approver1UserId,
            Guid approver2UserId,
            string code = "QT-CT-001")
    {
        var wf = new ApprovalWorkflow
        {
            Id = Guid.NewGuid(),
            Code = code,
            Version = 1,
            Name = "Test Contract Workflow V2",
            ApplicableType = ApprovalWorkflowApplicableType.Contract,
            IsActive = true,
            IsUserSelectable = true,
        };
        var step = new ApprovalWorkflowStep
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowId = wf.Id,
            Order = 1,
            DepartmentId = null,
            Name = "Bước 1 Phòng Kỹ Thuật",
        };
        var l1 = new ApprovalWorkflowLevel
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowStepId = step.Id,
            Order = 1,
            ApproverUserId = approver1UserId,
        };
        var l2 = new ApprovalWorkflowLevel
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowStepId = step.Id,
            Order = 2,
            ApproverUserId = approver2UserId,
        };
        db.ApprovalWorkflows.Add(wf);
        db.ApprovalWorkflowSteps.Add(step);
        db.ApprovalWorkflowLevels.Add(l1);
        db.ApprovalWorkflowLevels.Add(l2);
        await db.SaveChangesAsync(CancellationToken.None);
        return (wf, step, l1, l2);
    }

    // BW2: 1 Step × 1 Level — terminal sau Approve duy nhất.
    private static async Task<(ApprovalWorkflow wf, ApprovalWorkflowStep step, ApprovalWorkflowLevel l1)>
        SeedSingleLevelWorkflowAsync(TestApplicationDbContext db, Guid approverUserId)
    {
        var wf = new ApprovalWorkflow
        {
            Id = Guid.NewGuid(),
            Code = "QT-CT-TERMINAL",
            Version = 1,
            Name = "Test Single Level",
            ApplicableType = ApprovalWorkflowApplicableType.Contract,
            IsActive = true,
            IsUserSelectable = true,
        };
        var step = new ApprovalWorkflowStep
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowId = wf.Id,
            Order = 1,
            DepartmentId = null,
            Name = "Bước duy nhất",
        };
        var l1 = new ApprovalWorkflowLevel
        {
            Id = Guid.NewGuid(),
            ApprovalWorkflowStepId = step.Id,
            Order = 1,
            ApproverUserId = approverUserId,
        };
        db.ApprovalWorkflows.Add(wf);
        db.ApprovalWorkflowSteps.Add(step);
        db.ApprovalWorkflowLevels.Add(l1);
        await db.SaveChangesAsync(CancellationToken.None);
        return (wf, step, l1);
    }

    // BW3: 3 Step × 2 Level mỗi Step + slot Cấp 1 Bước 1 set AllowApproverSkipToFinal.
    private static async Task<(ApprovalWorkflow wf, ApprovalWorkflowStep s1, ApprovalWorkflowLevel s1l1, ApprovalWorkflowLevel s1l2, ApprovalWorkflowStep s2, ApprovalWorkflowStep s3)>
        SeedMultiStepF2WorkflowAsync(
            TestApplicationDbContext db,
            Guid s1l1Approver,
            Guid s1l2Approver,
            Guid s2l1Approver,
            Guid s2l2Approver,
            Guid s3l1Approver,
            Guid s3l2Approver,
            bool allowSkipToFinalSlotS1L1 = true)
    {
        var wf = new ApprovalWorkflow
        {
            Id = Guid.NewGuid(),
            Code = "QT-CT-F2",
            Version = 1,
            Name = "Test 3 Step F2 Contract",
            ApplicableType = ApprovalWorkflowApplicableType.Contract,
            IsActive = true,
            IsUserSelectable = true,
        };
        var s1 = new ApprovalWorkflowStep { Id = Guid.NewGuid(), ApprovalWorkflowId = wf.Id, Order = 1, DepartmentId = null, Name = "Bước 1 Kỹ Thuật" };
        var s2 = new ApprovalWorkflowStep { Id = Guid.NewGuid(), ApprovalWorkflowId = wf.Id, Order = 2, DepartmentId = null, Name = "Bước 2 CCM" };
        var s3 = new ApprovalWorkflowStep { Id = Guid.NewGuid(), ApprovalWorkflowId = wf.Id, Order = 3, DepartmentId = null, Name = "Bước 3 GĐ" };
        var s1l1 = new ApprovalWorkflowLevel { Id = Guid.NewGuid(), ApprovalWorkflowStepId = s1.Id, Order = 1, ApproverUserId = s1l1Approver, AllowApproverSkipToFinal = allowSkipToFinalSlotS1L1 };
        var s1l2 = new ApprovalWorkflowLevel { Id = Guid.NewGuid(), ApprovalWorkflowStepId = s1.Id, Order = 2, ApproverUserId = s1l2Approver };
        var s2l1 = new ApprovalWorkflowLevel { Id = Guid.NewGuid(), ApprovalWorkflowStepId = s2.Id, Order = 1, ApproverUserId = s2l1Approver };
        var s2l2 = new ApprovalWorkflowLevel { Id = Guid.NewGuid(), ApprovalWorkflowStepId = s2.Id, Order = 2, ApproverUserId = s2l2Approver };
        var s3l1 = new ApprovalWorkflowLevel { Id = Guid.NewGuid(), ApprovalWorkflowStepId = s3.Id, Order = 1, ApproverUserId = s3l1Approver };
        var s3l2 = new ApprovalWorkflowLevel { Id = Guid.NewGuid(), ApprovalWorkflowStepId = s3.Id, Order = 2, ApproverUserId = s3l2Approver };
        db.ApprovalWorkflows.Add(wf);
        db.ApprovalWorkflowSteps.AddRange(s1, s2, s3);
        db.ApprovalWorkflowLevels.AddRange(s1l1, s1l2, s2l1, s2l2, s3l1, s3l2);
        await db.SaveChangesAsync(CancellationToken.None);
        return (wf, s1, s1l1, s1l2, s2, s3);
    }

    // Build Contract V2 wiring helper. ContractDepartmentApprovals nav collection
    // mặc định empty — KHÔNG cần seed cho V2 happy path (PE workflow legacy V1 mới dùng).
    private static Contract BuildContractAtStep0Level(
        Guid workflowId,
        Guid supplierId,
        Guid projectId,
        Guid drafterId,
        int levelOrder = 1,
        ContractType type = ContractType.HopDongThauPhu)
    {
        return new Contract
        {
            Id = Guid.NewGuid(),
            Type = type,
            Phase = ContractPhase.ChoDuyet,
            SupplierId = supplierId,
            ProjectId = projectId,
            DrafterUserId = drafterId,
            TenHopDong = "Test V2 contract",
            GiaTri = 100_000_000m,
            ApprovalWorkflowId = workflowId,
            CurrentWorkflowStepIndex = 0,
            CurrentApprovalLevelOrder = levelOrder,
        };
    }

    // Seed Supplier + Project bằng Code cố định để gen mã RG-001 predictable.
    private static async Task<(Supplier sup, Project proj)> SeedSupplierProjectAsync(
        TestApplicationDbContext db,
        string supplierCode = "BTBM",
        string projectCode = "FLOCK01")
    {
        var sup = new Supplier
        {
            Id = Guid.NewGuid(),
            Code = supplierCode,
            Name = $"NCC {supplierCode}",
            Type = SupplierType.NhaThauPhu,
        };
        var proj = new Project
        {
            Id = Guid.NewGuid(),
            Code = projectCode,
            Name = $"Dự án {projectCode}",
        };
        db.Suppliers.Add(sup);
        db.Projects.Add(proj);
        await db.SaveChangesAsync(CancellationToken.None);
        return (sup, proj);
    }

    // ============ BW1: Happy path step advance Cấp 1 → Cấp 2 cùng Bước ============

    [Fact]
    public async Task ApproveV2_FirstLevel_AdvancesToSecondLevel_SameStep()
    {
        var fix = new IdentityFixture();
        using (fix)
        {
            var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
            var approver1 = await fix.CreateUserAsync("a1-bw1@test.local", "Approver 1 BW1",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var approver2 = await fix.CreateUserAsync("a2-bw1@test.local", "Approver 2 BW1",
                departmentId: null, roles: new[] { AppRoles.CostControl });

            // Recreate service với actorUserId = approver1 cho ChangelogService resolve UserName đúng
            var um = fix.Services.GetRequiredService<UserManager<User>>();
            var dt = new FixedDateTime(new DateTime(2026, 5, 26, 0, 0, 0, DateTimeKind.Utc));
            var notify = new NoOpNotificationService();
            var currentUser = new TestCurrentUser { UserId = approver1.Id, Roles = new[] { AppRoles.CostControl } };
            var changelog = new ChangelogService(db, currentUser, um);
            var codeGen = new ContractCodeGenerator(db, dt);
            var svc = new ContractWorkflowService(db, codeGen, dt, notify, changelog, um);

            var (wf, _, l1, _) = await SeedWorkflowAsync(db, approver1.Id, approver2.Id);
            var (sup, proj) = await SeedSupplierProjectAsync(db);
            var contract = BuildContractAtStep0Level(wf.Id, sup.Id, proj.Id, drafterId: Guid.NewGuid(), levelOrder: 1);
            db.Contracts.Add(contract);
            await db.SaveChangesAsync(CancellationToken.None);

            await svc.TransitionAsync(
                contract: contract,
                targetPhase: ContractPhase.ChoDuyet,
                actorUserId: approver1.Id,
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Approve,
                comment: "trải nghiệm test",
                ct: CancellationToken.None);

            contract.CurrentApprovalLevelOrder.Should().Be(2, "Lên Cấp 2 cùng Bước 1");
            contract.CurrentWorkflowStepIndex.Should().Be(0, "Step không advance vì còn Cấp 2");
            contract.Phase.Should().Be(ContractPhase.ChoDuyet, "Phase giữ ChoDuyet (chưa terminal)");
            contract.SlaDeadline.Should().NotBeNull("SLA reset 7d cho Cấp 2 nhận phiếu");

            var approvals = await db.ContractApprovals
                .Where(a => a.ContractId == contract.Id).ToListAsync();
            approvals.Should().HaveCount(1);
            approvals[0].ApproverUserId.Should().Be(approver1.Id);
            approvals[0].Decision.Should().Be(ApprovalDecision.Approve);

            var opinions = await db.ContractLevelOpinions
                .Where(o => o.ContractId == contract.Id).ToListAsync();
            opinions.Should().HaveCount(1, "UPSERT 1 row cho slot Cấp 1");
            opinions[0].ApprovalWorkflowLevelId.Should().Be(l1.Id);
            opinions[0].Comment.Should().Be("trải nghiệm test");
            opinions[0].SignedByUserId.Should().Be(approver1.Id);

            var changelogs = await db.ContractChangelogs
                .Where(c => c.ContractId == contract.Id
                    && c.EntityType == ChangelogEntityType.Workflow).ToListAsync();
            changelogs.Should().Contain(c => c.ContextNote != null
                && c.ContextNote.Contains("Hoàn tất Cấp 1, sang Cấp 2 cùng Bước 1"));
        }
    }

    // ============ BW2: Terminal Cấp cuối Bước cuối → DaPhatHanh + gen mã HĐ ============

    [Fact]
    public async Task ApproveV2_LastLevel_FinalStep_TransitionsToDaPhatHanh_GeneratesMaHopDong()
    {
        var fix = new IdentityFixture();
        using (fix)
        {
            var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
            var approver = await fix.CreateUserAsync("a-bw2@test.local", "Approver BW2",
                departmentId: null, roles: new[] { AppRoles.CostControl });

            var um = fix.Services.GetRequiredService<UserManager<User>>();
            var dt = new FixedDateTime(new DateTime(2026, 5, 26, 0, 0, 0, DateTimeKind.Utc));
            var notify = new NoOpNotificationService();
            var currentUser = new TestCurrentUser { UserId = approver.Id, Roles = new[] { AppRoles.CostControl } };
            var changelog = new ChangelogService(db, currentUser, um);
            var codeGen = new ContractCodeGenerator(db, dt);
            var svc = new ContractWorkflowService(db, codeGen, dt, notify, changelog, um);

            var (wf, _, l1) = await SeedSingleLevelWorkflowAsync(db, approver.Id);
            var (sup, proj) = await SeedSupplierProjectAsync(db, supplierCode: "BTBM", projectCode: "FLOCK01");
            var contract = BuildContractAtStep0Level(wf.Id, sup.Id, proj.Id,
                drafterId: Guid.NewGuid(), levelOrder: 1, type: ContractType.HopDongThauPhu);
            db.Contracts.Add(contract);
            await db.SaveChangesAsync(CancellationToken.None);

            await svc.TransitionAsync(
                contract: contract,
                targetPhase: ContractPhase.ChoDuyet,
                actorUserId: approver.Id,
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Approve,
                comment: "duyệt cuối",
                ct: CancellationToken.None);

            contract.Phase.Should().Be(ContractPhase.DaPhatHanh, "Terminal sau Cấp cuối Bước cuối");
            contract.MaHopDong.Should().NotBeNull();
            contract.MaHopDong.Should().Be("FLOCK01/HĐTP/SOL&BTBM/01",
                "RG-001 format ContractType.HopDongThauPhu");
            contract.CurrentWorkflowStepIndex.Should().BeNull();
            contract.CurrentApprovalLevelOrder.Should().BeNull();
            contract.SlaDeadline.Should().BeNull();

            var approvals = await db.ContractApprovals
                .Where(a => a.ContractId == contract.Id).ToListAsync();
            approvals.Should().HaveCount(1);

            var opinions = await db.ContractLevelOpinions
                .Where(o => o.ContractId == contract.Id).ToListAsync();
            opinions.Should().HaveCount(1, "Final UPSERT slot Cấp 1");
            opinions[0].ApprovalWorkflowLevelId.Should().Be(l1.Id);

            var changelogs = await db.ContractChangelogs
                .Where(c => c.ContractId == contract.Id
                    && c.EntityType == ChangelogEntityType.Workflow).ToListAsync();
            changelogs.Should().Contain(c => c.Summary != null
                && c.Summary.Contains("ChoDuyet") && c.Summary.Contains("DaPhatHanh"));
        }
    }

    // ============ BW3: skipToFinal F2 admin opt-in ============

    [Fact]
    public async Task ApproveV2_SkipToFinal_AdminTickFlag_AdvancesToLastStepLastLevel()
    {
        var fix = new IdentityFixture();
        using (fix)
        {
            var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
            var userA = await fix.CreateUserAsync("usera-bw3@test.local", "User A BW3",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var userB = await fix.CreateUserAsync("userb-bw3@test.local", "User B BW3",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var userC = await fix.CreateUserAsync("userc-bw3@test.local", "User C BW3",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var userD = await fix.CreateUserAsync("userd-bw3@test.local", "User D BW3",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var userE = await fix.CreateUserAsync("usere-bw3@test.local", "User E BW3",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var userF = await fix.CreateUserAsync("userf-bw3@test.local", "User F BW3",
                departmentId: null, roles: new[] { AppRoles.CostControl });

            var um = fix.Services.GetRequiredService<UserManager<User>>();
            var dt = new FixedDateTime(new DateTime(2026, 5, 26, 0, 0, 0, DateTimeKind.Utc));
            var notify = new NoOpNotificationService();
            var currentUser = new TestCurrentUser { UserId = userA.Id, Roles = new[] { AppRoles.CostControl } };
            var changelog = new ChangelogService(db, currentUser, um);
            var codeGen = new ContractCodeGenerator(db, dt);
            var svc = new ContractWorkflowService(db, codeGen, dt, notify, changelog, um);

            var (wf, _, _, _, _, _) = await SeedMultiStepF2WorkflowAsync(
                db, userA.Id, userB.Id, userC.Id, userD.Id, userE.Id, userF.Id,
                allowSkipToFinalSlotS1L1: true);
            var (sup, proj) = await SeedSupplierProjectAsync(db, supplierCode: "BTBM", projectCode: "FLOCK01");
            var contract = BuildContractAtStep0Level(wf.Id, sup.Id, proj.Id,
                drafterId: Guid.NewGuid(), levelOrder: 1);
            db.Contracts.Add(contract);
            await db.SaveChangesAsync(CancellationToken.None);

            await svc.TransitionAsync(
                contract: contract,
                targetPhase: ContractPhase.ChoDuyet,
                actorUserId: userA.Id,
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Approve,
                comment: "duyệt thẳng cấp cuối",
                skipToFinal: true,
                ct: CancellationToken.None);

            contract.CurrentWorkflowStepIndex.Should().Be(2,
                "lastStepIdx Bước 3 (index = 3-1 = 2)");
            contract.CurrentApprovalLevelOrder.Should().Be(2,
                "lastLevelMaxOrder Cấp 2 Bước cuối");
            contract.Phase.Should().Be(ContractPhase.ChoDuyet,
                "skip advance pointer KHÔNG terminal — NV cuối vẫn cần ký thật");
            contract.SlaDeadline.Should().NotBeNull();

            var approvals = await db.ContractApprovals
                .Where(a => a.ContractId == contract.Id).ToListAsync();
            approvals.Should().HaveCount(1);
            approvals[0].Comment.Should().StartWith("[Duyệt vượt cấp tới Cấp cuối]",
                "Prefix enrich từ ContractWorkflowService:270 khi skipToFinal=true");

            var changelogs = await db.ContractChangelogs
                .Where(c => c.ContractId == contract.Id
                    && c.EntityType == ChangelogEntityType.Workflow).ToListAsync();
            changelogs.Should().Contain(c => c.ContextNote != null
                && c.ContextNote.Contains("Approver skip thẳng tới Bước 3 Cấp 2"));
        }
    }

    // ============ BW4: Outsider TransitionAsync(Approve) → ForbiddenException ============

    [Fact]
    public async Task ApproveV2_OutsiderNonAdmin_ThrowsForbiddenException()
    {
        var fix = new IdentityFixture();
        using (fix)
        {
            var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
            var approver1 = await fix.CreateUserAsync("a1-bw4@test.local", "Approver 1 BW4",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var approver2 = await fix.CreateUserAsync("a2-bw4@test.local", "Approver 2 BW4",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var outsider = await fix.CreateUserAsync("out-bw4@test.local", "Outsider BW4",
                departmentId: null, roles: new[] { AppRoles.CostControl });

            var um = fix.Services.GetRequiredService<UserManager<User>>();
            var dt = new FixedDateTime(new DateTime(2026, 5, 26, 0, 0, 0, DateTimeKind.Utc));
            var notify = new NoOpNotificationService();
            var currentUser = new TestCurrentUser { UserId = outsider.Id, Roles = new[] { AppRoles.CostControl } };
            var changelog = new ChangelogService(db, currentUser, um);
            var codeGen = new ContractCodeGenerator(db, dt);
            var svc = new ContractWorkflowService(db, codeGen, dt, notify, changelog, um);

            var (wf, _, _, _) = await SeedWorkflowAsync(db, approver1.Id, approver2.Id);
            var (sup, proj) = await SeedSupplierProjectAsync(db);
            var contract = BuildContractAtStep0Level(wf.Id, sup.Id, proj.Id,
                drafterId: Guid.NewGuid(), levelOrder: 1);
            db.Contracts.Add(contract);
            await db.SaveChangesAsync(CancellationToken.None);

            var act = async () => await svc.TransitionAsync(
                contract: contract,
                targetPhase: ContractPhase.ChoDuyet,
                actorUserId: outsider.Id,
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Approve,
                comment: "outsider thử approve",
                ct: CancellationToken.None);

            await act.Should().ThrowAsync<ForbiddenException>()
                .WithMessage("*Bước 1*Cấp 1: bạn không có trong danh sách NV duyệt*");
            contract.Phase.Should().Be(ContractPhase.ChoDuyet,
                "Guard chặn trước mutate phase");
            contract.CurrentApprovalLevelOrder.Should().Be(1, "Pointer unchanged");
        }
    }

    // ============ BW7: V1 fallback skipToFinal non-admin → ConflictException ============

    [Fact]
    public async Task ApproveV1Fallback_SkipToFinal_NonAdmin_ThrowsConflictException()
    {
        var fix = new IdentityFixture();
        using (fix)
        {
            var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
            var approver = await fix.CreateUserAsync("a-bw7@test.local", "Approver BW7",
                departmentId: null, roles: new[] { AppRoles.CostControl });

            var um = fix.Services.GetRequiredService<UserManager<User>>();
            var dt = new FixedDateTime(new DateTime(2026, 5, 26, 0, 0, 0, DateTimeKind.Utc));
            var notify = new NoOpNotificationService();
            var currentUser = new TestCurrentUser { UserId = approver.Id, Roles = new[] { AppRoles.CostControl } };
            var changelog = new ChangelogService(db, currentUser, um);
            var codeGen = new ContractCodeGenerator(db, dt);
            var svc = new ContractWorkflowService(db, codeGen, dt, notify, changelog, um);

            var (sup, proj) = await SeedSupplierProjectAsync(db);
            // Contract V1 legacy — ApprovalWorkflowId = null
            var contract = new Contract
            {
                Id = Guid.NewGuid(),
                Type = ContractType.HopDongThauPhu,
                Phase = ContractPhase.ChoDuyet,
                SupplierId = sup.Id,
                ProjectId = proj.Id,
                DrafterUserId = Guid.NewGuid(),
                TenHopDong = "V1 legacy contract",
                GiaTri = 50_000_000m,
                ApprovalWorkflowId = null,
                WorkflowDefinitionId = null,
                CurrentWorkflowStepIndex = 0,
            };
            db.Contracts.Add(contract);
            await db.SaveChangesAsync(CancellationToken.None);

            var act = async () => await svc.TransitionAsync(
                contract: contract,
                targetPhase: ContractPhase.ChoDuyet,
                actorUserId: approver.Id,
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Approve,
                comment: "thử skipToFinal V1",
                skipToFinal: true,
                ct: CancellationToken.None);

            await act.Should().ThrowAsync<ConflictException>()
                .WithMessage("*skipToFinal chỉ hỗ trợ HĐ V2*");
            contract.Phase.Should().Be(ContractPhase.ChoDuyet, "State unchanged");
        }
    }

    // =====================================================================
    // [S187 W6] CỬA ĐÚNG-TRẠM cho TRẢ LẠI / TỪ CHỐI — vá gotcha #82-S156
    // =====================================================================
    // Lỗ S156: nhánh Reject của `TransitionAsync` chạy TRƯỚC MỌI rào ⇒ ai đăng nhập cũng
    // trả lại / từ chối được HĐ bất kỳ, kể cả HĐ đã `DaPhatHanh`. 5 [Fact] dưới đây neo CẢ
    // HAI chiều để rào không trượt về một phía:
    //   • chặn đúng người cần chặn — W6-1 (ngoài quy trình) · W6-2 (đúng người SAI LƯỢT) ·
    //     W6-3 (HĐ đã phát hành, kể cả người đang tới lượt)
    //   • KHÔNG chặn người cần cho qua — W6-4 (approver Cấp đang chờ trả lại được thật)
    //   • KHÔNG lan sang V1 — W6-5 (HĐ legacy giữ hành vi cũ)
    // Bỏ W6-4 thì một rào chặn-tất-cả vẫn xanh 4/5; bỏ W6-2 thì rào chỉ-cần-có-tên-trong-quy-
    // trình (không xét Cấp) cũng xanh — hai ca đó là phần chịu lực, đừng cắt khi gọn hoá.
    //
    // Vế "đúng approver duyệt được bình thường" KHÔNG viết lại ở đây: BW1 (:222) đã neo sẵn
    // và vẫn xanh sau đợt này — thêm bản sao chỉ là churn.

    // Dựng service + trả kèm những thứ test cần assert. Gom lại vì 5 ca dưới lặp y hệt
    // 8 dòng wiring của các ca BW phía trên.
    private static (ContractWorkflowService svc, TestApplicationDbContext db) WireService(
        IdentityFixture fix, Guid actorId, params string[] actorRoles)
    {
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var dt = new FixedDateTime(new DateTime(2026, 8, 10, 0, 0, 0, DateTimeKind.Utc));
        var currentUser = new TestCurrentUser { UserId = actorId, Roles = actorRoles };
        var changelog = new ChangelogService(db, currentUser, um);
        var svc = new ContractWorkflowService(
            db, new ContractCodeGenerator(db, dt), dt, new NoOpNotificationService(), changelog, um);
        return (svc, db);
    }

    // ---- W6-1: người NGOÀI quy trình bấm Trả lại HĐ V2 đang chờ duyệt → 409, HĐ đứng yên ----

    [Fact]
    public async Task RejectV2_OutsiderNonAdmin_ThrowsConflict_AndLeavesContractUntouched()
    {
        var fix = new IdentityFixture();
        using (fix)
        {
            var approver1 = await fix.CreateUserAsync("a1-w61@test.local", "Approver 1 W61",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var approver2 = await fix.CreateUserAsync("a2-w61@test.local", "Approver 2 W61",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var outsider = await fix.CreateUserAsync("out-w61@test.local", "Outsider W61",
                departmentId: null, roles: new[] { AppRoles.CostControl });

            var (svc, db) = WireService(fix, outsider.Id, AppRoles.CostControl);
            var (wf, _, _, _) = await SeedWorkflowAsync(db, approver1.Id, approver2.Id, code: "QT-CT-W61");
            var (sup, proj) = await SeedSupplierProjectAsync(db, supplierCode: "W61", projectCode: "PW61");
            var contract = BuildContractAtStep0Level(wf.Id, sup.Id, proj.Id,
                drafterId: Guid.NewGuid(), levelOrder: 1);
            db.Contracts.Add(contract);
            await db.SaveChangesAsync(CancellationToken.None);

            var act = async () => await svc.TransitionAsync(
                contract: contract,
                targetPhase: ContractPhase.TraLai,
                actorUserId: outsider.Id,
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Reject,
                comment: "người ngoài thử trả lại",
                ct: CancellationToken.None);

            await act.Should().ThrowAsync<ConflictException>()
                .WithMessage("*Không đến lượt bạn*");

            contract.Phase.Should().Be(ContractPhase.ChoDuyet, "Rào chặn TRƯỚC khi mutate phase");
            contract.CurrentWorkflowStepIndex.Should().Be(0, "Con-trỏ Bước nguyên vẹn");
            contract.CurrentApprovalLevelOrder.Should().Be(1, "Con-trỏ Cấp nguyên vẹn");

            // Rào ném trước `SaveChangesAsync` ⇒ không được để lại vết nào trong DB.
            var changelogs = await db.ContractChangelogs
                .Where(c => c.ContractId == contract.Id).ToListAsync();
            changelogs.Should().BeEmpty("Trả lại bị chặn thì không ghi lịch sử chuyển trạng thái");
        }
    }

    // ---- W6-2: ĐÚNG người trong quy trình nhưng SAI LƯỢT (approver Cấp 2 khi HĐ đang ở Cấp 1)
    // Ca chịu lực: phân biệt rào ĐÚNG-TRẠM với rào chỉ hỏi "có tên trong quy trình không". ----

    [Fact]
    public async Task RejectV2_ApproverOfLaterLevel_NotYetTheirTurn_ThrowsConflict()
    {
        var fix = new IdentityFixture();
        using (fix)
        {
            var approver1 = await fix.CreateUserAsync("a1-w62@test.local", "Approver 1 W62",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var approver2 = await fix.CreateUserAsync("a2-w62@test.local", "Approver 2 W62",
                departmentId: null, roles: new[] { AppRoles.CostControl });

            // Actor = approver Cấp 2 — CÓ tên trong quy trình, nhưng HĐ đang đứng ở Cấp 1.
            var (svc, db) = WireService(fix, approver2.Id, AppRoles.CostControl);
            var (wf, _, _, _) = await SeedWorkflowAsync(db, approver1.Id, approver2.Id, code: "QT-CT-W62");
            var (sup, proj) = await SeedSupplierProjectAsync(db, supplierCode: "W62", projectCode: "PW62");
            var contract = BuildContractAtStep0Level(wf.Id, sup.Id, proj.Id,
                drafterId: Guid.NewGuid(), levelOrder: 1);
            db.Contracts.Add(contract);
            await db.SaveChangesAsync(CancellationToken.None);

            var act = async () => await svc.TransitionAsync(
                contract: contract,
                targetPhase: ContractPhase.TraLai,
                actorUserId: approver2.Id,
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Reject,
                comment: "Cấp 2 nhảy lượt Cấp 1",
                ct: CancellationToken.None);

            await act.Should().ThrowAsync<ConflictException>()
                .WithMessage("*Không đến lượt bạn*Cấp 1*");
            contract.Phase.Should().Be(ContractPhase.ChoDuyet);
            contract.CurrentApprovalLevelOrder.Should().Be(1);
        }
    }

    // ---- W6-3: HĐ ĐÃ PHÁT HÀNH bất khả xâm phạm — kể cả người đang có chân duyệt (lỗ S156) ----

    [Fact]
    public async Task RejectV2_ContractDaPhatHanh_EvenApproverInWorkflow_ThrowsConflict()
    {
        var fix = new IdentityFixture();
        using (fix)
        {
            var approver1 = await fix.CreateUserAsync("a1-w63@test.local", "Approver 1 W63",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var approver2 = await fix.CreateUserAsync("a2-w63@test.local", "Approver 2 W63",
                departmentId: null, roles: new[] { AppRoles.CostControl });

            var (svc, db) = WireService(fix, approver1.Id, AppRoles.CostControl);
            var (wf, _, _, _) = await SeedWorkflowAsync(db, approver1.Id, approver2.Id, code: "QT-CT-W63");
            var (sup, proj) = await SeedSupplierProjectAsync(db, supplierCode: "W63", projectCode: "PW63");

            // HĐ đã đi hết quy trình: mã đã gen, con-trỏ đã xoá — đúng hình dạng sau terminal.
            var contract = BuildContractAtStep0Level(wf.Id, sup.Id, proj.Id,
                drafterId: Guid.NewGuid(), levelOrder: 1);
            contract.Phase = ContractPhase.DaPhatHanh;
            contract.MaHopDong = "PW63/HĐTP/SOL&W63/01";
            contract.CurrentWorkflowStepIndex = null;
            contract.CurrentApprovalLevelOrder = null;
            db.Contracts.Add(contract);
            await db.SaveChangesAsync(CancellationToken.None);

            var act = async () => await svc.TransitionAsync(
                contract: contract,
                targetPhase: ContractPhase.TuChoi,
                actorUserId: approver1.Id,
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Reject,
                comment: "thử lật HĐ đã phát hành",
                ct: CancellationToken.None);

            await act.Should().ThrowAsync<ConflictException>()
                .WithMessage("*Chỉ từ chối được HĐ đang ở trạng thái Đã gửi duyệt*");

            contract.Phase.Should().Be(ContractPhase.DaPhatHanh, "HĐ đã phát hành không bị lật");
            contract.MaHopDong.Should().Be("PW63/HĐTP/SOL&W63/01", "Mã HĐ đã cấp giữ nguyên");
        }
    }

    // ---- W6-4: CHIỀU DƯƠNG — approver của Cấp ĐANG CHỜ vẫn trả lại được thật ----
    // Không có ca này thì một rào chặn-sạch-mọi-request cũng xanh hết phần còn lại.

    [Fact]
    public async Task RejectV2_ApproverOfCurrentLevel_ReturnsContractToTraLai()
    {
        var fix = new IdentityFixture();
        using (fix)
        {
            var approver1 = await fix.CreateUserAsync("a1-w64@test.local", "Approver 1 W64",
                departmentId: null, roles: new[] { AppRoles.CostControl });
            var approver2 = await fix.CreateUserAsync("a2-w64@test.local", "Approver 2 W64",
                departmentId: null, roles: new[] { AppRoles.CostControl });

            var (svc, db) = WireService(fix, approver1.Id, AppRoles.CostControl);
            var (wf, _, _, _) = await SeedWorkflowAsync(db, approver1.Id, approver2.Id, code: "QT-CT-W64");
            var (sup, proj) = await SeedSupplierProjectAsync(db, supplierCode: "W64", projectCode: "PW64");
            var contract = BuildContractAtStep0Level(wf.Id, sup.Id, proj.Id,
                drafterId: Guid.NewGuid(), levelOrder: 1);
            db.Contracts.Add(contract);
            await db.SaveChangesAsync(CancellationToken.None);

            await svc.TransitionAsync(
                contract: contract,
                targetPhase: ContractPhase.TraLai,
                actorUserId: approver1.Id,
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Reject,
                comment: "thiếu phụ lục — nhờ bổ sung",
                ct: CancellationToken.None);

            contract.Phase.Should().Be(ContractPhase.TraLai, "Đúng người đang tới lượt → trả lại được");
            contract.CurrentWorkflowStepIndex.Should().BeNull("Trả lại xoá con-trỏ Bước (hành vi sẵn có)");
            contract.SlaDeadline.Should().BeNull();

            var changelogs = await db.ContractChangelogs
                .Where(c => c.ContractId == contract.Id
                    && c.EntityType == ChangelogEntityType.Workflow).ToListAsync();
            changelogs.Should().Contain(c => c.Summary != null
                && c.Summary.Contains("ChoDuyet") && c.Summary.Contains("TraLai"));
        }
    }

    // ---- W6-5: CONTROL V1 legacy — HĐ không pin V2 giữ nguyên hành vi cũ ----
    // 🔴 CỐ Ý chỉ neo ca `ChoDuyet`: đây là bằng chứng "rào W6 không lan sang V1". KHÔNG neo ca
    //    V1 + `DaPhatHanh` (lỗ S156 phía V1 còn mở theo quyết định owner "chỗ hợp đồng cứ từ
    //    từ") — viết assert cho một lỗ đang mở là biến nó thành hợp-đồng-hành-vi, để rồi đợt sau
    //    ai đi vá V1 lại phải cãi nhau với lưới test.
    [Fact]
    public async Task RejectV1Legacy_NonApprover_StillReturnsToTraLai_BehaviorUnchanged()
    {
        var fix = new IdentityFixture();
        using (fix)
        {
            var randomUser = await fix.CreateUserAsync("rnd-w65@test.local", "Random W65",
                departmentId: null, roles: new[] { AppRoles.CostControl });

            var (svc, db) = WireService(fix, randomUser.Id, AppRoles.CostControl);
            var (sup, proj) = await SeedSupplierProjectAsync(db, supplierCode: "W65", projectCode: "PW65");
            var contract = new Contract
            {
                Id = Guid.NewGuid(),
                Type = ContractType.HopDongThauPhu,
                Phase = ContractPhase.ChoDuyet,
                SupplierId = sup.Id,
                ProjectId = proj.Id,
                DrafterUserId = Guid.NewGuid(),
                TenHopDong = "HĐ V1 legacy",
                GiaTri = 50_000_000m,
                ApprovalWorkflowId = null,   // ← V1: không pin quy trình V2
                WorkflowDefinitionId = null,
                CurrentWorkflowStepIndex = 0,
            };
            db.Contracts.Add(contract);
            await db.SaveChangesAsync(CancellationToken.None);

            await svc.TransitionAsync(
                contract: contract,
                targetPhase: ContractPhase.TraLai,
                actorUserId: randomUser.Id,
                actorRoles: new[] { AppRoles.CostControl },
                decision: ApprovalDecision.Reject,
                comment: "V1 trả lại như cũ",
                ct: CancellationToken.None);

            contract.Phase.Should().Be(ContractPhase.TraLai,
                "HĐ V1 KHÔNG đi qua cửa đúng-trạm — hành vi legacy giữ nguyên từng byte");
            contract.CurrentWorkflowStepIndex.Should().BeNull();
            contract.SlaDeadline.Should().BeNull();
        }
    }
}
