/* @vitest-environment happy-dom */

import type { DmsPermissionResource } from '#/api/x/dms/permission';

import { flushPromises, mount } from '@vue/test-utils';
import { defineComponent, h } from 'vue';

import { beforeEach, describe, expect, it, vi } from 'vitest';

import GrantDrawer from '../GrantDrawer.vue';

const apiMocks = vi.hoisted(() => ({ createPermissionGrant: vi.fn() }));
const formMocks = vi.hoisted(() => ({ clearValidate: vi.fn(), config: undefined as any, resetFields: vi.fn(), setFieldsValue: vi.fn(), validate: vi.fn() }));
const drawerMocks = vi.hoisted(() => ({ changeOkLoading: vi.fn(), closeDrawer: vi.fn(), init: undefined as any }));
const messageMocks = vi.hoisted(() => ({ success: vi.fn(), warning: vi.fn() }));
const signMock = vi.hoisted(() => vi.fn());

vi.mock('#/api/x/dms/permission', () => ({ createPermissionGrant: apiMocks.createPermissionGrant }));
vi.mock('#/utils/jnpf', () => ({ onlineUtils: { sign: signMock } }));
vi.mock('@jnpf/hooks', () => ({ useMessage: () => ({ createMessage: messageMocks }) }));
vi.mock('@jnpf/ui/form', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    BasicForm: defineComponent({
      name: 'BasicForm',
      setup: (_, { slots }) => () => h('div', slots.principalIds?.({ field: 'principalIds', model: { principalIds: [], principalType: 'USER' } })),
    }),
    useForm: (config: any) => {
      formMocks.config = config;
      return [vi.fn(), { clearValidate: formMocks.clearValidate, resetFields: formMocks.resetFields, setFieldsValue: formMocks.setFieldsValue, validate: formMocks.validate }];
    },
  };
});
vi.mock('@jnpf/ui/drawer', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    BasicDrawer: defineComponent({
      name: 'BasicDrawer',
      emits: ['ok'],
      setup: (_, { slots }) => () => h('section', slots.default?.()),
    }),
    useDrawerInner: (init: any) => {
      drawerMocks.init = init;
      return [vi.fn(), { changeOkLoading: drawerMocks.changeOkLoading, closeDrawer: drawerMocks.closeDrawer }];
    },
  };
});

const folder: DmsPermissionResource = {
  createdAt: '2026-09-01T00:00:00Z',
  id: 'folder-1',
  name: '质量文件',
  resourceType: 'FOLDER',
  sourceId: 'folder-1',
  sourceType: 'MANUAL',
};

const SelectorStub = defineComponent({
  name: 'JnpfUserSelect',
  props: { multiple: Boolean, value: Array },
  setup: () => () => h('div'),
});

function mountDrawer() {
  return mount(GrantDrawer, {
    global: {
      components: {
        JnpfOrganizeSelect: SelectorStub,
        JnpfPosSelect: SelectorStub,
        JnpfRoleSelect: SelectorStub,
        JnpfUserSelect: SelectorStub,
      },
      stubs: { 'a-alert': true },
    },
  });
}

async function submit(wrapper: ReturnType<typeof mountDrawer>) {
  wrapper.findComponent({ name: 'BasicDrawer' }).vm.$emit('ok');
  await flushPromises();
}

describe('permission grant drawer', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    formMocks.resetFields.mockResolvedValue(undefined);
    formMocks.setFieldsValue.mockResolvedValue(undefined);
    formMocks.clearValidate.mockResolvedValue(undefined);
    formMocks.validate.mockResolvedValue({
      actions: ['READ', 'DOWNLOAD'],
      grantReason: '  项目协作  ',
      includeChildOrgs: false,
      principalIds: ['user-1', 'user-2'],
      principalType: 'USER',
      resourceScope: 'SELF',
    });
    apiMocks.createPermissionGrant.mockResolvedValue({ data: [{}, {}, {}, {}] });
  });

  it('creates one signed batch for every selected user and action', async () => {
    const wrapper = mountDrawer();
    await drawerMocks.init({ resource: folder });
    expect(formMocks.setFieldsValue).toHaveBeenCalledWith(expect.objectContaining({ principalIds: [] }));
    expect(formMocks.config.schemas.find((schema: any) => schema.field === 'principalIds').rules).toMatchObject([{ type: 'array', min: 1 }]);
    expect(wrapper.getComponent({ name: 'JnpfUserSelect' }).props('multiple')).toBe(true);

    await submit(wrapper);
    expect(signMock).toHaveBeenCalledOnce();
    const signed = signMock.mock.calls[0]![0];
    const batch = signed.metaData.biz_data[0];
    expect(batch.sourceId).toMatch(/^manual-/);
    expect(batch.items).toHaveLength(4);
    expect(batch.items.map(({ actionCode, principalId }: any) => [principalId, actionCode])).toEqual([
      ['user-1', 'READ'],
      ['user-1', 'DOWNLOAD'],
      ['user-2', 'READ'],
      ['user-2', 'DOWNLOAD'],
    ]);
    expect(batch.items.every((item: any) => item.grantReason === '项目协作' && item.resourceId === folder.id)).toBe(true);

    await signed.onSubmit({ biz_sign: 'signed-batch' });
    expect(apiMocks.createPermissionGrant).toHaveBeenCalledOnce();
    expect(apiMocks.createPermissionGrant).toHaveBeenCalledWith(batch, 'signed-batch');
    expect(drawerMocks.closeDrawer).toHaveBeenCalledOnce();
    expect(wrapper.emitted('reload')).toHaveLength(1);
    wrapper.unmount();
  });

  it('clears prior selections on principal type change and removes duplicate normalized IDs', async () => {
    const wrapper = mountDrawer();
    await drawerMocks.init({ resource: folder });
    await formMocks.config.schemas[0].componentProps.onChange();
    expect(formMocks.setFieldsValue).toHaveBeenLastCalledWith({ includeChildOrgs: false, maxUseCount: undefined, principalIds: [] });
    expect(formMocks.clearValidate).toHaveBeenCalledWith(['principalIds']);

    formMocks.validate.mockResolvedValueOnce({
      actions: ['READ'],
      grantReason: '',
      includeChildOrgs: true,
      principalIds: ['org-1--org', 'org-1--subOrg', 'org-2--org'],
      principalType: 'ORG',
      resourceScope: 'SELF',
    });
    await submit(wrapper);
    expect(signMock.mock.calls[0]![0].metaData.biz_data[0].items.map((item: any) => item.principalId)).toEqual(['org-1', 'org-2']);
    wrapper.unmount();
  });

  it('stops oversized batches before requesting a signature', async () => {
    formMocks.validate.mockResolvedValueOnce({
      actions: ['READ', 'DOWNLOAD'],
      grantReason: '',
      includeChildOrgs: false,
      principalIds: Array.from({ length: 251 }, (_, index) => `user-${index}`),
      principalType: 'USER',
      resourceScope: 'SELF',
    });
    const wrapper = mountDrawer();
    await drawerMocks.init({ resource: folder });
    await submit(wrapper);

    expect(messageMocks.warning).toHaveBeenCalledWith('一次最多创建 500 条授权，请减少主体或操作');
    expect(signMock).not.toHaveBeenCalled();
    expect(apiMocks.createPermissionGrant).not.toHaveBeenCalled();
    wrapper.unmount();
  });
});
