import { beforeEach, describe, expect, it, vi } from 'vitest';
const mocks = vi.hoisted(() => ({ post: vi.fn(), get: vi.fn(), print: vi.fn(), open: vi.fn(), emit: vi.fn(), preview: vi.fn() }));
vi.mock('#/api/request', () => ({ defHttp: { post: mocks.post, get: mocks.get } }));
vi.mock('#/utils/jnpf', () => ({ onlineUtils: { openFormModal: mocks.open, previewDmsFile: mocks.preview, getEmitter: () => ({ emit: mocks.emit }) } }));
vi.mock('../dmsHiprint', () => ({ printManagedDmsDocument: mocks.print }));
import { openDmsReprint, submitDmsReprint, sendDmsReprint, resumeDmsReprint, cancelDmsReprint, refreshDmsReprintLock, REPRINT_LOCK_EVENT } from '../dmsReprint';
import { configureReprintModal, reprintAuditSign, DMS_REPRINT_MODEL_ID } from '../dmsReprintMeasure';
let attempt: any;
let data: any;
let submit: any;
let context: any;
const receiptStore = new Map<string, string>();
beforeEach(() => {
  vi.resetAllMocks();
  receiptStore.clear();
  vi.stubGlobal('localStorage', {
    getItem: (k: string) => receiptStore.get(k),
    setItem: (k: string, v: string) => receiptStore.set(k, v),
    removeItem: (k: string) => receiptStore.delete(k),
  });
  attempt = {
    id: 'request',
    tenantId: '0',
    userId: 'actor',
    printRecordId: 'record',
    fileVersionId: 'version',
    reason: 'original reason',
    bizSign: 'original-sign',
    createdAt: 1700000000000,
    state: 'OPEN',
    numbersJson: JSON.stringify([{ serialNumber: 'original', countSerialNumber: '20260001' }]),
    copies: 1,
    batchesJson: '[]',
  };
  data = { chongda_fasong_id: 'request', biz_sign: 'new-sign', bupingheng_mingxi: [{ dayin_jilu_id: 'record', cuoshi: 'cuoshi3', yuanyin: 'new reason' }] };
  submit = vi.fn((resolve: any) => {
    attempt.documentId = 'doc';
    resolve({ code: 200, data: { id: 'doc' } });
  });
  context = {
    formData: data,
    setFormData: (k: string, v: any) => {
      data[k] = v;
    },
    formContext: { isEdit: false, submit, closeReprintForm: vi.fn().mockResolvedValue(undefined) },
  };
  mocks.post.mockImplementation(async ({ url }: any) => {
    if (url.endsWith('jieshu-fasong')) attempt.state = 'CLOSED';
    if (url.endsWith('queren-pici')) attempt.state = 'CONFIRMED';
    return { code: 200, data: { ...attempt } };
  });
  mocks.print.mockImplementation(async () => {
    attempt.state = 'CONFIRMED';
  });
  mocks.preview.mockResolvedValue(true);
});
describe('原生重打成功后登记及恢复', () => {
  it('在线配置格式贯穿重打入口、原生表单、预览和实际发送，号码保持原流水', async () => {
    const builder = async ({ serialNumber, countSerialNumber }: any) => [{ barValue: serialNumber, barText: `原件 ${serialNumber} / ${countSerialNumber || '历史无计数号'}` }];
    mocks.get.mockResolvedValue({ code: 200, data: { locked: false, printRecordId: 'record' } });
    await openDmsReprint('record', undefined, builder, ['RECORD', 'REPORT']);
    context.params = mocks.open.mock.calls[0]![0].params;
    expect(context.params.buildCustomBarcode).toBe(builder);
    expect(context.params.watermarkBusinessTypes).toEqual(['RECORD', 'REPORT']);
    await submitDmsReprint(context);
    const preview = mocks.preview.mock.calls[0]![0];
    expect(preview.watermarkType).toBe('print');
    expect(preview.watermarkBusinessTypes).toEqual(['RECORD', 'REPORT']);
    expect(preview.customBarcode).toEqual([{ barValue: 'original', barText: '原件 original / 20260001' }]);
    await preview.reprint.send();
    expect(mocks.print.mock.calls[0]![1].buildCustomBarcode).toBe(builder);
    expect(mocks.print.mock.calls[0]![1].watermarkBusinessTypes).toEqual(['RECORD', 'REPORT']);
    expect(mocks.print.mock.calls[0]![2].numbersJson).toBe(attempt.numbersJson);
  });
  it('共享格式接口失败时保留原重打任务，不打开预览、不发送、不提前登记', async () => {
    const builder = async () => { throw new Error('格式接口不可用'); };
    mocks.get.mockResolvedValue({ code: 200, data: { locked: false, printRecordId: 'record' } });
    await openDmsReprint('record', undefined, builder);
    context.params = mocks.open.mock.calls[0]![0].params;
    const result = await submitDmsReprint(context);
    expect(result.msg).toContain('格式接口不可用');
    expect(mocks.preview).not.toHaveBeenCalled();
    expect(mocks.print).not.toHaveBeenCalled();
    expect(submit).not.toHaveBeenCalled();
  });
  it('历史无计数号重打和已保存登记恢复仍使用在线配置，不补号', async () => {
    const builder = async ({ serialNumber, countSerialNumber }: any) => [{ barValue: serialNumber, barText: countSerialNumber || `历史 ${serialNumber}` }];
    attempt.numbersJson = JSON.stringify([{ serialNumber: 'original' }]);
    attempt.documentId = 'doc';
    mocks.get.mockResolvedValue({ code: 200, data: attempt });
    await resumeDmsReprint('record', 'request', undefined, builder);
    const preview = mocks.preview.mock.calls[0]![0];
    expect(preview.customBarcode).toEqual([{ barValue: 'original', barText: '历史 original' }]);
    await preview.reprint.send();
    expect(mocks.print.mock.calls[0]![1].buildCustomBarcode).toBe(builder);
    expect(mocks.post.mock.calls.some(([req]: any) => req.url.endsWith('/yulan-canshu'))).toBe(false);
  });
  it('占用弹窗及未保存登记恢复均保留格式函数', async () => {
    const builder = ({ serialNumber }: any) => [{ barValue: serialNumber, barText: serialNumber }];
    mocks.get.mockResolvedValueOnce({ code: 200, data: { locked: true } }).mockResolvedValueOnce({ code: 200, data: attempt });
    await openDmsReprint('record', undefined, builder);
    expect(mocks.emit.mock.calls[0]![1].buildCustomBarcode).toBe(builder);
    await resumeDmsReprint('record', 'request', undefined, builder);
    expect(mocks.open.mock.calls[0]![0].params.buildCustomBarcode).toBe(builder);
  });
  it('统一弹窗预填一条固定措施，恢复保持原请求ID', async () => {
    mocks.get.mockResolvedValue({ code: 200, data: attempt });
    await resumeDmsReprint('record', 'request');
    const config = mocks.open.mock.calls[0]![0];
    expect(config.submitMode).toBe('form');
    expect(config.params.requestId).toBe('request');
    expect(config.params.bupingheng_mingxi).toEqual([{ dayin_jilu_id: 'record', cuoshi: 'cuoshi3', yuanyin: 'original reason' }]);
  });
  it('查询未锁定后才打开原生重打表单', async () => {
    mocks.get.mockResolvedValue({ code: 200, data: { locked: false, printRecordId: 'record' } });
    await openDmsReprint('record');
    expect(mocks.get).toHaveBeenCalledWith({ url: expect.stringContaining('/record/lock-status') });
    expect(mocks.open).toHaveBeenCalledTimes(1);
    expect(mocks.emit).not.toHaveBeenCalled();
  });
  it('已有占用只打开状态弹窗，包括其他发起人的占用', async () => {
    const status = { locked: true, printRecordId: 'record', requestId: 'request', owner: false, phase: 'READY' };
    mocks.get.mockResolvedValue({ code: 200, data: status });
    await openDmsReprint('record');
    expect(mocks.open).not.toHaveBeenCalled();
    expect(mocks.emit).toHaveBeenCalledWith(REPRINT_LOCK_EVENT, { status, onConfirm: undefined, buildCustomBarcode: undefined });
  });
  it('刷新状态只补交已保存回执，不打印也不自行登记', async () => {
    attempt.state = 'OPEN';
    attempt.batchesJson = JSON.stringify([{ id: 'batch', state: 'SENDING' }]);
    receiptStore.set('dms-reprint-receipt:0:actor:request:batch', 'token');
    mocks.get.mockResolvedValueOnce({ code: 200, data: attempt }).mockResolvedValueOnce({ code: 200, data: { locked: true, phase: 'REGISTRATION_PENDING' } });
    const result = await refreshDmsReprintLock({ locked: true, printRecordId: 'record', requestId: 'request', owner: true });
    expect(result.phase).toBe('REGISTRATION_PENDING');
    expect(mocks.post).toHaveBeenCalledWith(
      expect.objectContaining({ url: expect.stringContaining('queren-pici'), data: expect.objectContaining({ token: 'token' }) }),
      expect.anything(),
    );
    expect(mocks.print).not.toHaveBeenCalled();
    expect(submit).not.toHaveBeenCalled();
    expect(receiptStore.size).toBe(0);
  });
  it('取消只使用原任务ID，后端拒绝时不打开新表单', async () => {
    mocks.post.mockResolvedValue({ code: 400, msg: '任务已派发，不能取消' });
    await expect(cancelDmsReprint('record', 'request')).rejects.toThrow('不能取消');
    expect(mocks.open).not.toHaveBeenCalled();
    expect(mocks.print).not.toHaveBeenCalled();
  });
  it('原任务已消失时恢复不偷偷创建新任务', async () => {
    mocks.get.mockResolvedValue({ code: 200, data: null });
    await expect(resumeDmsReprint('record', 'request')).rejects.toThrow('状态已变化');
    expect(mocks.open).not.toHaveBeenCalled();
  });
  it('先暂存关闭表单再打开预览，手动发送成功后才原生登记', async () => {
    await expect(submitDmsReprint(context)).resolves.toMatchObject({ code: 200, dmsReprintPrepared: true });
    expect(submit).not.toHaveBeenCalled();
    expect(data.biz_sign).toBe('original-sign');
    expect(data.dengji_ren_id).toBe('actor');
    expect(data.bupingheng_mingxi[0].yuanyin).toBe('original reason');
    expect(context.formContext.closeReprintForm.mock.invocationCallOrder[0]!).toBeLessThan(mocks.preview.mock.invocationCallOrder[0]!);
    expect(mocks.print).not.toHaveBeenCalled();
    expect(mocks.preview.mock.calls[0]![0]).toMatchObject({
      wenjianZhongxinId: 'version',
      printOptions: { copies: 1 },
      customBarcode: [{ barValue: 'original', barText: '打印流水号: original  计数流水号: 20260001' }],
    });
    await mocks.preview.mock.calls[0]![0].reprint.send();
    expect(mocks.print).toHaveBeenCalledTimes(1);
    expect(submit).toHaveBeenCalledTimes(1);
    expect(mocks.print.mock.invocationCallOrder[0]!).toBeLessThan(submit.mock.invocationCallOrder[0]!);
    expect(attempt.state).toBe('CLOSED');
  });
  it('打印成功登记失败保留确认状态，重试只补登记不再派发', async () => {
    submit.mockImplementation((_: any, reject: any) => reject(new Error('save failed')));
    await submitDmsReprint(context);
    const send = mocks.preview.mock.calls[0]![0].reprint.send;
    await expect(send()).rejects.toThrow('save failed');
    expect(attempt.state).toBe('CONFIRMED');
    expect(mocks.post.mock.calls.some(([r]: any[]) => r.url.endsWith('jieshu-fasong'))).toBe(false);
    submit.mockImplementation((resolve: any) => {
      attempt.documentId = 'doc';
      resolve({ code: 200 });
    });
    await send();
    expect(mocks.print).toHaveBeenCalledTimes(1);
    expect(attempt.state).toBe('CLOSED');
  });
  it('发送成功后原生保存响应丢失按唯一关联恢复，不重复创建', async () => {
    submit.mockImplementation((_: any, reject: any) => {
      attempt.documentId = 'doc';
      reject(new Error('network'));
    });
    await submitDmsReprint(context);
    await mocks.preview.mock.calls[0]![0].reprint.send();
    expect(submit).toHaveBeenCalledTimes(1);
    expect(attempt.state).toBe('CLOSED');
  });
  it('原生非200响应不得标记登记完成，保留已发送事实', async () => {
    submit.mockImplementation((resolve: any) => resolve({ code: 400, msg: 'save failed' }));
    await submitDmsReprint(context);
    await expect(mocks.preview.mock.calls[0]![0].reprint.send()).rejects.toThrow('save failed');
    expect(attempt.state).toBe('CONFIRMED');
    expect(mocks.emit).not.toHaveBeenCalledWith('DMS_REPRINT_COMPLETED', expect.anything());
  });
  it('预览打开失败保留技术任务，不生成不平衡登记', async () => {
    mocks.preview.mockRejectedValue(new Error('preview failed'));
    await expect(submitDmsReprint(context)).resolves.toMatchObject({ code: 200, dmsReprintPrepared: true, msg: expect.stringContaining('预览打开失败') });
    expect(submit).not.toHaveBeenCalled();
    expect(mocks.print).not.toHaveBeenCalled();
    expect(attempt.state).toBe('OPEN');
  });
  it('未发送就取消只结束技术任务，不创建登记', async () => {
    await submitDmsReprint(context);
    mocks.post.mockImplementation(async () => {
      attempt.state = 'CANCELLED';
      return { code: 200, data: attempt };
    });
    await cancelDmsReprint('record', 'request');
    await expect(mocks.preview.mock.calls[0]![0].reprint.send()).rejects.toThrow('已取消');
    expect(submit).not.toHaveBeenCalled();
    expect(mocks.print).not.toHaveBeenCalled();
  });
  it('已确认任务恢复表单后直接补登记，复用冻结签名且不打开预览', async () => {
    attempt.state = 'CONFIRMED';
    await expect(submitDmsReprint(context)).resolves.toMatchObject({ code: 200, data: { id: 'doc' } });
    expect(data.biz_sign).toBe('original-sign');
    expect(submit).toHaveBeenCalledTimes(1);
    expect(mocks.preview).not.toHaveBeenCalled();
    expect(mocks.print).not.toHaveBeenCalled();
  });
  it('迟到及并发成功回执只创建一次原生登记', async () => {
    let transport: any;
    mocks.print.mockImplementation(async (_: any, __: any, ___: any, t: any) => {
      transport = t;
      throw new Error('timeout');
    });
    await submitDmsReprint(context);
    await expect(mocks.preview.mock.calls[0]![0].reprint.send()).rejects.toThrow('timeout');
    expect(submit).not.toHaveBeenCalled();
    const b = { id: 'batch', state: 'SENDING' };
    await Promise.all([transport.submitted(attempt, b, 'token'), transport.submitted(attempt, b, 'token')]);
    expect(submit).toHaveBeenCalledTimes(1);
    expect(attempt.state).toBe('CLOSED');
  });
  it('暂存失败不关闭填写窗口，也不保存或打开预览', async () => {
    mocks.post.mockRejectedValue(new Error('prepare failed'));
    await expect(submitDmsReprint(context)).rejects.toThrow('prepare failed');
    expect(context.formContext.closeReprintForm).not.toHaveBeenCalled();
    expect(submit).not.toHaveBeenCalled();
    expect(mocks.preview).not.toHaveBeenCalled();
  });
  it('部分回传或结果未知时不登记，全部成功才登记', async () => {
    let transport: any;
    mocks.print.mockImplementation(async (_: any, __: any, ___: any, t: any) => {
      transport = t;
      throw new Error('unknown');
    });
    await submitDmsReprint(context);
    await expect(mocks.preview.mock.calls[0]![0].reprint.send()).rejects.toThrow('unknown');
    mocks.post.mockImplementation(async () => ({ code: 200, data: { ...attempt } }));
    await transport.submitted(attempt, { id: 'partial', state: 'SENDING' }, 'token');
    expect(submit).not.toHaveBeenCalled();
    attempt.state = 'CONFIRMED';
    mocks.post.mockImplementation(async ({ url }: any) => {
      if (url.endsWith('jieshu-fasong')) attempt.state = 'CLOSED';
      return { code: 200, data: { ...attempt } };
    });
    await transport.submitted(attempt, { id: 'final', state: 'SENDING' }, 'token');
    expect(submit).toHaveBeenCalledTimes(1);
    expect(attempt.state).toBe('CLOSED');
  });
  it('已有登记恢复直接打开原任务预览，不再次签名登记或自动打印', async () => {
    attempt.documentId = 'doc';
    mocks.get.mockResolvedValue({ code: 200, data: attempt });
    await resumeDmsReprint('record', 'request');
    expect(mocks.open).not.toHaveBeenCalled();
    expect(mocks.preview).toHaveBeenCalledTimes(1);
    expect(submit).not.toHaveBeenCalled();
    expect(mocks.print).not.toHaveBeenCalled();
  });
  it('同请求并发提交仅暂存并打开一次预览', async () => {
    const first = submitDmsReprint(context);
    const second = submitDmsReprint(context);
    expect(first).toBe(second);
    await first;
    expect(submit).not.toHaveBeenCalled();
    expect(mocks.preview).toHaveBeenCalledTimes(1);
  });
  it('无已保存登记时即使调用发送也不打印', async () => {
    await expect(sendDmsReprint(attempt)).rejects.toThrow('恢复原生登记上下文');
    expect(mocks.print).not.toHaveBeenCalled();
  });
  it('未知结果不能登记或结束任务', async () => {
    attempt.documentId = 'doc';
    mocks.print.mockRejectedValue(new Error('结果未知'));
    await expect(sendDmsReprint(attempt)).rejects.toThrow('结果未知');
    expect(submit).not.toHaveBeenCalled();
    expect(mocks.post.mock.calls.some(([r]: any[]) => r.url.endsWith('jieshu-fasong'))).toBe(false);
  });
  it('旧流程已有登记的迟到回传复用原单据', async () => {
    attempt.documentId = 'doc';
    let transport: any;
    mocks.print.mockImplementation(async (_: any, __: any, ___: any, t: any) => {
      transport = t;
      throw new Error('timeout');
    });
    await expect(sendDmsReprint(attempt)).rejects.toThrow('timeout');
    const b = { id: 'batch', items: [0], pages: [1], state: 'SENDING' };
    await transport.submitted(attempt, b, 'token');
    expect(mocks.emit).toHaveBeenCalledWith('DMS_REPRINT_COMPLETED', { printRecordId: 'record', requestId: 'request' });
    expect(submit).not.toHaveBeenCalled();
    expect(receiptStore.size).toBe(0);
  });
  it('发送成功但更新执行失败，重试读取确认状态后仅更新执行', async () => {
    attempt.documentId = 'doc';
    mocks.post.mockImplementation(async ({ url }: any) => {
      if (url.endsWith('jieshu-fasong')) throw new Error('finish failed');
      return { code: 200, data: { ...attempt } };
    });
    await expect(sendDmsReprint(attempt)).rejects.toThrow('finish failed');
    expect(attempt.state).toBe('CONFIRMED');
    // 真实传输层对CONFIRMED直接返回；模拟此契约，不派发新批次。
    mocks.print.mockImplementation(async (_: any, __: any, original: any) => {
      expect(original.state).toBe('CONFIRMED');
    });
    mocks.post.mockImplementation(async ({ url }: any) => {
      if (url.endsWith('jieshu-fasong')) attempt.state = 'CLOSED';
      return { code: 200, data: { ...attempt } };
    });
    await sendDmsReprint(attempt);
    expect(attempt.state).toBe('CLOSED');
    expect(submit).not.toHaveBeenCalled();
  });
});
describe('重打弹窗专属配置', () => {
  it('仅目标入口隐藏子表增删按钮并锁恢复原因', () => {
    const form = {
      fields: [
        {
          __vModel__: 'bupingheng_mingxi',
          columnBtnsList: [{ show: true }],
          footerBtnsList: [{ show: true }],
          __config__: { children: [{ __vModel__: 'cuoshi' }, { __vModel__: 'yuanyin' }] },
        },
      ],
    };
    configureReprintModal('other', form, { dmsReprint: true });
    expect(form.fields[0]!.columnBtnsList[0]!.show).toBe(true);
    configureReprintModal(DMS_REPRINT_MODEL_ID, form, { dmsReprint: true, recovering: true });
    expect(form.fields[0]!.columnBtnsList[0]!.show).toBe(false);
    expect(form.fields[0]!.footerBtnsList[0]!.show).toBe(false);
    expect((form.fields[0]!.__config__.children[1] as any).disabled).toBe(true);
  });
  it('仅已冻结任务恢复免再次签名，首次确认仍要求原生签名', () => {
    const form = { confirmBtnConfig: { biz_sign_enabled: true }, fields: [] };
    configureReprintModal(DMS_REPRINT_MODEL_ID, form, { dmsReprint: true });
    expect(form.confirmBtnConfig.biz_sign_enabled).toBe(true);
    configureReprintModal(DMS_REPRINT_MODEL_ID, form, { dmsReprint: true, recovering: true });
    expect(form.confirmBtnConfig.biz_sign_enabled).toBe(false);
  });
  it('恢复使用冻结签名作为原生保存审计关联，其他业务不变', () => {
    expect(reprintAuditSign(DMS_REPRINT_MODEL_ID, data, 'current-sign')).toBe('new-sign');
    expect(reprintAuditSign('other', data, 'current-sign')).toBe('current-sign');
  });
});
