/* eslint-disable vue/one-component-per-file -- 模拟表单解析器和三种弹窗外壳，验证真实宿主提交链路。 */
import { flushPromises, mount } from '@vue/test-utils';

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

import GlobalFormModal from './index.vue';

const mocks = vi.hoisted(() => ({
  events: new Map<string, (config: any) => Promise<void>>(),
  getConfigData: vi.fn(),
  getModelInfo: vi.fn(),
  createModel: vi.fn(),
  updateModel: vi.fn(),
  request: vi.fn(),
  error: vi.fn(),
}));
vi.mock('@jnpf/hooks', () => ({ useMessage: () => ({ createMessage: { error: mocks.error, success: vi.fn(), warning: vi.fn() } }) }));
vi.mock('@vben/stores', () => ({ useUserStore: () => ({ getUserInfo: {} }) }));
vi.mock('#/api/onlineDev/visualDev', () => mocks);
vi.mock('#/api/request', () => ({ defHttp: { get: vi.fn(), post: vi.fn() } }));
vi.mock('#/store', () => ({ useBaseStore: () => ({ getSysConfig: {} }) }));
vi.mock('#/utils/jnpf', () => ({
  onlineUtils: {
    request: mocks.request,
    getEmitter: () => ({ on: (name, handler) => mocks.events.set(name, handler), off: (name) => mocks.events.delete(name) }),
  },
}));
vi.mock('#/locales', () => ({ $t: (key, fallback) => fallback || key }));
vi.mock('#/components/FormExtraPanel/index.vue', () => ({ default: { render: () => null } }));
vi.mock('#/directives/disablePasswordAutofill', () => ({ vDisablePasswordAutofill: {} }));
vi.mock('@jnpf/ui/modal', async () => {
  const { defineComponent, h } = await import('vue');
  return {
    BasicModal: defineComponent({
      name: 'ModalShell',
      props: { open: Boolean, confirmLoading: Boolean, closeFunc: { type: Function, default: undefined } },
      setup:
        (_, { slots }) =>
        () =>
          h('div', slots.default?.()),
    }),
  };
});
vi.mock('@jnpf/ui/popup', async () => {
  const { BasicModal } = await import('@jnpf/ui/modal');
  return { BasicPopup: BasicModal };
});
vi.mock('@jnpf/ui/drawer', async () => {
  const { BasicModal } = await import('@jnpf/ui/modal');
  return { BasicDrawer: BasicModal };
});
vi.mock('@jnpf/utils', async () => {
  const { defineComponent, h } = await import('vue');
  const { getScriptFunc } = await import('../../../../../packages/jnpf/utils/src/helpers/common');
  return {
    getScriptFunc,
    getDateTimeUnit: () => 'day',
    createAsyncComponent: () => defineComponent({ name: 'ParserStub', emits: ['submit'], setup: () => () => h('div') }),
  };
});

let wrapper: ReturnType<typeof mount>;
const hook = `({ formData, formContext, onlineUtils }) => onlineUtils.request(
  '/api/dms/biz/wenjian/fenfa/dayin-jilu/' + formContext.id + '/qianshou-fafang',
  'post', { bizSign: formData.biz_sign, bizReviewSign: formData.biz_review_sign })`;
async function open(submitMode: string = 'form', funcs?: any, type = 'modal') {
  funcs ??= { customSubmit: true, customSubmitFunc: hook };
  mocks.getConfigData.mockResolvedValue({ data: { webType: 2, formData: JSON.stringify({ fields: [], funcs, popupType: type }) } });
  const config = { modelId: 'handover', id: 'print-1', type, submitMode, onConfirm: vi.fn(), onCancel: vi.fn(), onSubmit: vi.fn() };
  await mocks.events.get('OPEN_FORM_MODAL')!(config);
  await flushPromises();
  return config;
}
function submit(data?: any, auditBizSign = 'issuer-sign') {
  data ??= { biz_sign: 'issuer-sign', biz_review_sign: 'recipient-sign' };
  const callback = vi.fn();
  const setFormData = (key, value) => {
    data[key] = value;
  };
  wrapper.findComponent({ name: 'ParserStub' }).vm.$emit(
    'submit',
    data,
    callback,
    {
      formData: data,
      setFormData,
      onlineUtils: { request: mocks.request },
    },
    [],
    () => [],
    auditBizSign,
  );
  return callback;
}
beforeEach(() => {
  vi.clearAllMocks();
  mocks.getModelInfo.mockResolvedValue({ data: { id: 'print-1', data: '{}' } });
  mocks.updateModel.mockResolvedValue({ code: 200, data: { id: 'print-1' } });
  mocks.request.mockResolvedValue({ code: 200, msg: '发放成功', data: { id: 'print-1' } });
  wrapper = mount(GlobalFormModal, { global: { stubs: { AButton: true } } });
});
afterEach(() => wrapper.unmount());

