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 test-before-merge (SECURITY — anh chốt 5 nhóm menu CHỈ Admin thấy trên eoffice).
// Guard hành vi RevokeTemporarilyHiddenModulesAsync sau khi S92 MỞ RỘNG predicate phủ thêm
// HỢP ĐỒNG (Contracts + Ct_*) + DANH MỤC (Master + Suppliers + Projects + Departments + Catalog*),
// cộng dồn nhóm cũ S58 NHÂN SỰ (Hrm*) + VĂN PHÒNG SỐ (Off*) + CÁ NHÂN (Personal).
//
// Cơ chế ẩn: menu hiện ⟺ Permission.CanRead=true (GetMyMenuTree lọc CanRead, KHÔNG admin-bypass).
// Revoke set 4 cờ CRUD=false cho MỌI role non-Admin trên các key match predicate → menu tự ẩn.
// Admin loại trừ khỏi revoke (giữ full → vẫn thấy hết). Pe_* (Duyệt NCC) KHÔNG match predicate
// → module PE giữ nguyên cho user (invariant bảo vệ tính năng đang dùng).
//
// S92 CŨNG TẮT 2 call re-grant trong SeedAsync: SeedAllRolesHrmProfileReadPermissionsAsync (S65)
// + SeedAllRolesOfficeModulePermissionsAsync (S69) → revoke là bước permission CUỐI, thắng mọi grant.
// 2 method grant đó vẫn `private static` (reversibility) → OfficeModulePermissionSeedTests +
// HrmProfilePermissionSeedTests vẫn invoke-reflection method-cô-lập (assertion KHÔNG đổi, vẫn pass).
//
// Reflection harness: RevokeTemporarilyHiddenModulesAsync là `private static` →
// BindingFlags.NonPublic|Static, signature (ApplicationDbContext, RoleManager<Role>, ILogger).
// Test ĐÚNG behavior code thật, KHÔNG re-implement predicate.
//
// FK (PermissionConfiguration): Permission.MenuKey → MenuItem.Key (Cascade) + RoleId → Role
// (Cascade) → PHẢI seed MenuItem rows + Role TRƯỚC khi seed Permission (nếu không SQLite FK Error 19).
public class AdminOnlyModulesRevokeTests
{
    // [S159 2026-07-29] Owner SUPERSEDE S92: "đang phát triển → cho hiển thị hết để mọi
    // người góp ý; Duyệt NCC để như cũ" ⇒ nhánh Contracts/Ct_*/Master-set GỠ khỏi revoker.
    // Tập ẩn CÒN LẠI = 3 nhóm đang-làm (Hrm/Off/Personal — ý ❸ owner cùng ngày).
    private static readonly string[] StillHiddenKeys =
    {
        MenuKeys.Hrm,                // "Hrm"  — match StartsWith("Hrm")
        MenuKeys.Off,                // "Off"  — match StartsWith("Off")
        MenuKeys.Personal,           // "Personal" — match == Personal
    };

    // [S159] Bộ key S92 cũ nay MỞ LẠI — revoker KHÔNG ĐƯỢC chạm nữa. Giữ danh sách này
    // làm REGRESSION-GUARD: bài trả giá 2026-07-29 = grant 38 key bằng SQL tay bị chính
    // revoker lật ở app-restart kế (Run #423, 447/494 row rơi, 11/13 role về 0 —
    // cicd-verify-1a47a61.md mục 9). Ai re-add nhánh S92 vào predicate ⇒ test này ĐỎ.
    private static readonly string[] ReopenedKeys =
    {
        // HỢP ĐỒNG (Contracts + Ct_*)
        MenuKeys.Contracts,
        MenuKeys.ContractTypeGroup("ThauPhu"),     // "Ct_ThauPhu"
        // DANH MỤC (Master + 3 leaf + Catalog*)
        MenuKeys.Master,
        MenuKeys.Suppliers,
        MenuKeys.Projects,
        MenuKeys.Departments,
        MenuKeys.Catalogs,
        MenuKeys.CatalogWorkItems,
    };

    private static readonly string[] S92HiddenKeys =
        StillHiddenKeys.Concat(ReopenedKeys).ToArray(); // giữ tên cho seed-helper cũ

    // Pe_* (Duyệt NCC) + root PurchaseEvaluations — KHÔNG bị revoke (giữ cho user thường).
    private static readonly string[] PeKeptKeys =
    {
        MenuKeys.PurchaseEvaluations,                       // "PurchaseEvaluations" root — không match predicate
        MenuKeys.PurchaseEvaluationGroup("DuyetNcc"),       // "Pe_DuyetNcc" — không StartsWith Ct_/Catalog/Hrm/Off
    };

    private static readonly string[] AllMenuKeys =
        S92HiddenKeys.Concat(PeKeptKeys).Distinct().ToArray();

