using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Notifications;
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;

// ===== BUG-FIX regression S86 (anh Kiệt FDC UAT realtime, Zalo 16:05) =====
// Bug: ở Phiếu Duyệt NCC, NGƯỜI TẠO phiếu (drafter) nhận chuông "Phiếu cần bạn
// duyệt" cho chính phiếu mình tạo. Anh: "Các nhân viên ko cần thấy thông báo nhé,
// chỉ thông báo đúng người cần duyệt thôi."
//
// Root cause + fix (em-main, PurchaseEvaluationWorkflowService.LogTransitionAsync
// ~:1083-1135, khối notify "Phiếu cần bạn duyệt" :1117 — NotifyManyAsync):
//   approverIds resolve từ workflow.Steps[CurrentWorkflowStepIndex]
//     .Levels.Where(Order == CurrentApprovalLevelOrder), lọc
//     ApproverUserId != Guid.Empty && != actorUserId.
//   THIẾU lọc drafter → khi drafter cũng là approver 1 Cấp (hoặc phiếu advance TỚI
//   Cấp đó, actor != drafter) → drafter nhận chuông cần-duyệt phiếu mình tạo.
//   Fix: thêm `&& l.ApproverUserId != evaluation.DrafterUserId` vào filter.
//   Block notify KẾT-QUẢ cho drafter (:1060 — "đã duyệt"/"trả lại") GIỮ NGUYÊN.
//
// Test theo CODE (S34): capturing INotificationService fake bắt recipients của
// NotifyManyAsync (khác NoOpNotificationService trong cùng folder — cái đó NUỐT).
// Notify path là best-effort try/catch trong service NHƯNG vì DI nhận
// INotificationService trực tiếp ở ctor → fake-capture là cách spy chuẩn,
// KHÔNG cần WebApplicationFactory.
//
// Harness mirror PeCcmThresholdFinalizeTests.cs cùng folder (IdentityFixture +
// SQLite + SeedWorkflowAsync). Dựng PE ở ChoDuyet (qua submit guard) + pin pointer
// Step/Level → drive 1 Approve để phiếu ADVANCE tới Cấp có drafter.
//
// ⭐ RED-on-old-code: nếu gỡ điều-kiện `!= DrafterUserId` → Test 1 + Test 2 FAIL
//   (recipients sẽ chứa drafterId). actor-exclusion (!= actorUserId) KHÔNG che bug
//   vì actor = approver Cấp-1 (KHÁC drafter); drafter đứng Cấp-2 → identity riêng.
public class PeApproverNotifyExcludesDrafterTests
{
    // Tiêu đề khối notify cần-duyệt (prod :1123). Lọc đúng notification cần assert
    // (tránh nhầm với notify kết-quả drafter dùng tiêu đề khác).
    private const string NeedApprovalTitlePrefix = "Phiếu cần bạn duyệt";

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

