using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.PurchaseEvaluations;         // PeBudgetAccumulator, ComputeAsync/ComputePendingAsync, GetPurchaseEvaluationQuery
using SolutionErp.Application.PurchaseEvaluations.Dtos;    // PePendingPriorPeDto
using SolutionErp.Domain.ApprovalWorkflowsV2;
using SolutionErp.Domain.Contracts;                        // ApprovalDecision, ChangelogAction
using SolutionErp.Domain.Identity;                         // AppRoles
using SolutionErp.Domain.Master;
using SolutionErp.Domain.Master.Catalogs;
using SolutionErp.Domain.PurchaseEvaluations;
using SolutionErp.Infrastructure.Services;
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Services;

// ===== S134 (2026-07-17) TEST-C — PE lũy kế TẠM TÍNH (PeBudgetAccumulator.ComputePendingAsync) =====
// Code BE ĐÃ LAND (INV-2): lũy kế "phiếu TRƯỚC cùng cặp (ProjectId, WorkItemId) CHƯA duyệt xong"
// BỔ SUNG (KHÔNG thay) số CHÍNH XÁC ComputeAsync (S133). 3 tầng cần phủ:
//   (1) PeBudgetAccumulator.ComputePendingAsync — window Y HỆT ComputeAsync (peers cùng cặp,
//       Id != peId, CreatedAt < peCreatedAt). 3 nhóm số:
//         PendingSubmitted = Phase ∉ {DangSoanThao=1, DaDuyet=7, ChoDuyet=10, TuChoi=99} (= TraLai=98
//                            + legacy 2-6) → Count + SUM(BudgetPeriodAmount ?? 0). ChoDuyet/DaDuyet
//                            ĐÃ nằm trong ComputeAsync PrevSubmitted → loại tránh double-count.
//         PendingSelected  = Phase ∉ {DangSoanThao, DaDuyet, TuChoi} && Suppliers.Any(IsWinner)
//                            (GỒM ChoDuyet-có-winner = gap chính đề bài) → Count + SUM(ThanhTien IsSelected).
//         PriorPes         = Phase ∉ {DaDuyet, TuChoi} orderby CreatedAt → PePendingPriorPeDto(Id, MaPhieu, (int)Phase).
//   (2) GetPurchaseEvaluationQuery: nhánh live fill 5 field / nhánh frozen (DaDuyet + SnapshotAt) defaults 0/null.
//   (3) D4 ApplyBudgetSnapshotOnFinalizeAsync: finalize khi còn phiếu trước chưa duyệt → +1 Changelog audit.
//
// Test theo CODE (S34 single source of truth). TEST-AFTER (code đã land, build 0/0). KHÔNG touch
// production (src/Backend, fe-*). Mirror seed-helper PeBudgetFreezeTests :277-336 (CreatedAt override
// thủ công — TestApplicationDbContext KHÔNG wire audit interceptor).
public class PePendingAccumulationTests
{
    // Giá chốt hợp lệ khi finalize (ApplyApprovedPriceOnFinalize đòi source ∈ ValidApprovedPriceSources).
    private const decimal ValidApprovedPrice = 400_000_000m;
    private const string ValidApprovedSource = "Ncc";

    // Mốc thời gian: peers seed TRƯỚC (BaseT.AddDays(-n)); "phiếu đang xét" = CurCreatedAt (muộn hơn).
    private static readonly DateTime BaseT = new(2026, 7, 17, 8, 0, 0, DateTimeKind.Utc);
    private static readonly DateTime CurCreatedAt = new(2026, 7, 17, 12, 0, 0, DateTimeKind.Utc);

    private static (PurchaseEvaluationWorkflowService svc, IdentityFixture fix,
        TestApplicationDbContext db) CreateService()
    {
        var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();
        var clock = new FixedDateTime(new DateTime(2026, 7, 17, 0, 0, 0, DateTimeKind.Utc));
        var notify = new NoOpNotificationService();
        var svc = new PurchaseEvaluationWorkflowService(db, clock, notify, um);
        return (svc, fix, db);
    }

    // ---- shared seed helpers (mirror PeBudgetFreezeTests) ----

