/* eslint-disable vue/one-component-per-file -- 测试中分别模拟弹窗容器和表单解析器。 */
import { flushPromises, mount } from '@vue/test-utils';

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

import FormPopup from './FormPopup.vue';

const mocks = vi.hoisted(() => ({
  capture: vi.fn(),
  confirm: vi.fn(),
  createModel: vi.fn(),
  customFormInit: vi.fn(),
  error: vi.fn(),
  getDataInterfaceRes: vi.fn(),
  getLaunchFlowParamList: vi.fn(),
  getParamList: vi.fn(),
  init: undefined as ((data: any) => void) | undefined,
  launchFlow: vi.fn(),
  reset: vi.fn(),
  runtime: {} as any,
  selectedRows: vi.fn(),
  sign: vi.fn(),
  signedUtils: vi.fn(),
  submit: vi.fn(),
  validate: vi.fn(),
  warning: vi.fn(),
}));

vi.mock('@jnpf/hooks', () => ({
  useMessage: () => ({ createConfirm: mocks.confirm, createMessage: { error: mocks.error, success: vi.fn(), warning: mocks.warning } }),
}));
vi.mock('@vben/stores', () => ({ useUserStore: () => ({ getUserInfo: {} }) }));
vi.mock('#/api/onlineDev/visualDev', () => ({ createModel: mocks.createModel, launchFlow: mocks.launchFlow }));
vi.mock('#/api/systemData/dataInterface', () => ({ getDataInterfaceRes: mocks.getDataInterfaceRes }));
vi.mock('#/api/onlineDev/auditHeaders', () => ({ createAuditCorrelationId: () => 'audit-id' }));
vi.mock('#/locales', () => ({ $t: (value: string, fallback?: string) => fallback || value }));
vi.mock('#/utils/jnpf', () => ({
  getLaunchFlowParamList: mocks.getLaunchFlowParamList,
  getParamList: mocks.getParamList,
  onlineUtils: { capture: mocks.capture, sign: mocks.sign },
}));
vi.mock('#/components/FormGenerator/src/helper/auditDisplay', () => ({
  createAuditRequestOnlineUtils: (utils: any) => utils,
  createSignedAuditRequestOnlineUtils: mocks.signedUtils,
}));
vi.mock('../list/CustomForm.vue', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    default: defineComponent({
      setup(_, { expose }) {
        expose({ init: mocks.customFormInit });
        return () => h('div');
      },
    }),
  };
});
vi.mock('@jnpf/ui/popup', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    BasicPopup: defineComponent({
      setup:
        (_, { slots }) =>
        () =>
          h('div', [h('header', slots.insertToolbar?.()), slots.default?.()]),
    }),
    usePopupInner: (init: (data: any) => void) => {
      mocks.init = init;
      return [vi.fn(), { changeLoading: vi.fn(), setPopupProps: vi.fn() }];
    },
  };
});
vi.mock('@jnpf/utils', async () => {
  const { defineComponent, h, inject } = await import('vue');
  const { getScriptFunc } = await import('../../../../../../../packages/jnpf/utils/src/helpers/common');
  return {
    createAsyncComponent: () =>
      defineComponent({
        name: 'ParserStub',
        emits: ['runtimeChange', 'submit'],
        setup(_, { expose }) {
          const onlineUtils = inject('onlineUtils');
          expose({
            getRuntimeParameter: () => ({ ...mocks.runtime, getTableSelectedRows: mocks.selectedRows, onlineUtils }),
            handleReset: mocks.reset,
            handleSubmit: mocks.submit,
            handleValidate: mocks.validate,
          });
          return () => h('div');
        },
      }),
    getDateTimeUnit: () => 'day',
    getScriptFunc,
  };
});

