using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SolutionErp.Application.Common.Interfaces;
using SolutionErp.Application.PurchaseEvaluations;
using SolutionErp.Domain.Identity;
using SolutionErp.Domain.Notifications;
using SolutionErp.Domain.PurchaseEvaluations;
using SolutionErp.Infrastructure.Services;     // real NotificationService (Add-only, caller flushes)
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Application;

// [S88 FIX 3 — bug-fix anh Kiệt FDC] CEO-notify-on-urgent ĐÃ persist (trước rớt âm thầm).
//
// BUG (đã fix em-main, PurchaseEvaluationUrgentFeatures.cs): khối notify Director chạy SAU
//   `db.SaveChangesAsync` DUY NHẤT ở line ~75. NotifyManyAsync CHỈ Add (caller tự flush) →
//   chuông Director chưa từng được lưu (rớt + KHÔNG trigger SignalR push).
// FIX: thêm `await db.SaveChangesAsync(ct)` SAU NotifyManyAsync (trong if, vẫn best-effort
//   try/catch). ⇒ row Notification cho Director thực sự persist.
//
// Test theo CODE đã land (S34 — KHÔNG touch production). Khác PeUrgentToggleAuthzTests
//   (NoOpNotificationService nuốt → không observe được persistence): ở ĐÂY dùng REAL
//   NotificationService (Infrastructure) — nó Add Notification vào db (caller flush), khớp
//   đúng giả định production. Persistence quan sát qua db.Notifications sau handler.
//
// Gate notify (line 80): `request.IsUrgent && nowUrgent && !wasUrgent` — CHỈ khi MỚI bật
//   gấp (false→true). wasUrgent = snapshot 2 cờ TRƯỚC mutate. Suy ra:
//     • false→true  → notify (1 row / Director).
//     • true→true   → !wasUrgent = false → KHÔNG notify (no duplicate — guard chống spam).
//     • true→false  → request.IsUrgent = false → KHÔNG notify (gỡ cờ không báo).
//
// Handler 4 dep: (IApplicationDbContext, ICurrentUser, UserManager<User>, INotificationService).
public class PeUrgentNotifyDirectorTests
{
    private sealed class FakeCurrentUser(params string[] roles) : ICurrentUser
    {
        public Guid? UserId { get; init; } = Guid.NewGuid();
        public string? Email { get; } = "actor@test.local";
        public string? FullName { get; } = "Actor Test";
        public IReadOnlyList<string> Roles { get; } = roles ?? Array.Empty<string>();
        public bool IsAuthenticated => UserId is not null;
    }

    // REAL NotificationService — Add-only (KHÔNG SaveChanges), đúng hành vi production mà
    // handler dựa vào (handler tự flush sau NotifyManyAsync). FixedDateTime cho CreatedAt.
    private static SetPurchaseEvaluationUrgentCommandHandler BuildHandler(
        TestApplicationDbContext db, UserManager<User> um, ICurrentUser currentUser)
    {
        var clock = new FixedDateTime(new DateTime(2026, 6, 24, 9, 0, 0, DateTimeKind.Utc));
        var notify = new NotificationService(db, clock);
        return new SetPurchaseEvaluationUrgentCommandHandler(db, currentUser, um, notify);
    }

    private static async Task<PurchaseEvaluation> SeedPeAsync(
        TestApplicationDbContext db,
        bool urgentByPro = false,
        bool urgentByCcm = false,
        string code = "PE-URGN-001",
        string maPhieu = "PE-URGN-001")
    {
        var pe = new PurchaseEvaluation
        {
            Id = Guid.NewGuid(),
            Type = PurchaseEvaluationType.DuyetNcc,
            Phase = PurchaseEvaluationPhase.ChoDuyet,
            MaPhieu = maPhieu,
            TenGoiThau = "Gói thầu test urgent-notify " + code,
            ProjectId = Guid.NewGuid(),
            DrafterUserId = Guid.NewGuid(),
            IsUrgentByPro = urgentByPro,
            IsUrgentByCcm = urgentByCcm,
        };
        db.PurchaseEvaluations.Add(pe);
        await db.SaveChangesAsync(CancellationToken.None);
        return pe;
    }

