using System.Reflection;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using SolutionErp.Domain.Identity;
using SolutionErp.Infrastructure.Persistence;
using SolutionErp.Infrastructure.Tests.Common;

namespace SolutionErp.Infrastructure.Tests.Application;

// [S92 2026-06-29] Call grant này (SeedAllRolesOfficeModulePermissionsAsync, S69) ĐÃ TẮT khỏi
// SeedAsync — anh chốt nhóm "Văn phòng số" CHỈ Admin thấy. Test giữ guard method-CÔ-LẬP cho
// reversibility (method vẫn private static); hành vi prod THẬT = AdminOnlyModulesRevokeTests
// (revoke là bước cuối). Assertion dưới KHÔNG đổi — vẫn pass vì invoke trực tiếp method cô lập.
// S69 test-after (UAT mode — GOLIVE "public Văn phòng số cho all user eoffice" shipped S69).
// Mirror HrmProfilePermissionSeedTests (S65/S66) — same reflection harness, but Office grant
// mở READ + CREATE (self-service tạo phiếu) trên allow-list 16 key, KHÁC HRM chỉ READ 2 key.
//
// DbInitializer permission-seed chain (SeedAsync order, line 2042-2055):
//   1. RevokeTemporarilyHiddenModulesAsync (S58, :2166) → set MỌI Hrm*/Off*/Personal = false
//      cho non-Admin (StartsWith("Off") bắt cả Off, Off_Dashboard, Off_PhongHop_Manage, …).
//   2. SeedAllRolesOfficeModulePermissionsAsync (S69, :2277) → CHẠY SAU để THẮNG revoke,
//      nâng RIÊNG allow-list 16 key = CanRead+CanCreate=true (upgrade-only). Excluded 3 key
//      (Off_PhongHop_Manage / Off_AttendanceReport / Off_ChamCong) KHÔNG nâng → revoke vẫn che.
//
// Kết quả mong đợi (anh chốt public Văn phòng số self-service, ẩn admin-manage + report + chấm công):
//   - non-Admin role: 16 allow-list key → CanRead=true AND CanCreate=true (mở menu + nút tạo).
//   - non-Admin role: 3 excluded key → CanRead=false (revoke che, seed không nâng) ← LOAD-BEARING.
//   - non-Admin role: CanUpdate/CanDelete trên allow-list VẪN false (grant chỉ mở read+create).
//   - non-Admin role: HRM (trừ Hồ sơ NS S65) + Personal VẪN ẩn (Office grant không đụng).
//   - Admin role: KHÔNG bị revoke (giữ nguyên — quản trị/CRUD phòng/duyệt phiếu).
//
// 2 method `private static` trong DbInitializer → invoke qua REFLECTION (BindingFlags.NonPublic
// |Static). Test ĐÚNG behavior code thật, KHÔNG re-implement. Signature cả 2:
//   (ApplicationDbContext db, RoleManager<Role> roleManager, ILogger logger).
//
// FK lưu ý (PermissionConfiguration): Permission.MenuKey → MenuItem.Key (Cascade) +
// Permission.RoleId → Role (Cascade). → PHẢI seed MenuItem rows + Role TRƯỚC khi seed
// Permission rows (nếu không SQLite FK Error 19).
public class OfficeModulePermissionSeedTests
{
    // Allow-list 16 key — sau chain PHẢI có CanRead=true AND CanCreate=true (non-Admin).
    private static readonly string[] AllowListKeys =
    {
        MenuKeys.Off, MenuKeys.OffDashboard, MenuKeys.OffDanhBa,
        MenuKeys.OffPhongHop, MenuKeys.OffPhongHopView, MenuKeys.OffPhongHopBook,
        MenuKeys.OffDeXuat, MenuKeys.OffDeXuatList, MenuKeys.OffDeXuatCreate, MenuKeys.OffDeXuatInbox,
        MenuKeys.OffDonTu, MenuKeys.OffDonTuLeave, MenuKeys.OffDonTuOt, MenuKeys.OffDonTuTravel,
        MenuKeys.OffDatXe, MenuKeys.OffItTicket,
    };