describe('pure form toolbar and save', () => {
  let wrapper: ReturnType<typeof mount>;

  beforeEach(() => {
    vi.resetAllMocks();
    mocks.runtime = { formData: { id: '', status: 'ready' } };
    mocks.selectedRows.mockReturnValue([]);
    mocks.validate.mockResolvedValue(true);
    mocks.getDataInterfaceRes.mockResolvedValue({ msg: 'done' });
    mocks.launchFlow.mockResolvedValue({ msg: 'done' });
    mocks.signedUtils.mockImplementation((utils, getSign) => ({ ...utils, credential: getSign() }));
  });

  afterEach(() => {
    wrapper?.unmount();
    wrapper = undefined as any;
  });

  async function open(customSubmit = false, config: any = {}, isPreview = false) {
    if (!wrapper) wrapper = mount(FormPopup, { global: { stubs: { AButton: { template: '<button><slot /></button>' } } } });
    mocks.init?.({
      modelId: 'model',
      isPreview,
      formData: JSON.stringify({
        fields: [],
        funcs: { customSubmit, customSubmitFunc: '({ formContext }) => new Promise((resolve, reject) => formContext.submit(resolve, reject))' },
        ...config,
      }),
    });
    await flushPromises();
  }

  function customButton(actionConfig: any = {}, extra: any = {}) {
    return {
      value: 'action',
      label: '业务操作',
      show: true,
      actionConfig: { btnType: 2, func: '({ onlineUtils }) => onlineUtils.capture()', ...actionConfig },
      ...extra,
    };
  }

  async function click(label = '业务操作') {
    const button = wrapper.findAll('button').find((item) => item.text() === label);
    expect(button, `button ${label}`).toBeDefined();
    await button!.trigger('click');
    await flushPromises();
  }

  it.each([undefined, true, false])('hides confirm only for explicit false (%s)', async (hasConfirmBtn) => {
    await open(false, { hasConfirmBtn });
    expect(wrapper.findAll('button').some((button) => button.text() === 'common.okText')).toBe(hasConfirmBtn !== false);
    expect(wrapper.findAll('button').some((button) => button.text() === 'common.resetText')).toBe(true);
    if (hasConfirmBtn !== false) {
      await click('common.okText');
      expect(mocks.submit).toHaveBeenCalledOnce();
    }
  });

  it('runs a button with latest selected tuples without validating or submitting the scan form', async () => {
    await open(false, {
      hasConfirmBtn: false,
      formCustomBtns: [
        customButton({
          enableFormValidate: false,
          func: '({ formData, data, getTableSelectedRows, onlineUtils }) => onlineUtils.capture(formData, data, getTableSelectedRows("items"))',
        }),
      ],
    });
    mocks.runtime.formData = { id: '', status: 'changed' };
    const rows = [[2, { wenjian_id: 'doc-2' }]];
    mocks.selectedRows.mockReturnValue(rows);
    await click();
    expect(mocks.capture).toHaveBeenCalledExactlyOnceWith(mocks.runtime.formData, mocks.runtime.formData, rows);
    expect(mocks.selectedRows).toHaveBeenCalledWith('items');
    expect(mocks.validate).not.toHaveBeenCalled();
    expect(mocks.submit).not.toHaveBeenCalled();
    expect(mocks.createModel).not.toHaveBeenCalled();
    expect(mocks.reset).not.toHaveBeenCalled();
  });

  it('updates enable rules when runtime data changes and omits hidden buttons', async () => {
    await open(false, {
      formCustomBtns: [
        customButton({ enableFunc: '({ data }) => data.status === "ready"' }),
        customButton({}, { value: 'hidden', label: '隐藏按钮', show: false }),
      ],
    });
    expect(wrapper.text()).toContain('业务操作');
    expect(wrapper.text()).not.toContain('隐藏按钮');
    mocks.runtime.formData.status = 'blocked';
    wrapper.findComponent({ name: 'ParserStub' }).vm.$emit('runtimeChange');
    await flushPromises();
    expect(wrapper.text()).not.toContain('业务操作');
  });

  it.each([true, false])('validates by default and executes only on success (%s)', async (valid) => {
    mocks.validate.mockResolvedValue(valid);
    await open(false, { formCustomBtns: [customButton()] });
    await click();
    expect(mocks.validate).toHaveBeenCalledOnce();
    expect(mocks.capture).toHaveBeenCalledTimes(valid ? 1 : 0);
    expect(mocks.createModel).not.toHaveBeenCalled();
  });

  it('discards an old action when the form is reopened during validation', async () => {
    let resolveValidate!: (value: boolean) => void;
    mocks.validate.mockReturnValue(new Promise((resolve) => (resolveValidate = resolve)));
    await open(false, { formCustomBtns: [customButton()] });
    await click();
    await open(false);
    resolveValidate(true);
    await flushPromises();
    expect(mocks.capture).not.toHaveBeenCalled();
  });

  it('lets scripts validate before explicitly requesting a signature', async () => {
    await open(false, {
      formCustomBtns: [
        customButton({
          biz_sign_enabled: true,
          func: '({ formData, sign }) => { if (formData.status !== "ready") return; return sign(({ bizSign, onlineUtils }) => onlineUtils.capture(bizSign, onlineUtils.credential)); }',
        }),
      ],
    });
    mocks.runtime.formData.status = 'blocked';
    await click();
    expect(mocks.sign).not.toHaveBeenCalled();
    mocks.runtime.formData.status = 'ready';
    await click();
    expect(mocks.sign).toHaveBeenCalledOnce();
    expect(mocks.capture).not.toHaveBeenCalled();
    const options = mocks.sign.mock.calls[0]![0];
    expect(options.metaData).toMatchObject({ biz_form_id: 'model', biz_button: '业务操作', biz_data: [mocks.runtime.formData] });
    options.onCancel();
    expect(mocks.capture).not.toHaveBeenCalled();
    options.onSubmit({ biz_sign: 'sign-id' });
    expect(mocks.capture).toHaveBeenCalledExactlyOnceWith('sign-id', 'sign-id');
  });

  it('does not run a pending signed callback after reset', async () => {
    await open(false, { formCustomBtns: [customButton({ biz_sign_enabled: true, func: '({ sign }) => sign(({ onlineUtils }) => onlineUtils.capture())' })] });
    await click();
    const options = mocks.sign.mock.calls[0]![0];
    await click('common.resetText');
    options.onSubmit({ biz_sign: 'old-sign' });
    expect(mocks.capture).not.toHaveBeenCalled();
  });

  it('calls sign callbacks directly when electronic signing is disabled', async () => {
    await open(false, { formCustomBtns: [customButton({ func: '({ sign }) => sign(({ bizSign, onlineUtils }) => onlineUtils.capture(bizSign))' })] });
    await click();
    expect(mocks.capture).toHaveBeenCalledExactlyOnceWith(undefined);
    expect(mocks.sign).not.toHaveBeenCalled();
  });

  it('opens an associated form with the current record', async () => {
    await open(false, { formCustomBtns: [customButton({ btnType: 1, modelId: 'target' })] });
    await click();
    expect(mocks.customFormInit).toHaveBeenCalledWith(expect.objectContaining({ menuId: 'target', record: mocks.runtime.formData, recordModelId: 'model' }));
    expect(mocks.createModel).not.toHaveBeenCalled();
  });

  it('runs an interface only after its signature and confirmation', async () => {
    mocks.getParamList.mockReturnValue([{ field: 'status', defaultValue: 'ready' }]);
    await open(false, { formCustomBtns: [customButton({ btnType: 3, interfaceId: 'interface', templateJson: [], useConfirm: true, biz_sign_enabled: true })] });
    await click();
    expect(mocks.confirm).not.toHaveBeenCalled();
    mocks.sign.mock.calls[0]![0].onSubmit({ biz_sign: 'sign-id' });
    expect(mocks.confirm).toHaveBeenCalledOnce();
    expect(mocks.getDataInterfaceRes).not.toHaveBeenCalled();
    await mocks.confirm.mock.calls[0]![0].onOk();
    expect(mocks.getParamList).toHaveBeenCalledWith([], mocks.runtime.formData);
    expect(mocks.getDataInterfaceRes).toHaveBeenCalledExactlyOnceWith('interface', { paramList: [{ field: 'status', defaultValue: 'ready' }] });
  });

  it('launches a configured flow with mapped form data', async () => {
    mocks.getLaunchFlowParamList.mockReturnValue({ status: 'ready' });
    await open(false, { formCustomBtns: [customButton({ btnType: 4, launchFlow: { flowId: 'flow', transferList: [], currentUser: true } })] });
    await click();
    expect(mocks.getLaunchFlowParamList).toHaveBeenCalledWith([], mocks.runtime.formData);
    expect(mocks.launchFlow).toHaveBeenCalledWith('model', expect.objectContaining({ template: 'flow', btnCode: 'action', dataList: [{ status: 'ready' }] }));
  });

  it('prevents custom actions in preview', async () => {
    await open(false, { formCustomBtns: [customButton()] }, true);
    await click();
    expect(mocks.warning).toHaveBeenCalled();
    expect(mocks.validate).not.toHaveBeenCalled();
    expect(mocks.capture).not.toHaveBeenCalled();
  });

  it('reports rejected script promises without saving the form', async () => {
    await open(false, { formCustomBtns: [customButton({ func: '() => Promise.reject(new Error("业务失败"))' })] });
    await click();
    expect(mocks.error).toHaveBeenCalledWith('业务失败');
    expect(mocks.createModel).not.toHaveBeenCalled();
  });

  it.each([false, true])('passes the saved ID before resetting (customSubmit=%s)', async (customSubmit) => {
    await open(customSubmit);
    let resolveSave!: (response: any) => void;
    mocks.createModel.mockReturnValue(new Promise((resolve) => (resolveSave = resolve)));
    const callback = vi.fn(() => expect(mocks.reset).not.toHaveBeenCalled());
    wrapper.findComponent({ name: 'ParserStub' }).vm.$emit('submit', { id: '', name: 'new' }, callback);
    await flushPromises();
    expect(callback).not.toHaveBeenCalled();

    resolveSave({ data: { id: 'saved-id' }, msg: 'saved' });
    await flushPromises();
    expect(callback).toHaveBeenCalledExactlyOnceWith('saved-id');
    expect(mocks.reset).toHaveBeenCalledOnce();
  });

  it.each([false, true])('does not call afterSubmit or reset on failed save (customSubmit=%s)', async (customSubmit) => {
    await open(customSubmit);
    mocks.createModel.mockRejectedValue(new Error('save failed'));
    const callback = vi.fn();
    wrapper.findComponent({ name: 'ParserStub' }).vm.$emit('submit', { name: 'new' }, callback);
    await flushPromises();
    expect(callback).not.toHaveBeenCalled();
    expect(mocks.reset).not.toHaveBeenCalled();
  });
});
