import { act, renderHook, waitFor } from "@testing-library/react";
import { afterEach, beforeEach, expect, test, vi } from "vitest";

const mocks = vi.hoisted(() => ({
    abilities: { canCreate: true, canManagePermissions: true, canResetPassword: true, canStatus: true, canView: true },
    appCode: "fami",
    catalogError: "",
    catalogLoading: false,
    catalogPage: null,
    catalogQueryOptions: null,
    confirm: vi.fn(),
    createExternalAdminUser: vi.fn(),
    getExternalAdminUserPermissions: vi.fn(),
    invalidateQueries: vi.fn(),
    listExternalAdminUsers: vi.fn(),
    listExternalAdminPermissionCatalog: vi.fn(),
    lookupExternalAdminUserTarget: vi.fn(),
    queryOptions: null,
    queryPage: { items: [], page: 1, pageSize: 50, total: 0 },
    reload: vi.fn(),
    resetExternalAdminUserPassword: vi.fn(),
    showToast: vi.fn(),
    updateExternalAdminUserStatus: vi.fn(),
    updateExternalAdminUserPermissions: vi.fn(),
}));

vi.mock("@tanstack/react-query", () => ({
    useQueryClient: () => ({ invalidateQueries: mocks.invalidateQueries }),
}));

vi.mock("@/app/app-scope/AppScopeProvider.jsx", () => ({
    useAppScope: () => ({ appCode: mocks.appCode }),
}));

vi.mock("@/features/external-admin-users/api", () => ({
    createExternalAdminUser: mocks.createExternalAdminUser,
    getExternalAdminUserPermissions: mocks.getExternalAdminUserPermissions,
    listExternalAdminUsers: mocks.listExternalAdminUsers,
    listExternalAdminPermissionCatalog: mocks.listExternalAdminPermissionCatalog,
    lookupExternalAdminUserTarget: mocks.lookupExternalAdminUserTarget,
    resetExternalAdminUserPassword: mocks.resetExternalAdminUserPassword,
    updateExternalAdminUserStatus: mocks.updateExternalAdminUserStatus,
    updateExternalAdminUserPermissions: mocks.updateExternalAdminUserPermissions,
}));

vi.mock("@/features/external-admin-users/permissions.js", () => ({
    useExternalAdminUserAbilities: () => mocks.abilities,
}));

vi.mock("@/shared/hooks/useAdminQuery.js", () => ({
    useAdminQuery: (_queryFn, options) => {
        mocks.queryOptions = options;
        if (options.queryKey[0] === "external-admin-permission-catalog") {
            mocks.catalogQueryOptions = options;
            if (options.enabled) {
                void _queryFn();
            }
            return {
                data: mocks.catalogPage,
                error: mocks.catalogError,
                loading: mocks.catalogLoading,
                reload: mocks.reload,
            };
        }
        return { data: mocks.queryPage, error: "", loading: false, reload: mocks.reload };
    },
}));

vi.mock("@/shared/ui/ConfirmProvider.jsx", () => ({
    useConfirm: () => mocks.confirm,
}));

vi.mock("@/shared/ui/ToastProvider.jsx", () => ({
    useToast: () => ({ showToast: mocks.showToast }),
}));

import { useExternalAdminUsersPage } from "./useExternalAdminUsersPage.js";

beforeEach(() => {
    mocks.abilities = { canCreate: true, canManagePermissions: true, canResetPassword: true, canStatus: true, canView: true };
    mocks.appCode = "fami";
    mocks.catalogError = "";
    mocks.catalogLoading = false;
    mocks.catalogPage = permissionCatalogFixture();
    mocks.queryPage = { items: [], page: 1, pageSize: 50, total: 0 };
    mocks.confirm.mockResolvedValue(true);
    mocks.createExternalAdminUser.mockResolvedValue(externalUserFixture());
    mocks.listExternalAdminPermissionCatalog.mockResolvedValue(permissionCatalogFixture());
    mocks.getExternalAdminUserPermissions.mockResolvedValue({
        accountId: "71",
        permissions: ["user:list"],
        revision: 4,
    });
    mocks.invalidateQueries.mockResolvedValue(undefined);
    mocks.lookupExternalAdminUserTarget.mockResolvedValue(targetFixture());
    mocks.reload.mockResolvedValue(undefined);
    mocks.resetExternalAdminUserPassword.mockResolvedValue(externalUserFixture());
    mocks.updateExternalAdminUserStatus.mockResolvedValue({ ...externalUserFixture(), status: "disabled" });
    mocks.updateExternalAdminUserPermissions.mockResolvedValue(externalUserFixture());
});

afterEach(() => {
    vi.clearAllMocks();
});