    // Excluded 3 key — sau chain PHẢI GIỮ CanRead=false (revoke che, office-grant KHÔNG đụng).
    // Đây là security invariant trọng tâm: admin-manage / báo cáo / chấm công KHÔNG public.
    private static readonly string[] ExcludedOfficeKeys =
    {
        MenuKeys.OffPhongHopManage,    // "Off_PhongHop_Manage" — Admin CRUD phòng họp
        MenuKeys.OffAttendanceReport,  // "Off_AttendanceReport" — báo cáo chấm công (admin)
        MenuKeys.OffChamCong,          // "Off_ChamCong" — chấm công GPS (re-parent Cá nhân, golive riêng)
    };

    // HRM (non-profile) + Personal — phải GIỮ ẩn sau Office grant (cross-module leak check).
    // Hrm_HoSo cố ý KHÔNG đưa vào đây (S65 đã public riêng — Office chain không touch nó).
    private static readonly string[] OtherModuleHiddenKeys =
    {
        MenuKeys.HrmDashboard,
        MenuKeys.Personal,
    };

    // Tất cả MenuItem key cần seed (FK target cho Permission.MenuKey).
    private static readonly string[] AllMenuKeys =
        AllowListKeys
            .Concat(ExcludedOfficeKeys)
            .Concat(OtherModuleHiddenKeys)
            .ToArray();

    private static async Task InvokeRevokeAsync(IdentityFixture fix)
        => await InvokePrivateSeedAsync(fix, "RevokeTemporarilyHiddenModulesAsync");

    private static async Task InvokeOfficeSeedAsync(IdentityFixture fix)
        => await InvokePrivateSeedAsync(fix, "SeedAllRolesOfficeModulePermissionsAsync");

    // Reflection invoke private static (ApplicationDbContext, RoleManager<Role>, ILogger).
    private static async Task InvokePrivateSeedAsync(IdentityFixture fix, string methodName)
    {
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var rm = fix.Services.GetRequiredService<RoleManager<Role>>();
        var mi = typeof(DbInitializer).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
        mi.Should().NotBeNull($"DbInitializer.{methodName} phải tồn tại (private static) — đổi signature thì cập nhật test");
        var task = (Task)mi!.Invoke(null, new object[] { db, rm, NullLogger.Instance })!;
        await task;
    }

    // Ensure roles tồn tại (Admin + non-Admin). Trả (adminId, nonAdminId).
    private static async Task<(Guid adminId, Guid nonAdminId)> SeedRolesAsync(IdentityFixture fix)
    {
        var rm = fix.Services.GetRequiredService<RoleManager<Role>>();
        var admin = new Role { Id = Guid.NewGuid(), Name = AppRoles.Admin };
        var drafter = new Role { Id = Guid.NewGuid(), Name = AppRoles.Drafter };
        await rm.CreateAsync(admin);
        await rm.CreateAsync(drafter);
        return (admin.Id, drafter.Id);
    }

    // Seed MenuItem rows (FK target cho Permission.MenuKey).
    private static async Task SeedMenuItemsAsync(TestApplicationDbContext db)
    {
        foreach (var key in AllMenuKeys)
            db.MenuItems.Add(new MenuItem { Key = key, Label = key });
        await db.SaveChangesAsync(CancellationToken.None);
    }

    // Seed 1 Permission row (read-only mặc định — mô phỏng seed cũ trước revoke).
    private static void AddPerm(TestApplicationDbContext db, Guid roleId, string menuKey, bool canRead)
        => db.Permissions.Add(new Permission
        {
            RoleId = roleId,
            MenuKey = menuKey,
            CanRead = canRead,
            CanCreate = false,
            CanUpdate = false,
            CanDelete = false,
        });

    private static async Task<Permission?> GetPermAsync(TestApplicationDbContext db, Guid roleId, string menuKey)
        => await db.Permissions.AsNoTracking()
            .FirstOrDefaultAsync(p => p.RoleId == roleId && p.MenuKey == menuKey);

