using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Common.Exceptions;
using SolutionErp.Domain.ApprovalWorkflowsV2;
using SolutionErp.Domain.Contracts;   // ApprovalDecision enum (shared HĐ/PE)
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.Notifications;
using SolutionErp.Domain.PurchaseEvaluations;
using SolutionErp.Infrastructure.Services;
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Services;

// ===== item 4 (Mig 58 2026-06-24 — CCM/anh Kiệt FDC) — PER-LEVEL finalize "Duyệt là
// KẾT THÚC, không trình CEO" — test theo CODE (S34 rule, KHÔNG touch production).
// ⚠️ [S96 2026-07-01 SPEC CHANGE — anh Kiệt FDC "cái này option thôi"] Cấp cấu hình
// AllowApproverFinalize NAY là OPT-OUT: mặc-định vẫn auto-finalize (applyLevelFinalize=true,
// backward-compat test #1-5) NHƯNG người duyệt BỎ tick (applyLevelFinalize=false) → KHÔNG
// finalize, trình tiếp CEO (test #6). Đảo "AUTO im lặng 2026-06-24" → opt-out có thể hủy.
//
// SPEC item 4 (AUTO, KHÔNG ô-tích runtime): ApprovalWorkflowLevel +AllowApproverFinalize
//   (bool, Mig 58). Trong ApproveV2Async, SAU opinion UPSERT + skipToFinal, TRƯỚC
//   ccm-delegation + advance:
//     if (matchingLevel.AllowApproverFinalize) {
//         ApplyApprovedPriceOnFinalize(...);   // human PHẢI chọn giá chốt (Conflict nếu null)
//         Phase = DaDuyet; clear pointers (StepIdx/LevelOrder/SlaDeadline = null); return;
//     }
//   ⚠️ Quy-trình-driven: cấp được cấu hình KẾT THÚC → duyệt tới đó là TỰ done, KHÔNG cần
//   cờ runtime. KHÔNG threshold, KHÔNG role-gate (admin chỉ-định slot = đủ thẩm quyền). Bỏ
//   qua MỌI Bước/Cấp còn lại (kể cả CEO ở step sau). Wrong-actor vẫn Forbidden TRƯỚC finalize
//   (actor-match guard chạy trước).
//
// Mirror harness PeCcmThresholdFinalizeTests.cs cùng folder (IdentityFixture + SQLite +
//   SeedWorkflowAsync + BuildPeAtApprovalSlot + NoOpNotificationService). Dựng PE TRỰC TIẾP
//   ở ChoDuyet tại slot NON-terminal (còn Step/CEO sau) → drive 1 Approve.
public class PeApproverFinalizeTests
{
    private const decimal ValidApprovedPrice = 400_000_000m;
    private const string ValidApprovedSource = "Ncc";