    // Reflection invoke private static (ApplicationDbContext, RoleManager<Role>, ILogger).
    private static async Task InvokeRevokeAsync(IdentityFixture fix)
    {
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var rm = fix.Services.GetRequiredService<RoleManager<Role>>();
        var mi = typeof(DbInitializer).GetMethod(
            "RevokeTemporarilyHiddenModulesAsync", BindingFlags.NonPublic | BindingFlags.Static);
        mi.Should().NotBeNull(
            "DbInitializer.RevokeTemporarilyHiddenModulesAsync 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 (CanRead mặc định + optional CanCreate — mô phỏng grant trước revoke).
    private static void AddPerm(
        TestApplicationDbContext db, Guid roleId, string menuKey, bool canRead, bool canCreate = false)
        => db.Permissions.Add(new Permission
        {
            RoleId = roleId,
            MenuKey = menuKey,
            CanRead = canRead,
            CanCreate = canCreate,
            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;

    // ⭐ LOAD-BEARING: non-Admin có read trên 5 nhóm → sau revoke TẤT CẢ 4 cờ CRUD=false (menu ẩn).
    [Fact]
    public async Task Revoke_NonAdmin_S92Modules_CanReadFalse()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var (_, nonAdminId) = await SeedRolesAsync(fix);
        await SeedMenuItemsAsync(db);

        // Pre-grant CanRead=true cho non-Admin (Drafter) trên đại diện 5 nhóm → revoke có gì thu hồi.
        foreach (var key in S92HiddenKeys)
            AddPerm(db, nonAdminId, key, canRead: true);
        await db.SaveChangesAsync(CancellationToken.None);

        await InvokeRevokeAsync(fix);

        // Vế 1 — 3 nhóm đang-làm VẪN bị revoke (ý ❸ giữ).
        foreach (var key in StillHiddenKeys)
        {
            var row = await GetPermAsync(db, nonAdminId, key);
            row.Should().NotBeNull($"{key} vẫn có row (revoke chỉ hạ cờ, KHÔNG xóa — giữ vết flip lại)");
            row!.CanRead.Should().BeFalse($"{key} bị revoke CanRead — nhóm đang-làm ẩn khỏi non-Admin");
            row.CanCreate.Should().BeFalse($"{key} revoke CanCreate");
            row.CanUpdate.Should().BeFalse($"{key} revoke CanUpdate");
            row.CanDelete.Should().BeFalse($"{key} revoke CanDelete");
        }

        // Vế 2 — ⭐ REGRESSION-GUARD S159: bộ key MỞ LẠI phải GIỮ NGUYÊN CanRead=true
        // sau revoke (revoker không còn nhánh S92; re-add là test này đỏ ngay).
        foreach (var key in ReopenedKeys)
            (await CanReadAsync(db, nonAdminId, key)).Should().BeTrue(
                $"{key} — owner S159 mở lại cho mọi role; revoker KHÔNG được lật grant này nữa");
    }

    // Admin loại trừ khỏi revoke → giữ nguyên CanRead=true (vẫn thấy 5 nhóm).
    [Fact]
    public async Task Revoke_Admin_Untouched_KeepsRead()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var (adminId, nonAdminId) = await SeedRolesAsync(fix);
        await SeedMenuItemsAsync(db);

        // Cả Admin + non-Admin có read trên 5 nhóm — chỉ non-Admin bị revoke.
        foreach (var key in S92HiddenKeys)
        {
            AddPerm(db, adminId, key, canRead: true);
            AddPerm(db, nonAdminId, key, canRead: true);
        }
        await db.SaveChangesAsync(CancellationToken.None);

        await InvokeRevokeAsync(fix);

        foreach (var key in S92HiddenKeys)
            (await CanReadAsync(db, adminId, key)).Should()
                .BeTrue($"Admin KHÔNG bị revoke trên {key} — quản trị vẫn thấy đủ");
    }

    // ⭐ Invariant bảo vệ tính năng PE: Pe_* + root PurchaseEvaluations KHÔNG bị revoke (giữ cho user).
    [Fact]
    public async Task Revoke_DoesNotTouch_PeModule()
    {
        using var fix = new IdentityFixture();
        var db = fix.Services.GetRequiredService<TestApplicationDbContext>();
        var (_, nonAdminId) = await SeedRolesAsync(fix);
        await SeedMenuItemsAsync(db);

        // non-Admin có read (+create cho leaf Pe_*) — module Duyệt NCC đang dùng, KHÔNG được revoke.
        foreach (var key in PeKeptKeys)
            AddPerm(db, nonAdminId, key, canRead: true, canCreate: true);
        await db.SaveChangesAsync(CancellationToken.None);

        await InvokeRevokeAsync(fix);

        foreach (var key in PeKeptKeys)
        {
            var row = await GetPermAsync(db, nonAdminId, key);
            row!.CanRead.Should().BeTrue($"{key} KHÔNG bị revoke — module Duyệt NCC giữ cho user thường");
            row.CanCreate.Should().BeTrue($"{key} giữ CanCreate — user vẫn tạo phiếu Duyệt NCC");
        }
    }
}