describe('目标表单提交及成功回调', () => {
  it('原流水重打恢复通过原生createModel保存，并使用冻结签名作为审计关联', async () => {
    mocks.createModel.mockResolvedValue({ code: 200, data: { id: 'reprint-doc' } });
    mocks.getConfigData.mockResolvedValue({ data: { webType: 2, formData: JSON.stringify({ fields: [], funcs: {
      customSubmit: true,
      customSubmitFunc: `({setFormData,formContext}) => {
        setFormData('biz_sign','frozen-sign');
        return new Promise((resolve,reject)=>formContext.submit(resolve,reject));
      }`,
    } }) } });
    const config = { modelId: '37a318b30d7c4daf9670d141b33ed1b7', type: 'modal', submitMode: 'form', params: { dmsReprint: true }, onConfirm: vi.fn() };
    await mocks.events.get('OPEN_FORM_MODAL')!(config); await flushPromises();
    submit({ biz_sign: 'new-sign', chongda_fasong_id: 'request', bupingheng_mingxi: [{ dayin_jilu_id: 'copy', cuoshi: 'cuoshi3', yuanyin: 'reason' }] }, 'new-sign');
    await flushPromises();
    expect(mocks.createModel).toHaveBeenCalledWith(config.modelId, expect.objectContaining({ data: expect.stringContaining('frozen-sign') }), 'frozen-sign');
    expect(mocks.updateModel).not.toHaveBeenCalled();
    expect(config.onConfirm).toHaveBeenCalledWith({ success: true, data: { id: 'reprint-doc' } });
  });

  it.each(['default', 'custom'])('%s 保留打开失败时的原回调契约', async (mode) => {
    mocks.getConfigData.mockRejectedValueOnce(new Error('配置加载失败'));
    const config = await open(mode);
    expect(config.onCancel).not.toHaveBeenCalled();
    expect(config.onConfirm).not.toHaveBeenCalled();
  });

  it.each(['modal', 'fullScreen', 'drawer'])('%s 确认前取消不提交、不调用成功回调', async (type) => {
    const config = await open('form', undefined, type);
    const close = wrapper.findComponent({ name: 'ModalShell' }).props('closeFunc') as () => Promise<boolean>;
    expect(await close()).toBe(true);
    expect(config.onCancel).toHaveBeenCalledTimes(1);
    expect(config.onConfirm).not.toHaveBeenCalled();
    expect(mocks.request).not.toHaveBeenCalled();
    expect(mocks.updateModel).not.toHaveBeenCalled();
    expect(mocks.createModel).not.toHaveBeenCalled();
  });

  it.each(['modal', 'fullScreen', 'drawer'])('%s 等待发放请求成功并转发签名，进行中重复提交只执行一次', async (type) => {
    let resolve;
    mocks.request.mockImplementation(
      () =>
        new Promise((done) => {
          resolve = done;
        }),
    );
    const config = await open('form', undefined, type);
    const callback = submit(undefined, 'current-event-sign');
    submit();
    await flushPromises();
    expect(mocks.request).toHaveBeenCalledTimes(1);
    expect(mocks.request).toHaveBeenCalledWith(
      expect.stringContaining('/print-1/qianshou-fafang'),
      'post',
      { bizSign: 'issuer-sign', bizReviewSign: 'recipient-sign' },
      expect.objectContaining({ 'X-Audit-Biz-Sign': 'current-event-sign' }),
    );
    expect(mocks.updateModel).not.toHaveBeenCalled();
    expect(mocks.createModel).not.toHaveBeenCalled();
    expect(callback).not.toHaveBeenCalled();
    expect(config.onConfirm).not.toHaveBeenCalled();
    resolve({ code: 200, data: { id: 'print-1' } });
    await flushPromises();
    expect(callback).toHaveBeenCalledWith('print-1');
    expect(config.onConfirm).toHaveBeenCalledExactlyOnceWith({ success: true, data: { id: 'print-1' } });
  });

  it.each(['reject', 'code'])('接口失败（%s）不触发成功回调，允许同一表单重试', async (failure) => {
    const config = await open();
    if (failure === 'reject') mocks.request.mockRejectedValueOnce(new Error('发放失败'));
    else mocks.request.mockResolvedValueOnce({ code: 500, msg: '发放失败' });
    const callback = submit();
    await flushPromises();
    expect(config.onConfirm).not.toHaveBeenCalled();
    expect(callback).not.toHaveBeenCalled();
    expect(mocks.error).toHaveBeenCalledWith('发放失败');
    submit();
    await flushPromises();
    expect(config.onConfirm).toHaveBeenCalledTimes(1);
    expect(mocks.updateModel).not.toHaveBeenCalled();
  });

  it('目标脚本无效时不退回通用保存', async () => {
    const config = await open('form', { customSubmit: true, customSubmitFunc: '' });
    submit();
    await flushPromises();
    expect(config.onConfirm).not.toHaveBeenCalled();
    expect(mocks.updateModel).not.toHaveBeenCalled();
    expect(mocks.error).toHaveBeenCalled();
  });

  it('目标脚本可以显式调用原生保存，携带最新字段和签名', async () => {
    const config = await open('form', {
      customSubmit: true,
      customSubmitFunc: `({setFormData, formContext}) => {
      setFormData('beizhu', '修改后的备注');
      return new Promise((resolve, reject) => formContext.submit(resolve, reject));
    }`,
    });
    submit();
    await flushPromises();
    expect(mocks.updateModel).toHaveBeenCalledExactlyOnceWith(
      'handover',
      expect.objectContaining({
        onlineUtilsOpen: true,
        data: expect.stringContaining('修改后的备注'),
      }),
      'issuer-sign',
    );
    expect(config.onConfirm).toHaveBeenCalledTimes(1);
  });

  it.each(['default', 'form'])('%s 未启用自定义提交时仍走原生保存并返回 ID', async (mode) => {
    const config = await open(mode, { customSubmit: false });
    const callback = submit();
    await flushPromises();
    expect(mocks.updateModel).toHaveBeenCalledTimes(1);
    expect(callback).toHaveBeenCalledWith('print-1');
    expect(config.onConfirm).toHaveBeenCalledTimes(1);
    expect(mocks.request).not.toHaveBeenCalled();
  });

  it('未选择 form 模式时保留原默认保存语义', async () => {
    const config = await open('default');
    submit();
    await flushPromises();
    expect(mocks.updateModel).toHaveBeenCalledTimes(1);
    expect(mocks.request).not.toHaveBeenCalled();
    expect(config.onConfirm).toHaveBeenCalledTimes(1);
  });

  it.each(['modal', 'fullScreen', 'drawer'])('%s 旧默认模式仍不转发新增审计参数', async (type) => {
    const config = await open('default', undefined, type);
    delete config.submitMode; // 未传 submitMode 的旧调用也是默认模式。
    submit(undefined, 'must-not-forward');
    await flushPromises();
    expect(mocks.updateModel.mock.calls[0]?.[2]).toBeUndefined();
    expect(mocks.request).not.toHaveBeenCalled();
    expect(config.onConfirm).toHaveBeenCalledTimes(1);
  });

  it.each(['default', 'custom'])('%s 原提交失败保留弹窗，不调用成功或取消回调', async (mode) => {
    const config = await open(mode);
    if (mode === 'default') mocks.updateModel.mockRejectedValueOnce(new Error('save failed'));
    else config.onSubmit.mockRejectedValueOnce(new Error('custom failed'));
    const callback = submit();
    await flushPromises();
    expect(config.onConfirm).not.toHaveBeenCalled();
    expect(config.onCancel).not.toHaveBeenCalled();
    expect(callback).not.toHaveBeenCalled();
    expect(wrapper.findComponent({ name: 'ParserStub' }).exists()).toBe(true);
    submit();
    await flushPromises();
    expect(config.onConfirm).toHaveBeenCalledTimes(1);
    expect(mocks.request).not.toHaveBeenCalled();
  });

  it('调用方 custom 模式等待原 onSubmit Promise，仍不调用目标脚本或 afterSubmit', async () => {
    let resolve;
    const config = await open('custom');
    config.onSubmit.mockImplementation(
      () =>
        new Promise((done) => {
          resolve = done;
        }),
    );
    const callback = submit();
    await flushPromises();
    expect(config.onConfirm).not.toHaveBeenCalled();
    resolve();
    await flushPromises();
    expect(config.onConfirm).toHaveBeenCalledTimes(1);
    expect(callback).not.toHaveBeenCalled();
    expect(mocks.request).not.toHaveBeenCalled();
    expect(mocks.updateModel).not.toHaveBeenCalled();
  });

  it('调用方 custom 模式继续由 onSubmit 处理', async () => {
    const config = await open('custom');
    submit();
    await flushPromises();
    expect(config.onSubmit).toHaveBeenCalledTimes(1);
    expect(config.onConfirm).toHaveBeenCalledTimes(1);
    expect(mocks.updateModel).not.toHaveBeenCalled();
    expect(mocks.request).not.toHaveBeenCalled();
  });
});