test("creates an App-scoped account with the canonical userId returned by lookup", async () => {
    const { result } = renderHook(() => useExternalAdminUsersPage());

    act(() => result.current.openCreate());
    act(() => result.current.setDisplayUserId("VIP-1001"));
    await act(async () => result.current.lookupTarget());
    act(() =>
        result.current.setCreateForm({
            confirmPassword: "StrongPass123!",
            password: "StrongPass123!",
            username: "ops.fami",
        }),
    );
    await act(async () => result.current.submitCreate({ preventDefault: vi.fn() }));

    expect(mocks.lookupExternalAdminUserTarget).toHaveBeenCalledWith("fami", "VIP-1001");
    expect(mocks.createExternalAdminUser).toHaveBeenCalledWith("fami", {
        password: "StrongPass123!",
        permissions: ["user:list", "user:update"],
        targetUserId: "internal-user-1001",
        username: "ops.fami",
    });
    expect(mocks.invalidateQueries).toHaveBeenCalledWith({ queryKey: ["external-admin-users", "fami"] });
    expect(result.current.createOpen).toBe(false);
});

test("creates an explicit zero-permission account after the operator clears defaults", async () => {
    const { result } = renderHook(() => useExternalAdminUsersPage());

    act(() => result.current.openCreate());
    act(() => result.current.setCreateForm({ permissions: [] }));
    act(() => result.current.setDisplayUserId("VIP-1001"));
    await act(async () => result.current.lookupTarget());
    act(() =>
        result.current.setCreateForm({
            confirmPassword: "StrongPass123!",
            password: "StrongPass123!",
            username: "ops.zero",
        }),
    );
    await act(async () => result.current.submitCreate({ preventDefault: vi.fn() }));

    expect(mocks.createExternalAdminUser).toHaveBeenCalledWith("fami", {
        password: "StrongPass123!",
        permissions: [],
        targetUserId: "internal-user-1001",
        username: "ops.zero",
    });
});

test("does not request the permission catalog without the configure-permissions grant", () => {
    mocks.abilities = { ...mocks.abilities, canCreate: false, canManagePermissions: false };

    renderHook(() => useExternalAdminUsersPage());

    expect(mocks.catalogQueryOptions.enabled).toBe(false);
    expect(mocks.listExternalAdminPermissionCatalog).not.toHaveBeenCalled();
});

test("treats an empty successful catalog as unavailable and blocks create and update writes", async () => {
    mocks.catalogPage = { items: [], total: 0 };
    const item = externalUserFixture();
    const { result } = renderHook(() => useExternalAdminUsersPage());

    expect(result.current.catalogError).toBe("外管权限目录为空，请联系管理员");
    act(() => result.current.openCreate());
    act(() => result.current.setDisplayUserId("VIP-1001"));
    await act(async () => result.current.lookupTarget());
    act(() =>
        result.current.setCreateForm({
            confirmPassword: "StrongPass123!",
            password: "StrongPass123!",
            username: "ops.empty",
        }),
    );
    await act(async () => result.current.submitCreate({ preventDefault: vi.fn() }));
    expect(mocks.createExternalAdminUser).not.toHaveBeenCalled();

    act(() => result.current.closeCreate());
    act(() => result.current.openPermissions(item));
    await waitFor(() => expect(result.current.permissionLoading).toBe(false));
    await act(async () => result.current.submitPermissions());

    expect(mocks.updateExternalAdminUserPermissions).not.toHaveBeenCalled();
    expect(mocks.showToast).toHaveBeenCalledWith("外管权限目录为空，请联系管理员", "error");
});

test("clears list filters, lookup results and password form when App scope changes", async () => {
    const { result, rerender } = renderHook(() => useExternalAdminUsersPage());

    act(() => result.current.setKeyword("old account"));
    act(() => result.current.setStatus("active"));
    act(() => result.current.openCreate());
    act(() => result.current.setDisplayUserId("VIP-1001"));
    await act(async () => result.current.lookupTarget());
    act(() => result.current.openPassword(externalUserFixture()));
    act(() => result.current.setPasswordForm({ confirmPassword: "OldPass123!", password: "OldPass123!" }));

    mocks.appCode = "lalu";
    rerender();

    await waitFor(() => expect(result.current.keyword).toBe(""));
    expect(result.current.status).toBe("");
    expect(result.current.createOpen).toBe(false);
    expect(result.current.createForm).toEqual({ confirmPassword: "", displayUserId: "", password: "", permissions: [], username: "" });
    expect(result.current.targetUser).toBeNull();
    expect(result.current.passwordUser).toBeNull();
    expect(result.current.passwordForm).toEqual({ confirmPassword: "", password: "" });
    expect(mocks.queryOptions.queryKey[1]).toBe("lalu");
});

test("loads and replaces an account permission snapshot, including explicit zero permissions", async () => {
    const item = externalUserFixture();
    const { result } = renderHook(() => useExternalAdminUsersPage());

    act(() => result.current.openPermissions(item));
    await waitFor(() => expect(result.current.permissionForm.permissions).toEqual(["user:list"]));
    expect(mocks.getExternalAdminUserPermissions).toHaveBeenCalledWith("fami", 71);

    act(() => result.current.setPermissions([]));
    await act(async () => result.current.submitPermissions());

    expect(mocks.updateExternalAdminUserPermissions).toHaveBeenCalledWith("fami", 71, [], 4);
    expect(mocks.showToast).toHaveBeenCalledWith("权限已更新，该账号需重新登录", "success");
    expect(result.current.permissionUser).toBeNull();
});