    // PE đứng ở ChoDuyet tại pointer (stepIdx, levelOrder), pin V2 + drafter cụ thể.
    private static PurchaseEvaluation BuildPeAtApprovalSlot(
        Guid approvalWorkflowId, Guid drafterUserId, int stepIdx, int levelOrder,
        string code = "PE-S86-001")
    {
        return new PurchaseEvaluation
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = PurchaseEvaluationPhase.ChoDuyet,
            MaPhieu = code,
            TenGoiThau = "Test S86 — drafter KHÔNG nhận chuông cần-duyệt",
            ProjectId = Guid.NewGuid(),
            DrafterUserId = drafterUserId,
            ApprovalWorkflowId = approvalWorkflowId,
            CurrentWorkflowStepIndex = stepIdx,
            CurrentApprovalLevelOrder = levelOrder,
            SlaDeadline = new DateTime(2026, 6, 30, 0, 0, 0, DateTimeKind.Utc),
        };
    }

    // Seed workflow V2: stepLevelApprovers[s][lvl] = mảng NV (OR-of-N) cho Cấp lvl+1
    // (Order 1-based) của Bước s (Order s+1). Cho phép nhiều NV/cấp để dựng case
    // drafter + approver-khác cùng 1 Cấp. Return ApprovalWorkflow đã persist.
    private static async Task<ApprovalWorkflow> SeedWorkflowAsync(
        TestApplicationDbContext db, Guid[][][] stepLevelApprovers)
    {
        var wf = new ApprovalWorkflow
        {
            Code = "QT-S86-V2",
            Version = 1,
            ApplicableType = ApprovalWorkflowApplicableType.DuyetNcc,
            Name = "QT test S86 notify",
            IsActive = true,
            IsUserSelectable = true,
        };
        for (int s = 0; s < stepLevelApprovers.Length; s++)
        {
            var step = new ApprovalWorkflowStep
            {
                ApprovalWorkflowId = wf.Id,
                Order = s + 1,
                Name = $"Bước {s + 1}",
            };
            for (int lvl = 0; lvl < stepLevelApprovers[s].Length; lvl++)
            {
                foreach (var approverId in stepLevelApprovers[s][lvl])
                {
                    step.Levels.Add(new ApprovalWorkflowLevel
                    {
                        ApprovalWorkflowStepId = step.Id,
                        Order = lvl + 1,
                        Name = $"Cấp {lvl + 1}",
                        ApproverUserId = approverId,
                    });
                }
            }
            wf.Steps.Add(step);
        }
        db.ApprovalWorkflows.Add(wf);
        await db.SaveChangesAsync(CancellationToken.None);
        return wf;
    }

    // approvedPrice* chỉ cần khi approve cấp cuối → terminal DaDuyet
    // (ApplyApprovedPriceOnFinalize bắt human chọn 1 giá chốt, Mig 54). Advance
    // trung gian (Test 1/2) KHÔNG cần → mặc định null.
    private static Task ApproveAsync(
        PurchaseEvaluationWorkflowService svc, PurchaseEvaluation pe, Guid actorUserId, string[] roles,
        decimal? approvedPriceAmount = null, string? approvedPriceSource = null) =>
        svc.TransitionAsync(
            evaluation: pe,
            targetPhase: PurchaseEvaluationPhase.ChoDuyet,  // approve-in-place (advance pointer)
            actorUserId: actorUserId,
            actorRoles: roles,
            decision: ApprovalDecision.Approve,
            comment: null,
            approvedPriceAmount: approvedPriceAmount,
            approvedPriceSource: approvedPriceSource,
            ct: CancellationToken.None);

    // Gom recipients của MỌI call NotifyManyAsync có tiêu đề "Phiếu cần bạn duyệt".
    private static List<Guid> NeedApprovalRecipients(CapturingNotificationService notify) =>
        notify.ManyCalls
            .Where(c => c.Title.StartsWith(NeedApprovalTitlePrefix, StringComparison.Ordinal))
            .SelectMany(c => c.Recipients)
            .ToList();

    // =====================================================================
    // 1. ⭐ CORE REGRESSION — phiếu ADVANCE tới Cấp mà drafter là 1 approver
    //    (cùng 1 approver-KHÁC). Actor = approver Cấp 1 (KHÔNG phải drafter).
    //    Approve → pointer sang Cấp 2 → notify Cấp-2 approvers.
    //    Assert: recipients KHÔNG chứa drafter NHƯNG CÓ approver-khác.
    //    RED nếu gỡ `!= DrafterUserId`: drafter lọt vào recipients.
    // =====================================================================
    [Fact]
    public async Task ApproveV2_AdvancesToLevelWhereDrafterIsApprover_NeedApprovalNotify_ExcludesDrafter_IncludesOther()
    {
        var (svc, fix, db, notify) = CreateService();
        using (fix)
        {
            // Cấp 1 (đang đứng): approverL1 — chính là actor sẽ duyệt (KHÁC drafter).
            // Cấp 2 (advance tới): { drafter, otherApproverL2 }.
            var drafter = (await fix.CreateUserAsync("drafter1@s86.test", "Người tạo", null, new[] { AppRoles.Procurement })).Id;
            var approverL1 = (await fix.CreateUserAsync("apprL1@s86.test", "Approver Cấp 1", null, new[] { AppRoles.ProjectManager })).Id;
            var otherApproverL2 = (await fix.CreateUserAsync("otherL2@s86.test", "Approver Cấp 2 khác", null, new[] { AppRoles.CostControl })).Id;

            var wf = await SeedWorkflowAsync(db, new[]
            {
                new[]                                   // Bước 1
                {
                    new[] { approverL1 },               //   Cấp 1 = approverL1 (actor)
                    new[] { drafter, otherApproverL2 }, //   Cấp 2 = drafter + người khác
                },
            });

            // PE đứng Bước 1 (stepIdx 0) Cấp 1 — đến lượt approverL1. Drafter = chủ phiếu.
            var pe = BuildPeAtApprovalSlot(wf.Id, drafter, stepIdx: 0, levelOrder: 1);
            db.PurchaseEvaluations.Add(pe);
            await db.SaveChangesAsync(CancellationToken.None);

            // approverL1 (KHÔNG phải drafter) duyệt → advance Cấp 1→2.
            await ApproveAsync(svc, pe, approverL1, new[] { AppRoles.ProjectManager });

            // Sanity: phiếu đã advance tới Cấp 2 (pointer đúng nơi notify resolve).
            pe.Phase.Should().Be(PurchaseEvaluationPhase.ChoDuyet);
            pe.CurrentWorkflowStepIndex.Should().Be(0);
            pe.CurrentApprovalLevelOrder.Should().Be(2, "advance sang Cấp 2 cùng Bước 1");

            var recipients = NeedApprovalRecipients(notify);
            recipients.Should().NotBeEmpty(
                "Cấp 2 có 1 approver-khác (otherApproverL2) → vẫn phát chuông cần-duyệt");
            recipients.Should().NotContain(drafter,
                "⭐ S86: NGƯỜI TẠO phiếu KHÔNG nhận chuông 'cần bạn duyệt' dù được cấu hình làm approver Cấp 2 " +
                "(RED nếu gỡ điều-kiện `!= DrafterUserId`)");
            recipients.Should().Contain(otherApproverL2,
                "approver-khác (không phải người tạo) VẪN nhận chuông cần-duyệt — fix chỉ lọc drafter");
        }
    }

    // =====================================================================
    // 2. Drafter là approver DUY NHẤT ở Cấp advance tới → KHÔNG phát chuông
    //    cần-duyệt nào cả (sau khi lọc drafter, approverIds rỗng → NotifyManyAsync
    //    không được gọi cho khối này). RED nếu gỡ lọc: 1 call chứa drafter.
    // =====================================================================
    [Fact]
    public async Task ApproveV2_AdvancesToLevelWhereDrafterIsSoleApprover_EmitsNoNeedApprovalNotify()
    {
        var (svc, fix, db, notify) = CreateService();
        using (fix)
        {
            var drafter = (await fix.CreateUserAsync("drafter2@s86.test", "Người tạo", null, new[] { AppRoles.Procurement })).Id;
            var approverL1 = (await fix.CreateUserAsync("apprL1b@s86.test", "Approver Cấp 1", null, new[] { AppRoles.ProjectManager })).Id;

            var wf = await SeedWorkflowAsync(db, new[]
            {
                new[]                          // Bước 1
                {
                    new[] { approverL1 },      //   Cấp 1 = approverL1 (actor)
                    new[] { drafter },         //   Cấp 2 = CHỈ drafter
                },
            });

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

            await ApproveAsync(svc, pe, approverL1, new[] { AppRoles.ProjectManager });

            pe.CurrentApprovalLevelOrder.Should().Be(2, "advance sang Cấp 2 (chỉ drafter)");

            var recipients = NeedApprovalRecipients(notify);
            recipients.Should().BeEmpty(
                "Cấp 2 chỉ có drafter → sau khi lọc drafter, danh sách rỗng → KHÔNG phát chuông cần-duyệt " +
                "(RED nếu gỡ `!= DrafterUserId`: drafter lọt vào)");
            recipients.Should().NotContain(drafter);
        }
    }

    // =====================================================================
    // 3. SANITY — fix KHÔNG over-broad: drafter VẪN nhận chuông KẾT-QUẢ khi phiếu
    //    về terminal DaDuyet (block :1060 NotifyAsync giữ nguyên). Bảo vệ chống
    //    misfix kiểu "drafter không bao giờ được notify". Actor = approver cấp cuối
    //    (KHÁC drafter) để qua nhánh `drafterId != actorUserId`.
    // =====================================================================
    [Fact]
    public async Task ApproveV2_TerminalDaDuyet_DrafterStillReceivesResultNotification()
    {
        var (svc, fix, db, notify) = CreateService();
        using (fix)
        {
            var drafter = (await fix.CreateUserAsync("drafter3@s86.test", "Người tạo", null, new[] { AppRoles.Procurement })).Id;
            var approver = (await fix.CreateUserAsync("apprFinal@s86.test", "Approver cuối", null, new[] { AppRoles.Director })).Id;

            // Workflow 1 Bước, 1 Cấp = approver (slot cuối) → duyệt = terminal DaDuyet.
            var wf = await SeedWorkflowAsync(db, new[]
            {
                new[] { new[] { approver } },   // Bước 1 Cấp 1 = approver
            });

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

            // approve slot cuối → terminal DaDuyet ⇒ Mig 54 buộc chọn 1 giá chốt.
            await ApproveAsync(svc, pe, approver, new[] { AppRoles.Director },
                approvedPriceAmount: 500_000_000m, approvedPriceSource: "Ncc");

            pe.Phase.Should().Be(PurchaseEvaluationPhase.DaDuyet, "approve slot cuối → terminal");

            // Drafter NHẬN chuông KẾT-QUẢ "đã duyệt" (NotifyAsync single — block :1060).
            notify.SingleCalls.Should().Contain(
                c => c.UserId == drafter && c.Title.Contains("đã duyệt"),
                "drafter VẪN được báo kết-quả phiếu đã duyệt — fix S86 chỉ chặn chuông CẦN-DUYỆT, " +
                "KHÔNG chặn chuông kết-quả");

            // Đồng thời KHÔNG có chuông cần-duyệt nào (terminal, không còn cấp chờ).
            NeedApprovalRecipients(notify).Should().BeEmpty(
                "phiếu terminal → không advance tới cấp nào → không phát chuông cần-duyệt");
        }
    }
}