    // =====================================================================
    // 1. false→true bởi PRO → Director Notification row PERSIST (title "GẤP" + RefId = PE id).
    //    Đây là bug đã sửa: trước row này rớt vì save trước khối notify.
    // =====================================================================
    [Fact]
    public async Task SetUrgent_FalseToTrue_ByProcurement_PersistsDirectorNotification()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();

        var director = await fix.CreateUserAsync(
            "ceo@test.local", "Giám đốc", departmentId: null, roles: new[] { AppRoles.Director });
        var pe = await SeedPeAsync(db, maPhieu: "PE-GAP-01");

        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Procurement));
        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        // Chuông Director PHẢI được lưu (FIX 3) — đọc lại bằng context riêng để chắc đã flush.
        var rows = await db.Notifications.AsNoTracking()
            .Where(n => n.UserId == director.Id && n.RefId == pe.Id).ToListAsync();
        rows.Should().HaveCount(1, "false→true → đúng 1 chuông gửi Giám đốc, và ĐÃ persist (bug rớt-chuông đã sửa)");
        rows[0].Title.Should().Contain("GẤP", "tiêu đề chuông báo phiếu được đánh dấu GẤP");
        rows[0].Title.Should().Contain("PE-GAP-01", "tiêu đề kèm mã phiếu");
        rows[0].Href.Should().Be($"/purchase-evaluations/{pe.Id}", "link điều hướng tới phiếu");
        rows[0].Type.Should().Be(NotificationType.Generic);
        // Cờ ĐỎ cũng đã được bật (toggle thành công + chuông cùng lưu).
        var reloadPe = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloadPe.IsUrgentByPro.Should().BeTrue();
    }

    // =====================================================================
    // 2. false→true bởi CCM → cũng persist chuông Director (cùng nhánh notify).
    // =====================================================================
    [Fact]
    public async Task SetUrgent_FalseToTrue_ByCostControl_PersistsDirectorNotification()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();

        var director = await fix.CreateUserAsync(
            "ceo2@test.local", "Giám đốc 2", departmentId: null, roles: new[] { AppRoles.Director });
        var pe = await SeedPeAsync(db, code: "PE-URGN-002", maPhieu: "PE-GAP-02");

        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.CostControl));
        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        var rows = await db.Notifications.AsNoTracking()
            .Where(n => n.UserId == director.Id && n.RefId == pe.Id).ToListAsync();
        rows.Should().HaveCount(1, "CCM bật gấp cũng notify Director (cờ XANH false→true)");
        rows[0].Title.Should().Contain("GẤP");
    }

    // =====================================================================
    // 3. Admin false→true → notify Director. Đồng thời chỉ Director nhận (non-Director KHÔNG).
    // =====================================================================
    [Fact]
    public async Task SetUrgent_FalseToTrue_ByAdmin_NotifiesOnlyDirectors()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();

        var director = await fix.CreateUserAsync(
            "ceo3@test.local", "Giám đốc 3", departmentId: null, roles: new[] { AppRoles.Director });
        // Người KHÔNG phải Director — KHÔNG được nhận chuông (target chỉ role Director).
        var other = await fix.CreateUserAsync(
            "staff@test.local", "Nhân viên", departmentId: null, roles: new[] { AppRoles.Finance });
        var pe = await SeedPeAsync(db, code: "PE-URGN-003", maPhieu: "PE-GAP-03");

        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Admin));
        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        var all = await db.Notifications.AsNoTracking().Where(n => n.RefId == pe.Id).ToListAsync();
        all.Should().ContainSingle("chỉ 1 Director được seed → đúng 1 chuông");
        all[0].UserId.Should().Be(director.Id);
        all.Should().NotContain(n => n.UserId == other.Id, "người KHÔNG phải Director không nhận chuông gấp");
    }

    // =====================================================================
    // 4. true→true (đã gấp sẵn, bật lại) → KHÔNG thêm chuông (guard !wasUrgent chống spam).
    //    Pre-seed cờ XANH true, PRO bật cờ ĐỎ (nowUrgent vẫn true nhưng wasUrgent đã true).
    // =====================================================================
    [Fact]
    public async Task SetUrgent_TrueToTrue_DoesNotAddDuplicateNotification()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();

        await fix.CreateUserAsync(
            "ceo4@test.local", "Giám đốc 4", departmentId: null, roles: new[] { AppRoles.Director });
        // Phiếu ĐÃ gấp (cờ XANH bật) → wasUrgent = true. PRO bật cờ ĐỎ (vẫn IsUrgent=true).
        var pe = await SeedPeAsync(db, urgentByCcm: true, code: "PE-URGN-004", maPhieu: "PE-GAP-04");

        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Procurement));
        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        var rows = await db.Notifications.AsNoTracking().Where(n => n.RefId == pe.Id).ToListAsync();
        rows.Should().BeEmpty("đã gấp từ trước (wasUrgent=true) → guard !wasUrgent chặn → KHÔNG chuông trùng");
        // Nhưng cờ ĐỎ vẫn được set (toggle vẫn chạy, chỉ bỏ qua notify).
        var reloadPe = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloadPe.IsUrgentByPro.Should().BeTrue("toggle vẫn set cờ ĐỎ; chỉ notify bị skip");
        reloadPe.IsUrgentByCcm.Should().BeTrue("cờ XANH giữ nguyên");
    }

    // =====================================================================
    // 5. true→false (GỠ cờ) → KHÔNG notify (request.IsUrgent=false → gate false ngay).
    //    Dùng Admin để qua authz UNSET (Admin gỡ cả 2). Pre-seed cả 2 cờ true.
    // =====================================================================
    [Fact]
    public async Task SetUrgent_TrueToFalse_DoesNotNotify()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();

        await fix.CreateUserAsync(
            "ceo5@test.local", "Giám đốc 5", departmentId: null, roles: new[] { AppRoles.Director });
        var pe = await SeedPeAsync(db, urgentByPro: true, urgentByCcm: true,
            code: "PE-URGN-005", maPhieu: "PE-GAP-05");

        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Admin));
        await handler.Handle(new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: false), CancellationToken.None);

        var rows = await db.Notifications.AsNoTracking().Where(n => n.RefId == pe.Id).ToListAsync();
        rows.Should().BeEmpty("GỠ cờ (IsUrgent=false) → KHÔNG báo Giám đốc");
        var reloadPe = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloadPe.IsUrgentByPro.Should().BeFalse("Admin gỡ cả ĐỎ");
        reloadPe.IsUrgentByCcm.Should().BeFalse("Admin gỡ cả XANH");
    }

    // =====================================================================
    // 6. false→true nhưng KHÔNG có Director nào → handler không lỗi, không chuông
    //    (directorIds.Count == 0 → block notify bỏ qua). Cờ vẫn được bật (toggle thành công).
    // =====================================================================
    [Fact]
    public async Task SetUrgent_FalseToTrue_NoDirectorSeeded_TogglesWithoutNotificationOrError()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var um = fix.Services.GetRequiredService<UserManager<User>>();

        // KHÔNG seed Director.
        var pe = await SeedPeAsync(db, code: "PE-URGN-006", maPhieu: "PE-GAP-06");

        var handler = BuildHandler(db, um, new FakeCurrentUser(AppRoles.Procurement));
        var act = async () => await handler.Handle(
            new SetPurchaseEvaluationUrgentCommand(pe.Id, IsUrgent: true), CancellationToken.None);

        await act.Should().NotThrowAsync("không có Director → bỏ qua notify, KHÔNG fail toggle");

        var rows = await db.Notifications.AsNoTracking().Where(n => n.RefId == pe.Id).ToListAsync();
        rows.Should().BeEmpty("0 Director → 0 chuông");
        var reloadPe = await db.PurchaseEvaluations.AsNoTracking().FirstAsync(x => x.Id == pe.Id);
        reloadPe.IsUrgentByPro.Should().BeTrue("cờ ĐỎ vẫn được bật (toggle độc lập với notify)");
    }
}