test("keeps the permission drawer open and offers reload after a concurrent update", async () => {
    const conflict = Object.assign(new Error("conflict"), { status: 409 });
    mocks.updateExternalAdminUserPermissions.mockRejectedValueOnce(conflict);
    const item = externalUserFixture();
    const { result } = renderHook(() => useExternalAdminUsersPage());

    act(() => result.current.openPermissions(item));
    await waitFor(() => expect(result.current.permissionLoading).toBe(false));
    await act(async () => result.current.submitPermissions());

    expect(result.current.permissionUser).toEqual(item);
    expect(result.current.permissionError).toBe("账号权限已被更新，请重新加载");
    expect(mocks.showToast).toHaveBeenCalledWith("账号权限已被更新，请重新加载", "error");

    mocks.getExternalAdminUserPermissions.mockResolvedValueOnce({
        accountId: "71",
        permissions: ["user:list", "user:update"],
        revision: 5,
    });
    act(() => result.current.retryPermissions());
    await waitFor(() => expect(result.current.permissionForm.expectedRevision).toBe(5));
    await act(async () => result.current.submitPermissions());

    expect(mocks.updateExternalAdminUserPermissions).toHaveBeenNthCalledWith(
        2,
        "fami",
        71,
        ["user:list", "user:update"],
        5,
    );
    expect(result.current.permissionUser).toBeNull();
});

test("does not expose backend permission codes when a non-conflict update fails", async () => {
    mocks.updateExternalAdminUserPermissions.mockRejectedValueOnce(
        new Error("privilege:grant requires user-title:grant"),
    );
    const { result } = renderHook(() => useExternalAdminUsersPage());

    act(() => result.current.openPermissions(externalUserFixture()));
    await waitFor(() => expect(result.current.permissionLoading).toBe(false));
    await act(async () => result.current.submitPermissions());

    expect(result.current.permissionError).toBe("更新外管权限失败");
    expect(mocks.showToast).toHaveBeenCalledWith("更新外管权限失败", "error");
});

test("ignores a late user lookup response after switching App", async () => {
    let resolveLookup;
    mocks.lookupExternalAdminUserTarget.mockImplementationOnce(
        () => new Promise((resolve) => {
            resolveLookup = resolve;
        }),
    );
    const { result, rerender } = renderHook(() => useExternalAdminUsersPage());

    act(() => result.current.openCreate());
    act(() => result.current.setDisplayUserId("VIP-1001"));
    let lookupPromise;
    act(() => {
        lookupPromise = result.current.lookupTarget();
    });
    mocks.appCode = "lalu";
    rerender();
    await waitFor(() => expect(result.current.createOpen).toBe(false));
    await act(async () => {
        resolveLookup(targetFixture());
        await lookupPromise;
    });

    expect(result.current.targetUser).toBeNull();
});

test("refreshes the App-scoped list after status and password changes", async () => {
    const item = externalUserFixture();
    const { result } = renderHook(() => useExternalAdminUsersPage());

    await act(async () => result.current.toggleStatus(item));
    expect(mocks.updateExternalAdminUserStatus).toHaveBeenCalledWith("fami", 71, "disabled");

    act(() => result.current.openPassword(item));
    act(() => result.current.setPasswordForm({ confirmPassword: "NextPass123!", password: "NextPass123!" }));
    await act(async () => result.current.submitPassword({ preventDefault: vi.fn() }));

    expect(mocks.resetExternalAdminUserPassword).toHaveBeenCalledWith("fami", 71, "NextPass123!");
    expect(mocks.invalidateQueries).toHaveBeenCalledTimes(2);
    expect(mocks.invalidateQueries).toHaveBeenNthCalledWith(1, { queryKey: ["external-admin-users", "fami"] });
    expect(mocks.invalidateQueries).toHaveBeenNthCalledWith(2, { queryKey: ["external-admin-users", "fami"] });
});

function targetFixture() {
    return {
        avatar: "https://cdn.example.com/user.png",
        defaultDisplayUserId: "1001",
        displayUserId: "VIP-1001",
        prettyDisplayUserId: "VIP-1001",
        prettyId: "pretty-1",
        status: "active",
        userId: "internal-user-1001",
        username: "Fami user",
    };
}

function externalUserFixture() {
    return {
        appCode: "fami",
        createdAtMs: 1784073600000,
        createdByAdminId: 9,
        id: 71,
        lastLoginAtMs: 1784077200000,
        linkedUser: targetFixture(),
        permissions: [],
        permissionRevision: 4,
        status: "active",
        updatedAtMs: 1784077200000,
        username: "ops.fami",
    };
}

function permissionCatalogFixture() {
    return {
        items: [
            { capabilities: ["user:list"], code: "user:list", defaultGranted: true, dependencies: [], group: "用户管理", label: "用户列表" },
            { capabilities: ["user:list", "user:update"], code: "user:update", defaultGranted: true, dependencies: ["user:list"], group: "用户管理", label: "编辑用户" },
            { capabilities: ["room:list"], code: "room:list", defaultGranted: false, dependencies: [], group: "房间管理", label: "房间列表" },
        ],
        total: 3,
    };
}