describe('重打运行时属性与先保存收尾', () => {
  async function openReprint() {
    mocks.getConfigData.mockResolvedValue({ data: { webType: 2, formData: JSON.stringify({
      popupType: 'fullScreen',
      fields: [{ __vModel__: 'bupingheng_mingxi', __config__: { jnpfKey: 'table', children: [
        { __vModel__: '', controlKey: 'wenjian_mingcheng', __config__: { isVirtualField: true } },
      ] } }],
      funcs: { customSubmit: true, customSubmitFunc: `({ formData, setFormData, formContext, onlineUtils }) => {
        setFormData('bupingheng_mingxi', [{...formData.bupingheng_mingxi[0], yuanyin: 'frozen'}]);
        return onlineUtils.request('/reprint-test', 'post', {formData, formContext});
      }` },
    }) } });
    const config = { modelId: '37a318b30d7c4daf9670d141b33ed1b7', submitMode: 'form', params: { dmsReprint: true }, onConfirm: vi.fn() };
    await mocks.events.get('OPEN_FORM_MODAL')!(config);
    await flushPromises();
    return config;
  }
  it('回填保留文件展示属性，但原生保存仍剔除虚拟字段', async () => {
    let resolve!: (value: any) => void;
    mocks.request.mockReturnValue(new Promise(r => { resolve = r; }));
    const config = await openReprint();
    const runtime = { bupingheng_mingxi: [{ dayin_jilu_id: 'record', cuoshi: 'cuoshi3', wenjian_mingcheng: '原文件', yuanyin: 'reason' }] };
    submit(runtime);
    await flushPromises();
    expect(runtime.bupingheng_mingxi[0]).toMatchObject({ wenjian_mingcheng: '原文件', yuanyin: 'frozen' });
    const ctx = mocks.request.mock.calls[0]![2].formContext;
    mocks.createModel.mockResolvedValue({ code: 200, data: { id: 'doc' } });
    await new Promise((resolve, reject) => ctx.submit(resolve, reject));
    const saved = JSON.parse(mocks.createModel.mock.calls[0]![1].data);
    expect(saved.bupingheng_mingxi[0]).not.toHaveProperty('wenjian_mingcheng');
    expect(saved.bupingheng_mingxi[0].dayin_jilu_id).toBe('record');
    resolve({ code: 200, data: { id: 'doc' } });
    await flushPromises();
    expect(config.onConfirm).toHaveBeenCalledTimes(1);
    expect(wrapper.findComponent({ name: 'ModalShell' }).props('open')).toBe(false);
  });
  it('暂存关闭不触发登记成功回调，后续原生保存使用关闭前快照', async () => {
    let captured: any;
    mocks.request.mockImplementation(async (_url, _method, { formContext }) => {
      captured = formContext;
      await formContext.closeReprintForm();
      return { code: 200, dmsReprintPrepared: true };
    });
    const config = await openReprint();
    const runtime = { biz_sign: 'frozen-sign', chongda_fasong_id: 'request', bupingheng_mingxi: [{ dayin_jilu_id: 'record', cuoshi: 'cuoshi3', wenjian_mingcheng: '原文件', yuanyin: 'reason' }] };
    const callback = submit(runtime);
    await flushPromises();
    expect(mocks.createModel).not.toHaveBeenCalled();
    expect(config.onConfirm).not.toHaveBeenCalled();
    expect(callback).not.toHaveBeenCalled();
    expect(wrapper.findComponent({ name: 'ModalShell' }).props('open')).toBe(false);
    runtime.bupingheng_mingxi.length = 0;
    runtime.biz_sign = 'changed';
    mocks.createModel.mockResolvedValue({ code: 200, data: { id: 'doc' } });
    await new Promise((resolve, reject) => captured.submit(resolve, reject));
    const saved = JSON.parse(mocks.createModel.mock.calls[0]![1].data);
    expect(saved.bupingheng_mingxi[0]).toMatchObject({ dayin_jilu_id: 'record', yuanyin: 'frozen' });
    expect(saved.bupingheng_mingxi[0]).not.toHaveProperty('wenjian_mingcheng');
    expect(mocks.createModel.mock.calls[0]![2]).toBe('frozen-sign');
  });
  it('登记保存失败停止loading并保留原表单供重试', async () => {
    mocks.request.mockRejectedValue(new Error('登记失败'));
    const config = await openReprint();
    submit({ bupingheng_mingxi: [{ dayin_jilu_id: 'record', cuoshi: 'cuoshi3' }] });
    await flushPromises();
    expect(wrapper.findComponent({ name: 'ModalShell' }).props('confirmLoading')).toBe(false);
    expect(wrapper.findComponent({ name: 'ModalShell' }).props('open')).toBe(true);
    expect(config.onConfirm).not.toHaveBeenCalled();
  });
});