    private static async Task<bool> CanReadAsync(TestApplicationDbContext db, Guid roleId, string menuKey)
        => (await GetPermAsync(db, roleId, menuKey))?.CanRead ?? false;

    // ============================================================
    // Full chain: pre-grant Off* → Revoke → OfficeSeed (đúng SeedAsync order)
    // ============================================================

    [Fact]
    public async Task Chain_NonAdmin_AllowList16_ReadAndCreateTrue_Excluded3_StayHidden()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var (adminId, nonAdminId) = await SeedRolesAsync(fix);
        await SeedMenuItemsAsync(db);

        // PRE-STATE: non-Admin từng được grant CanRead=true trên MỌI Off* (mô phỏng seed cũ)
        // → để revoke có gì đó để thu hồi (gồm cả 3 excluded). Admin cũng có (KHÔNG bị revoke).
        foreach (var key in AllowListKeys.Concat(ExcludedOfficeKeys))
        {
            AddPerm(db, nonAdminId, key, canRead: true);
            AddPerm(db, adminId, key, canRead: true);
        }
        await db.SaveChangesAsync(CancellationToken.None);

        // CHAIN đúng thứ tự SeedAsync: revoke (Off* → false) → office-grant (allow-list → read+create).
        await InvokeRevokeAsync(fix);
        await InvokeOfficeSeedAsync(fix);

        // non-Admin: allow-list 16 key mở READ + CREATE (self-service Văn phòng số).
        foreach (var key in AllowListKeys)
        {
            var row = await GetPermAsync(db, nonAdminId, key);
            row.Should().NotBeNull($"{key} phải có row sau office-grant");
            row!.CanRead.Should().BeTrue($"{key} mở Read cho user thường (golive Văn phòng số)");
            row.CanCreate.Should().BeTrue($"{key} mở Create cho user thường (self-service tạo phiếu)");
        }