    private static async Task<(Project project, WorkItem wi)> SeedProjectAndWorkItemAsync(
        TestApplicationDbContext db, string suffix)
    {
        var project = new Project { Id = Guid.NewGuid(), Code = "PRJ-PA-" + suffix, Name = "Dự án " + suffix };
        var wi = new WorkItem { Id = Guid.NewGuid(), Code = "WI-PA-" + suffix, Name = "Hạng mục " + suffix, IsActive = true };
        db.Projects.Add(project);
        db.WorkItems.Add(wi);
        await db.SaveChangesAsync(CancellationToken.None);
        return (project, wi);
    }

    // 1 record PeWorkItemBudget per cặp (ProjectId × WorkItemId). NAMING: Initial/Adjustment = CCM.
    private static async Task SeedPairAsync(
        TestApplicationDbContext db, Guid projectId, Guid wiId, decimal? ccmInit)
    {
        db.PeWorkItemBudgets.Add(new PeWorkItemBudget
        {
            Id = Guid.NewGuid(),
            ProjectId = projectId,
            WorkItemId = wiId,
            InitialAmount = ccmInit,
        });
        await db.SaveChangesAsync(CancellationToken.None);
    }

    // Seed 1 đơn vị IsWinner + 1 detail + 1 quote IsSelected cho phiếu (own quotes → winnerQuoteTotal).
    private static async Task SeedWinnerQuoteAsync(
        TestApplicationDbContext db, PurchaseEvaluation pe, decimal thanhTien)
    {
        var pes = new PurchaseEvaluationSupplier
        {
            Id = Guid.NewGuid(),
            PurchaseEvaluationId = pe.Id,
            SupplierId = Guid.NewGuid(),
            Order = 0,
            IsWinner = true,
        };
        var detail = new PurchaseEvaluationDetail
        {
            Id = Guid.NewGuid(),
            PurchaseEvaluationId = pe.Id,
            GroupCode = "A.I",
            GroupName = "Bê tông",
            NoiDung = "Concrete",
            Order = 0,
        };
        db.PurchaseEvaluationSuppliers.Add(pes);
        db.PurchaseEvaluationDetails.Add(detail);
        db.PurchaseEvaluationQuotes.Add(new PurchaseEvaluationQuote
        {
            Id = Guid.NewGuid(),
            PurchaseEvaluationDetailId = detail.Id,
            PurchaseEvaluationSupplierId = pes.Id,
            ThanhTien = thanhTien,
            IsSelected = true,
        });
        await db.SaveChangesAsync(CancellationToken.None);
    }