    private static (PurchaseEvaluationWorkflowService svc, IdentityFixture fix,
        TestApplicationDbContext db, FixedDateTime clock) 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, 6, 24, 0, 0, 0, DateTimeKind.Utc));
        var notify = new NoOpNotificationService();
        var svc = new PurchaseEvaluationWorkflowService(db, clock, notify, um);
        return (svc, fix, db, clock);
    }

    private static PurchaseEvaluation BuildPeAtApprovalSlot(
        Guid approvalWorkflowId, int stepIdx, int levelOrder, string code = "PE-AF-001")
        => new()
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = PurchaseEvaluationPhase.ChoDuyet,
            MaPhieu = code,
            TenGoiThau = "Test item 4 — per-level finalize (auto)",
            ProjectId = Guid.NewGuid(),
            DrafterUserId = Guid.NewGuid(),
            ApprovalWorkflowId = approvalWorkflowId,
            CurrentWorkflowStepIndex = stepIdx,
            CurrentApprovalLevelOrder = levelOrder,
            SlaDeadline = new DateTime(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc),
        };

    // Seed workflow V2. stepApprovers[s][lvl] = (approverUserId, allowFinalize) cho Cấp
    // (Order lvl+1) của Bước (Order s+1). 1 NV / cấp. Return ApprovalWorkflow đã persist.
    private static async Task<ApprovalWorkflow> SeedWorkflowAsync(
        TestApplicationDbContext db, (Guid user, bool allowFinalize)[][] stepApprovers)
    {
        var wf = new ApprovalWorkflow
        {
            Code = "QT-AF-V2",
            Version = 1,
            ApplicableType = ApprovalWorkflowApplicableType.DuyetNcc,
            Name = "QT test item 4 per-level finalize (auto)",
            IsActive = true,
            IsUserSelectable = true,
        };
        for (int s = 0; s < stepApprovers.Length; s++)
        {
            var step = new ApprovalWorkflowStep
            {
                ApprovalWorkflowId = wf.Id,
                Order = s + 1,
                Name = $"Bước {s + 1}",
            };
            for (int lvl = 0; lvl < stepApprovers[s].Length; lvl++)
            {
                var (user, allow) = stepApprovers[s][lvl];
                step.Levels.Add(new ApprovalWorkflowLevel
                {
                    ApprovalWorkflowStepId = step.Id,
                    Order = lvl + 1,
                    Name = $"Cấp {lvl + 1}",
                    ApproverUserId = user,
                    AllowApproverFinalize = allow,
                });
            }
            wf.Steps.Add(step);
        }
        db.ApprovalWorkflows.Add(wf);
        await db.SaveChangesAsync(CancellationToken.None);
        return wf;
    }

    // [S96 opt-out] Cấp cấu hình KẾT THÚC NAY người duyệt BỎ tick được: applyLevelFinalize=false
    // → KHÔNG finalize, trình tiếp CEO. Default true = giữ auto cũ (test #1-5 backward-compat).
    private static Task ApproveAsync(
        PurchaseEvaluationWorkflowService svc, PurchaseEvaluation pe, Guid actorUserId,
        string[] roles,
        decimal? approvedPriceAmount = null,
        string? approvedPriceSource = null,
        bool applyLevelFinalize = true) =>
        svc.TransitionAsync(
            evaluation: pe,
            targetPhase: PurchaseEvaluationPhase.ChoDuyet,   // approve-in-place
            actorUserId: actorUserId,
            actorRoles: roles,
            decision: ApprovalDecision.Approve,
            comment: null,
            approvedPriceAmount: approvedPriceAmount,
            approvedPriceSource: approvedPriceSource,
            applyLevelFinalize: applyLevelFinalize,
            ct: CancellationToken.None);

    // =====================================================================
    // 1. ⭐ LOAD-BEARING — slot NON-terminal AllowApproverFinalize=true + approver duyệt
    //    (+ giá chốt) → TỰ DaDuyet, bỏ CEO (Step 2), pointers cleared. AUTO, không ô-tích.
    // =====================================================================
    [Fact]
    public async Task ApproveV2_LevelAllowsFinalize_AutoFinalizesDaDuyet_SkipsCeo_PointersCleared()
    {
        var (svc, fix, db, _) = CreateService();
        using (fix)
        {
            // Bước 1 Cấp 1 = approver (AllowApproverFinalize=TRUE, đang đứng đây).
            // Bước 2 Cấp 1 = CEO (sẽ bị BỎ QUA hoàn toàn).
            var approver = (await fix.CreateUserAsync("appr@af.test", "Approver", null, Array.Empty<string>())).Id;
            var ceo = (await fix.CreateUserAsync("ceo@af.test", "CEO", null, new[] { AppRoles.Director })).Id;

            var wf = await SeedWorkflowAsync(db, new[]
            {
                new[] { (approver, true) },    // Bước 1 Cấp 1 = approver, finalize-allowed
                new[] { (ceo, false) },        // Bước 2 Cấp 1 = CEO (bỏ qua)
            });

            var pe = BuildPeAtApprovalSlot(wf.Id, stepIdx: 0, levelOrder: 1);
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            await ApproveAsync(svc, pe, approver, Array.Empty<string>(),
                approvedPriceAmount: ValidApprovedPrice, approvedPriceSource: ValidApprovedSource);

            pe.Phase.Should().Be(PurchaseEvaluationPhase.DaDuyet,
                "slot AllowApproverFinalize → duyệt tới đây là TỰ kết thúc, bỏ CEO");
            pe.CurrentWorkflowStepIndex.Should().BeNull("terminal → step pointer null");
            pe.CurrentApprovalLevelOrder.Should().BeNull("terminal → level pointer null");
            pe.SlaDeadline.Should().BeNull("terminal → SLA null");
            pe.ApprovedPriceAmount.Should().Be(ValidApprovedPrice, "giá chốt bind khi finalize");
            pe.ApprovedPriceSource.Should().Be(ValidApprovedSource);
            // [S97 anh Kiệt FDC] nhánh level-finalize set RUNTIME flag → EndsBeforeCeo display đọc field này.
            pe.EndedByLevelFinalize.Should().BeTrue("finalize sớm tại cấp này → cờ runtime = true");

            // CEO KHÔNG được ghi opinion (bị bỏ qua — chỉ approver Bước 1 ký).
            var opinions = await db.PurchaseEvaluationLevelOpinions
                .Where(o => o.PurchaseEvaluationId == pe.Id).ToListAsync();
            var approverLevelId = wf.Steps.First(s => s.Order == 1).Levels.First(l => l.Order == 1).Id;
            opinions.Should().ContainSingle(o => o.ApprovalWorkflowLevelId == approverLevelId,
                "chỉ slot approver ký, không ghi hộ CEO");

            // 1 Approval Approve (của approver), finalize-branch return → KHÔNG double.
            var approvals = await db.PurchaseEvaluationApprovals
                .Where(a => a.PurchaseEvaluationId == pe.Id && a.Decision == ApprovalDecision.Approve)
                .ToListAsync();
            approvals.Should().HaveCount(1, "1 lần duyệt = 1 Approval row, không double-finalize");
        }
    }

    // =====================================================================
    // 2. CONFIG GATES IT — slot AllowApproverFinalize=FALSE + approver duyệt → KHÔNG
    //    finalize, advance bình thường lên CEO (Step 2). Chứng minh CHỈ cấp được cấu
    //    hình KẾT THÚC mới dừng; cấp thường vẫn trình tiếp.
    // =====================================================================
    [Fact]
    public async Task ApproveV2_LevelNotAllowed_AdvancesToCeo_PhaseStaysChoDuyet()
    {
        var (svc, fix, db, clock) = CreateService();
        using (fix)
        {
            var approver = (await fix.CreateUserAsync("appr2@af.test", "Approver", null, Array.Empty<string>())).Id;
            var ceo = (await fix.CreateUserAsync("ceo2@af.test", "CEO", null, new[] { AppRoles.Director })).Id;

            var wf = await SeedWorkflowAsync(db, new[]
            {
                new[] { (approver, false) },   // ⭐ slot KHÔNG cấu hình KẾT THÚC
                new[] { (ceo, false) },
            });

            var pe = BuildPeAtApprovalSlot(wf.Id, stepIdx: 0, levelOrder: 1, code: "PE-AF-002");
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            await ApproveAsync(svc, pe, approver, Array.Empty<string>());   // không giá chốt — chưa terminal

            pe.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet,
                "slot không cấu hình kết thúc → advance bình thường lên CEO");
            pe.CurrentWorkflowStepIndex.Should().Be(1, "advance sang Bước 2 (CEO)");
            pe.CurrentApprovalLevelOrder.Should().Be(1);
            pe.SlaDeadline.Should().Be(clock.UtcNow.AddDays(7), "SLA reset cho CEO");
            pe.ApprovedPriceAmount.Should().BeNull("chưa terminal → chưa bind giá chốt");
            // [S97] KHÔNG qua nhánh finalize (advance lên CEO) → cờ runtime giữ false → EndsBeforeCeo false.
            pe.EndedByLevelFinalize.Should().BeFalse("advance lên CEO (không finalize) → cờ runtime = false");
        }
    }

    // =====================================================================
    // 3. PRICE REQUIRED — slot allow=true NHƯNG approvedPrice null (human) →
    //    Conflict("giá chốt"). Finalize cũng cần giá chốt như terminal cuối.
    // =====================================================================
    [Fact]
    public async Task ApproveV2_LevelAllowsFinalize_NullPrice_ThrowsConflict()
    {
        var (svc, fix, db, _) = CreateService();
        using (fix)
        {
            var approver = (await fix.CreateUserAsync("appr3@af.test", "Approver", null, Array.Empty<string>())).Id;
            var ceo = (await fix.CreateUserAsync("ceo3@af.test", "CEO", null, new[] { AppRoles.Director })).Id;

            var wf = await SeedWorkflowAsync(db, new[]
            {
                new[] { (approver, true) },
                new[] { (ceo, false) },
            });

            var pe = BuildPeAtApprovalSlot(wf.Id, stepIdx: 0, levelOrder: 1, code: "PE-AF-003");
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            // slot allowed NHƯNG KHÔNG truyền giá chốt → finalize đòi giá chốt.
            var act = async () => await ApproveAsync(svc, pe, approver, Array.Empty<string>(),
                approvedPriceAmount: null, approvedPriceSource: null);

            await act.Should().ThrowAsync<ConflictException>()
                .WithMessage("*giá chốt*");

            var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
            reloaded.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet, "thiếu giá chốt → không finalize");
        }
    }

    // =====================================================================
    // 4. WRONG-ACTOR precedence — approver-match guard chạy TRƯỚC finalize-branch:
    //    NV KHÔNG thuộc Cấp hiện tại duyệt → Forbidden (không phải lượt), KHÔNG tới
    //    được finalize logic. Bảo vệ forge request.
    // =====================================================================
    [Fact]
    public async Task ApproveV2_ActorNotInLevel_ThrowsForbidden_BeforeFinalize()
    {
        var (svc, fix, db, _) = CreateService();
        using (fix)
        {
            var realApprover = (await fix.CreateUserAsync("real4@af.test", "Real Approver", null, Array.Empty<string>())).Id;
            var intruder = (await fix.CreateUserAsync("intruder4@af.test", "Intruder", null, Array.Empty<string>())).Id;
            var ceo = (await fix.CreateUserAsync("ceo4@af.test", "CEO", null, new[] { AppRoles.Director })).Id;

            var wf = await SeedWorkflowAsync(db, new[]
            {
                new[] { (realApprover, true) },   // slot cấu hình kết thúc — nhưng intruder KHÔNG phải người này
                new[] { (ceo, false) },
            });

            var pe = BuildPeAtApprovalSlot(wf.Id, stepIdx: 0, levelOrder: 1, code: "PE-AF-004");
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            // intruder (không trong Cấp) duyệt → Forbidden TRƯỚC finalize logic.
            var act = async () => await ApproveAsync(svc, pe, intruder, Array.Empty<string>(),
                approvedPriceAmount: ValidApprovedPrice, approvedPriceSource: ValidApprovedSource);

            await act.Should().ThrowAsync<ForbiddenException>();

            var reloaded = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
            reloaded.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet, "không phải lượt → không finalize");
        }
    }

    // =====================================================================
    // 5. AT-LAST-SLOT — slot CUỐI (1 bước 1 cấp) + allow=true → DaDuyet qua finalize-
    //    branch (chạy TRƯỚC normal terminal advance). 1 Approval, không double.
    // =====================================================================
    [Fact]
    public async Task ApproveV2_AtLastSlot_LevelAllowsFinalize_FinalizesDaDuyet_NoDoubleApproval()
    {
        var (svc, fix, db, _) = CreateService();
        using (fix)
        {
            // 1 bước 1 cấp = approver (slot cuối luôn), allow finalize.
            var approver = (await fix.CreateUserAsync("appr5@af.test", "Approver", null, Array.Empty<string>())).Id;

            var wf = await SeedWorkflowAsync(db, new[]
            {
                new[] { (approver, true) },
            });

            var pe = BuildPeAtApprovalSlot(wf.Id, stepIdx: 0, levelOrder: 1, code: "PE-AF-005");
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            await ApproveAsync(svc, pe, approver, Array.Empty<string>(),
                approvedPriceAmount: ValidApprovedPrice, approvedPriceSource: ValidApprovedSource);

            pe.Phase.Should().Be(PurchaseEvaluationPhase.DaDuyet,
                "finalize ở slot cuối → DaDuyet qua finalize-branch (trước normal advance)");
            pe.CurrentWorkflowStepIndex.Should().BeNull();
            pe.CurrentApprovalLevelOrder.Should().BeNull();
            pe.ApprovedPriceAmount.Should().Be(ValidApprovedPrice);

            var approvals = await db.PurchaseEvaluationApprovals
                .Where(a => a.PurchaseEvaluationId == pe.Id && a.Decision == ApprovalDecision.Approve)
                .ToListAsync();
            approvals.Should().HaveCount(1, "1 lần duyệt = 1 Approval row, không double");
        }
    }

    // =====================================================================
    // 6. ⭐ OPT-OUT (S96 2026-07-01 — anh Kiệt FDC "cái này option thôi") — slot
    //    AllowApproverFinalize=TRUE NHƯNG người duyệt BỎ tick (applyLevelFinalize=FALSE)
    //    → KHÔNG finalize, advance bình thường lên CEO (Step 2). Đảo AUTO (test #1): cùng
    //    cấu hình cấp kết-thúc, chỉ khác cờ opt-out → KHÔNG DaDuyet, KHÔNG cần giá chốt.
    // =====================================================================
    [Fact]
    public async Task ApproveV2_LevelAllowsFinalize_ApproverUnchecks_AdvancesToCeo_NotFinalized()
    {
        var (svc, fix, db, clock) = CreateService();
        using (fix)
        {
            // Bước 1 Cấp 1 = approver (AllowApproverFinalize=TRUE) — nhưng người duyệt BỎ tick.
            // Bước 2 Cấp 1 = CEO (LẦN NÀY phải tới được vì bỏ tick kết-thúc).
            var approver = (await fix.CreateUserAsync("appr6@af.test", "Approver", null, Array.Empty<string>())).Id;
            var ceo = (await fix.CreateUserAsync("ceo6@af.test", "CEO", null, new[] { AppRoles.Director })).Id;

            var wf = await SeedWorkflowAsync(db, new[]
            {
                new[] { (approver, true) },     // slot CẤU HÌNH kết thúc — nhưng opt-out
                new[] { (ceo, false) },         // CEO Step 2 — phải tới được
            });

            var pe = BuildPeAtApprovalSlot(wf.Id, stepIdx: 0, levelOrder: 1, code: "PE-AF-006");
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            // ⭐ BỎ tick "Cấp này KẾT THÚC" → applyLevelFinalize=false. KHÔNG cần giá chốt (chưa terminal).
            await ApproveAsync(svc, pe, approver, Array.Empty<string>(), applyLevelFinalize: false);

            pe.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet,
                "bỏ tick KẾT THÚC → KHÔNG finalize, advance bình thường lên CEO");
            pe.CurrentWorkflowStepIndex.Should().Be(1, "advance sang Bước 2 (CEO) vì opt-out");
            pe.CurrentApprovalLevelOrder.Should().Be(1);
            pe.SlaDeadline.Should().Be(clock.UtcNow.AddDays(7), "SLA reset cho CEO");
            pe.ApprovedPriceAmount.Should().BeNull("opt-out → chưa terminal → chưa bind giá chốt");
            // [S97 bug anh Kiệt] config-có-finalize NHƯNG opt-out → lên CEO thật → cờ runtime PHẢI
            // false (đây chính là kịch bản projection repro EndsBeforeCeo=false, không hiện sai badge).
            pe.EndedByLevelFinalize.Should().BeFalse(
                "opt-out finalize → phiếu lên CEO thật → cờ runtime false → EndsBeforeCeo sẽ false");

            // approver VẪN ghi opinion (đã duyệt cấp mình), CEO chưa.
            var opinions = await db.PurchaseEvaluationLevelOpinions
                .Where(o => o.PurchaseEvaluationId == pe.Id).ToListAsync();
            opinions.Should().ContainSingle("approver ký cấp mình rồi mới đẩy lên CEO");
        }
    }

    // =====================================================================
    // 7. ⭐ STALE-NOTI CLEAR (S96 2026-07-01 — anh Kiệt FDC "chuông không bỏ ra được") —
    //    phiếu tới Cấp CEO → CEO có noti "cần bạn duyệt" (Generic + RefId=phiếu). Khi phiếu
    //    FINALIZE (duyệt-thay/kết-thúc hộ tại đây) → noti CŨ của CEO = stale → TỰ mark-read
    //    (ReadAt set) để chuông sạch. Trực-tiếp vá "noti cần-duyệt không tự bỏ khi phiếu xong".
    // =====================================================================
    [Fact]
    public async Task ApproveV2_Finalize_MarksStalePendingNotificationRead()
    {
        var (svc, fix, db, _) = CreateService();
        using (fix)
        {
            var approver = (await fix.CreateUserAsync("appr7@af.test", "Approver", null, Array.Empty<string>())).Id;
            var ceo = (await fix.CreateUserAsync("ceo7@af.test", "CEO", null, new[] { AppRoles.Director })).Id;

            var wf = await SeedWorkflowAsync(db, new[]
            {
                new[] { (approver, true) },     // slot cấu hình KẾT THÚC — approver duyệt-thay hộ CEO
                new[] { (ceo, false) },
            });

            var pe = BuildPeAtApprovalSlot(wf.Id, stepIdx: 0, levelOrder: 1, code: "PE-AF-007");
            db.PurchaseEvaluations.Add(pe);
            // Giả-lập noti CŨ "cần bạn duyệt" của CEO cho phiếu này (RefId=phiếu, Generic, chưa đọc).
            db.Notifications.Add(new Notification
            {
                UserId = ceo,
                Type = NotificationType.Generic,
                Title = "Phiếu cần bạn duyệt: PE-AF-007",
                RefId = pe.Id,
                ReadAt = null,
            });
            await db.SaveChangesAsync(CancellationToken.None);

            await ApproveAsync(svc, pe, approver, Array.Empty<string>(),
                approvedPriceAmount: ValidApprovedPrice, approvedPriceSource: ValidApprovedSource);

            pe.Phase.Should().Be(PurchaseEvaluationPhase.DaDuyet, "finalize");
            var ceoNotif = await db.Notifications.AsNoTracking()
                .FirstAsync(n => n.UserId == ceo && n.RefId == pe.Id);
            ceoNotif.ReadAt.Should().NotBeNull(
                "phiếu finalize → noti 'cần bạn duyệt' cũ của CEO tự mark-read, chuông sạch (không còn phiếu thừa)");
        }
    }
}