        // ⭐ LOAD-BEARING: 3 excluded key VẪN ẩn (revoke che, office-grant KHÔNG nâng).
        foreach (var key in ExcludedOfficeKeys)
            (await CanReadAsync(db, nonAdminId, key)).Should()
                .BeFalse($"{key} KHÔNG public — chỉ Admin (admin-manage / báo cáo / chấm công)");
    }

    [Fact]
    public async Task Chain_NonAdmin_AllowList_UpdateAndDelete_StayFalse()
    {
        // Grant chỉ mở read+create — Update/Delete (sửa/xóa phòng, duyệt cấp cao) GIỮ false.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var (_, nonAdminId) = await SeedRolesAsync(fix);
        await SeedMenuItemsAsync(db);

        foreach (var key in AllowListKeys)
            AddPerm(db, nonAdminId, key, canRead: true);
        await db.SaveChangesAsync(CancellationToken.None);

        await InvokeRevokeAsync(fix);
        await InvokeOfficeSeedAsync(fix);

        foreach (var key in AllowListKeys)
        {
            var row = await GetPermAsync(db, nonAdminId, key);
            row!.CanUpdate.Should().BeFalse($"{key} KHÔNG mở Update cho non-Admin (write quản lý khóa ở controller)");
            row.CanDelete.Should().BeFalse($"{key} KHÔNG mở Delete cho non-Admin");
        }
    }

    [Fact]
    public async Task Chain_NonAdmin_OfficeGrant_DoesNotLeakIntoHrmOrPersonal()
    {
        // Office grant CHỈ đụng allow-list Off* — KHÔNG vô tình mở HRM (Dashboard) hay Personal.
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var (_, nonAdminId) = await SeedRolesAsync(fix);
        await SeedMenuItemsAsync(db);

        // Pre-grant cả HRM-dashboard + Personal (để revoke thu hồi) — office-grant không được nâng lại.
        foreach (var key in AllowListKeys.Concat(OtherModuleHiddenKeys))
            AddPerm(db, nonAdminId, key, canRead: true);
        await db.SaveChangesAsync(CancellationToken.None);

        await InvokeRevokeAsync(fix);
        await InvokeOfficeSeedAsync(fix);

        foreach (var key in OtherModuleHiddenKeys)
            (await CanReadAsync(db, nonAdminId, key)).Should()
                .BeFalse($"{key} GIỮ ẩn — Office grant KHÔNG mở HRM/Personal");
    }

    [Fact]
    public async Task Chain_Admin_NotRevoked_KeepsAllOfficeReadAfterChain()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var (adminId, nonAdminId) = await SeedRolesAsync(fix);
        await SeedMenuItemsAsync(db);

        // Admin có read trên cả allow-list + 3 excluded → KHÔNG bị revoke đụng.
        foreach (var key in AllowListKeys.Concat(ExcludedOfficeKeys))
        {
            AddPerm(db, adminId, key, canRead: true);
            AddPerm(db, nonAdminId, key, canRead: true);
        }
        await db.SaveChangesAsync(CancellationToken.None);

        await InvokeRevokeAsync(fix);
        await InvokeOfficeSeedAsync(fix);

        // Admin GIỮ nguyên CanRead=true trên mọi Off* (kể cả 3 excluded) — revoke loại trừ Admin.
        foreach (var key in AllowListKeys.Concat(ExcludedOfficeKeys))
            (await CanReadAsync(db, adminId, key)).Should().BeTrue($"Admin KHÔNG bị revoke trên {key}");
    }

    // ============================================================
    // SeedAllRolesOfficeModulePermissionsAsync — isolated behavior
    // ============================================================

    [Fact]
    public async Task OfficeSeed_CreatesMissingRow_ReadCreateTrue_UpdateDeleteFalse()
    {
        // Row chưa có (DB mới) → tạo CanRead=true + CanCreate=true, Update/Delete=false (line 2312).
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var (_, nonAdminId) = await SeedRolesAsync(fix);
        await SeedMenuItemsAsync(db);
        // KHÔNG seed Permission row nào.

        await InvokeOfficeSeedAsync(fix);

        var off = await GetPermAsync(db, nonAdminId, MenuKeys.Off);
        off.Should().NotBeNull("office-seed tạo row mới khi chưa tồn tại");
        off!.CanRead.Should().BeTrue();
        off.CanCreate.Should().BeTrue();
        off.CanUpdate.Should().BeFalse();
        off.CanDelete.Should().BeFalse();

        // Excluded key KHÔNG được office-seed tạo row (chỉ allow-list).
        var manage = await GetPermAsync(db, nonAdminId, MenuKeys.OffPhongHopManage);
        manage.Should().BeNull("office-seed KHÔNG tạo row cho Off_PhongHop_Manage (ngoài allow-list)");
    }

    [Fact]
    public async Task OfficeSeed_UpgradesExistingFalseRow_PreservesAdminRaisedUpdateDelete()
    {
        // Upgrade-only path (line 2302-2308): row CanRead=false + CanCreate=false (revoke vừa set)
        // → nâng read+create=true. NHƯNG nếu admin đã chỉnh Update/Delete=true → KHÔNG hạ
        // (office-grant chỉ đụng Read/Create). Lock invariant "upgrade-only, không phá quyền admin".
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var (_, nonAdminId) = await SeedRolesAsync(fix);
        await SeedMenuItemsAsync(db);

        // Row tiền tồn: Read/Create=false (như sau revoke) nhưng Update/Delete=true (admin nâng tay).
        db.Permissions.Add(new Permission
        {
            RoleId = nonAdminId,
            MenuKey = MenuKeys.OffDeXuatCreate,
            CanRead = false,
            CanCreate = false,
            CanUpdate = true,
            CanDelete = true,
        });
        await db.SaveChangesAsync(CancellationToken.None);

        await InvokeOfficeSeedAsync(fix);

        var row = await GetPermAsync(db, nonAdminId, MenuKeys.OffDeXuatCreate);
        row!.CanRead.Should().BeTrue("upgrade nâng Read=true");
        row.CanCreate.Should().BeTrue("upgrade nâng Create=true");
        row.CanUpdate.Should().BeTrue("office-grant KHÔNG hạ Update đã được admin nâng");
        row.CanDelete.Should().BeTrue("office-grant KHÔNG hạ Delete đã được admin nâng");
    }
}