    // Peer PE cùng cặp cho accumulator (lũy kế phiếu TRƯỚC). CreatedAt override thủ công.
    private static async Task<PurchaseEvaluation> SeedPeerAsync(
        TestApplicationDbContext db, Guid projectId, Guid wiId,
        PurchaseEvaluationPhase phase, decimal? budgetPeriod, DateTime createdAt,
        decimal? winnerQuote, string code)
    {
        var peer = new PurchaseEvaluation
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = phase,
            MaPhieu = code,
            TenGoiThau = "Peer " + code,
            ProjectId = projectId,
            WorkItemId = wiId,
            DrafterUserId = Guid.NewGuid(),
            BudgetPeriodAmount = budgetPeriod,
        };
        db.PurchaseEvaluations.Add(peer);
        await db.SaveChangesAsync(CancellationToken.None);
        peer.CreatedAt = createdAt;   // accumulator dùng CreatedAt < peCreatedAt
        await db.SaveChangesAsync(CancellationToken.None);
        if (winnerQuote is decimal wq)
            await SeedWinnerQuoteAsync(db, peer, wq);
        return peer;
    }

    // Workflow V2 1 Bước 1 Cấp AllowApproverFinalize=true → duyệt slot cuối = DaDuyet (finalize-branch).
    private static async Task<ApprovalWorkflow> SeedFinalizeWorkflowAsync(
        TestApplicationDbContext db, Guid approverUserId, string code)
    {
        var wf = new ApprovalWorkflow
        {
            Code = code,
            Version = 1,
            ApplicableType = ApprovalWorkflowApplicableType.DuyetNcc,
            Name = "QT test pending-accumulation",
            IsActive = true,
            IsUserSelectable = true,
        };
        var step = new ApprovalWorkflowStep { ApprovalWorkflowId = wf.Id, Order = 1, Name = "Bước 1" };
        step.Levels.Add(new ApprovalWorkflowLevel
        {
            ApprovalWorkflowStepId = step.Id,
            Order = 1,
            Name = "Cấp 1",
            ApproverUserId = approverUserId,
            AllowApproverFinalize = true,
        });
        wf.Steps.Add(step);
        db.ApprovalWorkflows.Add(wf);
        await db.SaveChangesAsync(CancellationToken.None);
        return wf;
    }

    // PE đứng tại slot duyệt cuối V2 (ChoDuyet) gắn cặp + workflow đã pin.
    private static PurchaseEvaluation BuildPeAtFinalizeSlot(
        Guid approvalWorkflowId, Guid projectId, Guid? workItemId, Guid drafterUserId, string code)
        => new()
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = PurchaseEvaluationPhase.ChoDuyet,
            MaPhieu = code,
            TenGoiThau = "Gói thầu " + code,
            ProjectId = projectId,
            WorkItemId = workItemId,
            DrafterUserId = drafterUserId,
            ApprovalWorkflowId = approvalWorkflowId,
            CurrentWorkflowStepIndex = 0,
            CurrentApprovalLevelOrder = 1,
            SlaDeadline = new DateTime(2026, 7, 24, 0, 0, 0, DateTimeKind.Utc),
        };

    // Approve-in-place + giá chốt hợp lệ → DaDuyet qua finalize path.
    private static Task ApproveWithPriceAsync(
        PurchaseEvaluationWorkflowService svc, PurchaseEvaluation pe, Guid approver, string[] roles) =>
        svc.TransitionAsync(
            evaluation: pe,
            targetPhase: PurchaseEvaluationPhase.ChoDuyet,
            actorUserId: approver,
            actorRoles: roles,
            decision: ApprovalDecision.Approve,
            comment: null,
            approvedPriceAmount: ValidApprovedPrice,
            approvedPriceSource: ValidApprovedSource,
            ct: CancellationToken.None);

    // Direct call convenience — curPeId synthetic (KHÔNG cần row phiếu đang xét; accumulator chỉ
    // filter p.Id != peId && p.CreatedAt < peCreatedAt). Window = CurCreatedAt (muộn hơn mọi peer sớm).
    private static Task<PePendingAccumulation> ComputePendingAsync(
        TestApplicationDbContext db, Guid projectId, Guid wiId) =>
        PeBudgetAccumulator.ComputePendingAsync(db, projectId, wiId, Guid.NewGuid(), CurCreatedAt, CancellationToken.None);

    // =====================================================================
    // C1 — peer ChoDuyet + IsWinner + quote IsSelected → PendingSelected (gap chính đề bài):
    //   Total = SUM ThanhTien, Count = 1; peer CÓ MẶT trong PriorPes (đúng Id + MaPhieu + Phase=10).
    //   PendingSubmitted = 0 (ChoDuyet CỐ Ý loại — đã nằm ComputeAsync PrevSubmitted, tránh double-count).
    // =====================================================================
    [Fact]
    public async Task ChoDuyetWinner_CountsInPendingSelected_AndListedInPriorPes()
    {
        var (_, fix, db) = CreateService();
        using (fix)
        {
            var (project, wi) = await SeedProjectAndWorkItemAsync(db, "C1");
            var peer = await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.ChoDuyet,
                budgetPeriod: 30_000_000m, createdAt: BaseT.AddDays(-2), winnerQuote: 70_000_000m, code: "PE-C1-CD");

            var r = await ComputePendingAsync(db, project.Id, wi.Id);

            r.PendingSelectedTotal.Should().Be(70_000_000m, "peer ChoDuyet + winner + quote IsSelected → tạm tính đã chọn");
            r.PendingSelectedCount.Should().Be(1);
            r.PendingSubmittedTotal.Should().Be(0m, "ChoDuyet loại khỏi PendingSubmitted (đã nằm ComputeAsync PrevSubmitted)");
            r.PendingSubmittedCount.Should().Be(0);

            r.PriorPes.Should().ContainSingle("ChoDuyet ∉ {DaDuyet, TuChoi} → có trong danh sách cần lưu ý");
            var prior = r.PriorPes.Single();
            prior.Id.Should().Be(peer.Id);
            prior.MaPhieu.Should().Be("PE-C1-CD");
            prior.Phase.Should().Be(10, "Phase = (int)ChoDuyet");
        }
    }

    // =====================================================================
    // C2 — peer TuChoi → mọi delta 0 + KHÔNG trong PriorPes; peer DaDuyet → delta 0 (nằm bên
    //   ComputeAsync chính xác, KHÔNG lũy kế tạm tính) + KHÔNG trong PriorPes.
    // =====================================================================
    [Fact]
    public async Task TuChoiAndDaDuyetPeers_ContributeNothingToPending_AndAbsentFromPriorPes()
    {
        var (_, fix, db) = CreateService();
        using (fix)
        {
            var (project, wi) = await SeedProjectAndWorkItemAsync(db, "C2");
            // Cả 2 peer đều có winner + budget để CHỨNG MINH exclusion theo Phase (không phải thiếu data).
            await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.TuChoi,
                budgetPeriod: 50_000_000m, createdAt: BaseT.AddDays(-3), winnerQuote: 30_000_000m, code: "PE-C2-REJ");
            await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.DaDuyet,
                budgetPeriod: 100_000_000m, createdAt: BaseT.AddDays(-2), winnerQuote: 90_000_000m, code: "PE-C2-DD");

            var r = await ComputePendingAsync(db, project.Id, wi.Id);

            r.PendingSubmittedTotal.Should().Be(0m, "TuChoi + DaDuyet đều bị loại khỏi PendingSubmitted");
            r.PendingSubmittedCount.Should().Be(0);
            r.PendingSelectedTotal.Should().Be(0m, "TuChoi + DaDuyet đều bị loại khỏi PendingSelected dù có winner");
            r.PendingSelectedCount.Should().Be(0);
            r.PriorPes.Should().BeEmpty("cả 2 phase = terminal → không nằm danh sách cần lưu ý");
        }
    }

    // =====================================================================
    // C3 — peer DangSoanThao (Nháp) → TRONG PriorPes NHƯNG delta tiền = 0 (D1 list-only); peer TraLai
    //   có BudgetPeriodAmount → PendingSubmittedTotal đúng (D2). Chứng minh: budget 40tr của Nháp
    //   KHÔNG cộng, budget 60tr của TraLai CÓ cộng.
    // =====================================================================
    [Fact]
    public async Task DraftPeer_ListOnly_TraLaiPeer_CountsInPendingSubmitted()
    {
        var (_, fix, db) = CreateService();
        using (fix)
        {
            var (project, wi) = await SeedProjectAndWorkItemAsync(db, "C3");
            var draft = await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.DangSoanThao,
                budgetPeriod: 40_000_000m, createdAt: BaseT.AddDays(-3), winnerQuote: null, code: "PE-C3-NHAP");
            var traLai = await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.TraLai,
                budgetPeriod: 60_000_000m, createdAt: BaseT.AddDays(-2), winnerQuote: null, code: "PE-C3-TRALAI");

            var r = await ComputePendingAsync(db, project.Id, wi.Id);

            r.PendingSubmittedTotal.Should().Be(60_000_000m, "chỉ TraLai cộng (D2); Nháp 40tr là list-only (D1)");
            r.PendingSubmittedCount.Should().Be(1, "chỉ TraLai; DangSoanThao loại khỏi PendingSubmitted");
            r.PendingSelectedTotal.Should().Be(0m, "không peer nào có winner");
            r.PendingSelectedCount.Should().Be(0);

            r.PriorPes.Should().HaveCount(2, "Nháp + TraLai đều ∉ {DaDuyet, TuChoi}");
            r.PriorPes.Select(p => p.Id).Should().ContainInOrder(new[] { draft.Id, traLai.Id }, "orderby CreatedAt");
            r.PriorPes.Should().Contain(p => p.Id == draft.Id && p.Phase == 1,
                "peer Nháp CÓ trong danh sách (list-only) dù đóng góp tiền = 0");
        }
    }

    // =====================================================================
    // C4 — peer-window: peer CreatedAt > phiếu đang xét → loại HOÀN TOÀN (mirror PeBudgetFreezeTests
    //   :435-451). Contrast: peer TRƯỚC vẫn lọt để chứng minh filter phân biệt đúng chiều.
    // =====================================================================
    [Fact]
    public async Task PeerCreatedAfterCurrent_ExcludedFromWindow()
    {
        var (_, fix, db) = CreateService();
        using (fix)
        {
            var (project, wi) = await SeedProjectAndWorkItemAsync(db, "C4");
            // (a) peer TRƯỚC CurCreatedAt (noon) → LỌT.
            var before = await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.ChoDuyet,
                budgetPeriod: 10_000_000m, createdAt: BaseT.AddDays(-1), winnerQuote: 25_000_000m, code: "PE-C4-BEFORE");
            // (b) peer SAU CurCreatedAt → LOẠI hoàn toàn.
            var after = await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.ChoDuyet,
                budgetPeriod: 999_000_000m, createdAt: CurCreatedAt.AddHours(1), winnerQuote: 999_000_000m, code: "PE-C4-AFTER");

            var r = await ComputePendingAsync(db, project.Id, wi.Id);

            r.PendingSelectedTotal.Should().Be(25_000_000m, "chỉ peer TRƯỚC lọt; peer SAU (999tr) bị loại khỏi window");
            r.PendingSelectedCount.Should().Be(1);
            r.PriorPes.Should().ContainSingle().Which.Id.Should().Be(before.Id);
            r.PriorPes.Should().NotContain(p => p.Id == after.Id, "peer CreatedAt > current → ngoài window");
        }
    }

    // =====================================================================
    // C5 — quote ÂM trong PendingSelected (signed-sum): peer ChoDuyet + winner + quote −500k →
    //   PendingSelectedTotal = −500k (báo giá âm = phát-sinh-giảm/hoàn NCC, KHÔNG clamp).
    // =====================================================================
    [Fact]
    public async Task NegativeSelectedQuote_ProducesSignedPendingSelectedTotal()
    {
        var (_, fix, db) = CreateService();
        using (fix)
        {
            var (project, wi) = await SeedProjectAndWorkItemAsync(db, "C5");
            await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.ChoDuyet,
                budgetPeriod: null, createdAt: BaseT.AddDays(-1), winnerQuote: -500_000m, code: "PE-C5-NEG");

            var r = await ComputePendingAsync(db, project.Id, wi.Id);

            r.PendingSelectedTotal.Should().Be(-500_000m, "signed-sum: quote âm giữ dấu, KHÔNG clamp về 0");
            r.PendingSelectedCount.Should().Be(1);
        }
    }

    // =====================================================================
    // C6 REGRESSION chính-xác bất động: cùng seed {DaDuyet+winner, ChoDuyet+winner}, gọi CẢ
    //   ComputeAsync (S133 số CHÍNH XÁC) và ComputePendingAsync (S134 tạm tính) — chứng minh 2 hàm
    //   BỔ SUNG nhau, KHÔNG chồng lấn:
    //     ComputeAsync.PrevSelected  = CHỈ DaDuyet+winner (thêm peer ChoDuyet KHÔNG đổi).
    //     ComputeAsync.PrevSubmitted = ChoDuyet + DaDuyet (như cũ).
    //     ComputePendingAsync.PendingSelected  = CHỈ ChoDuyet+winner (DaDuyet loại).
    //     ComputePendingAsync.PendingSubmitted = 0 (ChoDuyet + DaDuyet đều loại khỏi tạm tính).
    // =====================================================================
    [Fact]
    public async Task Compute_And_ComputePending_AreComplementary_NoDoubleCount()
    {
        var (_, fix, db) = CreateService();
        using (fix)
        {
            var (project, wi) = await SeedProjectAndWorkItemAsync(db, "C6");
            await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.DaDuyet,
                budgetPeriod: 100_000_000m, createdAt: BaseT.AddDays(-3), winnerQuote: 90_000_000m, code: "PE-C6-DD");
            await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.ChoDuyet,
                budgetPeriod: 30_000_000m, createdAt: BaseT.AddDays(-2), winnerQuote: 50_000_000m, code: "PE-C6-CD");

            var curPeId = Guid.NewGuid();
            var exact = await PeBudgetAccumulator.ComputeAsync(
                db, project.Id, wi.Id, curPeId, CurCreatedAt, Array.Empty<Guid>(), CancellationToken.None);
            var pending = await PeBudgetAccumulator.ComputePendingAsync(
                db, project.Id, wi.Id, curPeId, CurCreatedAt, CancellationToken.None);

            // ComputeAsync (S133) — số CHÍNH XÁC KHÔNG bị peer ChoBudget kéo lệch.
            exact.PrevSelectedTotal.Should().Be(90_000_000m, "PrevSelected CHỈ đếm DaDuyet+winner");
            exact.PrevSelectedCount.Should().Be(1, "peer ChoDuyet KHÔNG cộng vào PrevSelected (bất động)");
            exact.PrevSubmittedTotal.Should().Be(130_000_000m, "PrevSubmitted = ChoDuyet 30tr + DaDuyet 100tr (như cũ)");
            exact.PrevSubmittedCount.Should().Be(2);

            // ComputePendingAsync (S134) — bổ sung ChoDuyet winner, KHÔNG chồng DaDuyet.
            pending.PendingSelectedTotal.Should().Be(50_000_000m, "PendingSelected CHỈ ChoDuyet+winner (DaDuyet loại)");
            pending.PendingSelectedCount.Should().Be(1);
            pending.PendingSubmittedTotal.Should().Be(0m, "ChoDuyet + DaDuyet đều loại khỏi PendingSubmitted (no double-count)");
            pending.PendingSubmittedCount.Should().Be(0);
            pending.PriorPes.Should().ContainSingle("chỉ ChoDuyet ∉ {DaDuyet, TuChoi}");
        }
    }

    // =====================================================================
    // C7 — DISPLAY frozen: phiếu DaDuyet (finalize) + SnapshotAt qua GetPurchaseEvaluationQuery →
    //   DTO pending 4 số = 0 + PendingPriorPes null, DÙ tồn tại peer ChoDuyet cùng cặp (nhánh frozen
    //   hardcode defaults, KHÔNG chạy accumulator). Mirror T7 PeBudgetFreezeTests :730.
    // =====================================================================
    [Fact]
    public async Task FrozenQuery_ReturnsZeroPendingAndNullPriorPes_EvenWithPendingPeer()
    {
        var (svc, fix, db) = CreateService();
        using (fix)
        {
            var um = fix.Services.GetRequiredService<UserManager<User>>();
            var (project, wi) = await SeedProjectAndWorkItemAsync(db, "C7");
            await SeedPairAsync(db, project.Id, wi.Id, 200_000_000m);

            // Peer ChoDuyet + winner cùng cặp, TRƯỚC phiếu B → nếu B ở nhánh LIVE sẽ có pending != 0.
            await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.ChoDuyet,
                budgetPeriod: 50_000_000m, createdAt: BaseT.AddDays(-2), winnerQuote: 80_000_000m, code: "PE-C7-PEER");

            var approver = (await fix.CreateUserAsync("appr@c7.test", "Approver", null, Array.Empty<string>())).Id;
            var wf = await SeedFinalizeWorkflowAsync(db, approver, "QT-PA-C7");
            var pe = BuildPeAtFinalizeSlot(wf.Id, project.Id, wi.Id, Guid.NewGuid(), "PE-C7-B");
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);
            pe.CreatedAt = BaseT;   // muộn hơn peer → peer lọt window (nếu live)
            await db.SaveChangesAsync(CancellationToken.None);

            await ApproveWithPriceAsync(svc, pe, approver, Array.Empty<string>());
            pe.Phase.Should().Be(PurchaseEvaluationPhase.DaDuyet);

            var admin = new TestCurrentUser(Guid.NewGuid(), "Admin", null, AppRoles.Admin);
            var bundle = await new GetPurchaseEvaluationQueryHandler(db, um, admin)
                .Handle(new GetPurchaseEvaluationQuery(pe.Id), CancellationToken.None);
            var s = bundle.BudgetSummary!;

            s.BudgetFrozen.Should().BeTrue("phiếu DaDuyet + SnapshotAt → nhánh frozen (test không vacuous)");
            s.PendingSubmittedTotal.Should().Be(0m, "nhánh frozen hardcode 0 dù tồn tại peer ChoDuyet");
            s.PendingSubmittedCount.Should().Be(0);
            s.PendingSelectedTotal.Should().Be(0m, "nhánh frozen KHÔNG chạy accumulator tạm tính");
            s.PendingSelectedCount.Should().Be(0);
            s.PendingPriorPes.Should().BeNull("nhánh frozen serve PendingPriorPes null");
        }
    }

    // =====================================================================
    // C8 — D4 audit: finalize phiếu B tới DaDuyet QUA TransitionAsync (finalize path) khi TỒN TẠI
    //   peer A ChoDuyet cùng cặp → +1 Changelog Action=Update, EntityType=Workflow, UserId=null,
    //   UserName="Hệ thống", ContextNote chứa "[S134 pending-prior]" + MaPhieu A (filter discriminator
    //   ContextNote.Contains — #48 tie-break tránh row Transition). CONTRAST: finalize phiếu KHÔNG có
    //   pending peer → 0 row loại này.
    // =====================================================================
    [Fact]
    public async Task Finalize_WithPendingPriorPeer_WritesAuditChangelog_ContrastNoPeerWritesNone()
    {
        var (svc, fix, db) = CreateService();
        using (fix)
        {
            // ---- Case chính: có peer A ChoDuyet trước phiếu B ----
            var (project, wi) = await SeedProjectAndWorkItemAsync(db, "C8");
            var peerA = await SeedPeerAsync(db, project.Id, wi.Id, PurchaseEvaluationPhase.ChoDuyet,
                budgetPeriod: 50_000_000m, createdAt: BaseT.AddDays(-2), winnerQuote: null, code: "PE-C8-A");

            var approver = (await fix.CreateUserAsync("appr@c8.test", "Approver", null, Array.Empty<string>())).Id;
            var wf = await SeedFinalizeWorkflowAsync(db, approver, "QT-PA-C8");
            var peB = BuildPeAtFinalizeSlot(wf.Id, project.Id, wi.Id, Guid.NewGuid(), "PE-C8-B");
            db.PurchaseEvaluations.Add(peB);
            await db.SaveChangesAsync(CancellationToken.None);
            peB.CreatedAt = BaseT;   // muộn hơn peer A → A lọt window pending
            await db.SaveChangesAsync(CancellationToken.None);

            await ApproveWithPriceAsync(svc, peB, approver, Array.Empty<string>());
            peB.Phase.Should().Be(PurchaseEvaluationPhase.DaDuyet);

            // Filter discriminator ContextNote.Contains (#48 tie-break) → cô lập row D4 khỏi row Transition.
            var d4 = await db.PurchaseEvaluationChangelogs.AsNoTracking()
                .Where(c => c.PurchaseEvaluationId == peB.Id
                    && c.ContextNote != null && c.ContextNote.Contains("[S134 pending-prior]"))
                .ToListAsync(CancellationToken.None);

            d4.Should().ContainSingle("finalize khi còn 1 phiếu trước chưa duyệt → đúng 1 row audit D4");
            var row = d4.Single();
            row.Action.Should().Be(ChangelogAction.Update, "D4 = Update, KHÔNG Transition (gotcha #49 tránh 2 row chuyển phase)");
            row.EntityType.Should().Be(PurchaseEvaluationEntityType.Workflow);
            row.PhaseAtChange.Should().Be(PurchaseEvaluationPhase.DaDuyet);
            row.UserId.Should().BeNull("D4 do hệ thống ghi");
            row.UserName.Should().Be("Hệ thống");
            row.ContextNote.Should().Contain(peerA.MaPhieu!, "audit liệt kê mã phiếu trước còn treo (peer A)");

            // ---- CONTRAST: phiếu C cặp MỚI không peer → 0 row D4 ----
            var (project2, wi2) = await SeedProjectAndWorkItemAsync(db, "C8none");
            var approver2 = (await fix.CreateUserAsync("appr2@c8.test", "Approver2", null, Array.Empty<string>())).Id;
            var wf2 = await SeedFinalizeWorkflowAsync(db, approver2, "QT-PA-C8b");
            var peC = BuildPeAtFinalizeSlot(wf2.Id, project2.Id, wi2.Id, Guid.NewGuid(), "PE-C8-C");
            db.PurchaseEvaluations.Add(peC);
            await db.SaveChangesAsync(CancellationToken.None);
            peC.CreatedAt = BaseT;
            await db.SaveChangesAsync(CancellationToken.None);

            await ApproveWithPriceAsync(svc, peC, approver2, Array.Empty<string>());
            peC.Phase.Should().Be(PurchaseEvaluationPhase.DaDuyet);

            var noneCount = await db.PurchaseEvaluationChangelogs.AsNoTracking()
                .CountAsync(c => c.PurchaseEvaluationId == peC.Id
                    && c.ContextNote != null && c.ContextNote.Contains("[S134 pending-prior]"),
                    CancellationToken.None);
            noneCount.Should().Be(0, "finalize không còn phiếu trước treo → KHÔNG ghi row D4");
        }
    }
}