// ===== Capturing fake — bắt mọi NotifyAsync / NotifyManyAsync call =====
// Khác NoOpNotificationService (nuốt im lặng) trong cùng folder: ghi lại recipients
// + title để assert. Service nhận INotificationService trực tiếp ở ctor → spy chuẩn.
internal sealed class CapturingNotificationService : INotificationService
{
    public sealed record SingleCall(Guid UserId, NotificationType Type, string Title);
    public sealed record ManyCall(IReadOnlyList<Guid> Recipients, NotificationType Type, string Title);

    public List<SingleCall> SingleCalls { get; } = new();
    public List<ManyCall> ManyCalls { get; } = new();

    public Task NotifyAsync(
        Guid userId,
        NotificationType type,
        string title,
        string? description = null,
        string? href = null,
        Guid? refId = null,
        CancellationToken ct = default)
    {
        SingleCalls.Add(new SingleCall(userId, type, title));
        return Task.CompletedTask;
    }

    public Task NotifyManyAsync(
        IEnumerable<Guid> userIds,
        NotificationType type,
        string title,
        string? description = null,
        string? href = null,
        Guid? refId = null,
        CancellationToken ct = default)
    {
        ManyCalls.Add(new ManyCall(userIds.ToList(), type, title));
        return Task.CompletedTask;
    }
}
