import { readFileSync } from 'node:fs';

import { unref } from 'vue';

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

import { createUploadEditCoordinator } from '#/components/Jnpf/upload/src/uploadEditCoordinator';

// Run the actual SFC handlers together, without mounting unrelated designer controls.
function loadHandlers(path: string, names: string[], indent: string, dependencies: Record<string, any>) {
  const source = readFileSync(path, 'utf8');
  const functions = names.map((name) => {
    const match = source.match(new RegExp(`${indent}(?:async )?function ${name}\\([^]*?\\n${indent}\\}`));
    if (!match) throw new Error(`Missing handler ${name}`);
    return match[0];
  });
  const code = ts.transpileModule(`const { ${Object.keys(dependencies).join(', ')} } = deps;\n${functions.join('\n')}\nreturn { ${names.join(', ')} };`, {
    compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.None },
  }).outputText;
  return new Function('deps', code)(dependencies);
}

function harness() {
  const rows = [{ id: 'existing', name: '' }, { name: 'unfinished' }];
  const tableState = {
    dataSource: rows,
    tableFormData: rows.map(() => [{ __vModel__: 'name', required: true, regList: [], config: { __config__: {} }, valid: true }]),
  };
  const checkRegData = vi.fn(() => true);
  const table = loadHandlers(
    'apps/jnpf-web-apps-main/src/components/Jnpf/InputTable/src/InputTable.vue',
    ['submit', 'getDataSource', 'checkData', 'clearCellValidation'],
    '',
    { props: { config: {} }, state: tableState, checkRegData },
  );
  const state = { formData: { title: '', items: [] }, tableRefs: { items: { tableRef: table } } };
  const validate = vi.fn().mockRejectedValue(new Error('title is required'));
  const prepare = vi.fn().mockResolvedValue({ draftId: 'file-draft', revision: 3 });
  const beforeSubmit = vi.fn();
  const confirmSign = vi.fn().mockResolvedValue({ approved: true, bizSign: 'signature' });
  const submit = vi.fn();
  const toast = vi.fn();
  const claimDisplayedBillNumbers = vi.fn().mockResolvedValue(true);
  const parser = loadHandlers(
    'apps/jnpf-web-apps-main/src/components/FormGenerator/src/components/Parser.vue',
    ['checkTableData', 'handleValidate', 'handleSubmit'],
    '    ',
    {
      state,
      props: { requireReview: false, deferBillNumberClaim: true },
      unref,
      isTableValid: { value: false },
      formElRef: { value: { validate } },
      isUnreachedControlledStepField: () => false,
      isVirtualTable: () => false,
      getFieldByVModel: () => ({}),
      isReviewRequired: () => false,
      reviewPassed: { value: true },
      uploadEditors: new Set([{ prepare }]),
      uploadCoordinator: createUploadEditCoordinator(vi.fn()),
      onlineUtils: { toast },
      claimDisplayedBillNumbers,
      beforeSubmit,
      confirmSign,
      submit,
    },
  );
  return { ...parser, state, rows, tableState, checkRegData, validate, prepare, beforeSubmit, confirmSign, submit, toast };
}

describe('workflow draft collection', () => {
  it('saves incomplete main and child fields, retaining current rows and prepared attachments', async () => {
    const h = harness();
    // Include edits and a new row beyond the initially loaded data.
    h.rows[1]!.name = 'latest edit';
    h.rows.push({ name: '' });

    expect(await h.handleSubmit(true)).toBe(true);

    expect(h.state.formData.items).toEqual(h.rows);
    expect(h.state.formData).toHaveProperty('__jnpfUploadEdit', [{ draftId: 'file-draft', revision: 3 }]);
    expect(h.validate).not.toHaveBeenCalled();
    expect(h.checkRegData).not.toHaveBeenCalled();
    expect(h.beforeSubmit).not.toHaveBeenCalled();
    expect(h.confirmSign).not.toHaveBeenCalled();
    expect(h.submit).toHaveBeenCalledOnce();
  });

  it('still rejects missing required child values on formal submission after a successful draft', async () => {
    const h = harness();
    await h.handleSubmit(true);
    h.submit.mockClear();

    expect(await h.handleSubmit()).toBe(false);

    expect(h.tableState.tableFormData[0]![0]!.valid).toBe(false);
    expect(h.submit).not.toHaveBeenCalled();
  });

  it('still rejects missing required main values on formal submission', async () => {
    const h = harness();
    h.rows.forEach((row) => (row.name = 'complete child'));

    expect(await h.handleSubmit()).toBe(false);

    expect(h.validate).toHaveBeenCalledOnce();
    expect(h.beforeSubmit).not.toHaveBeenCalled();
    expect(h.submit).not.toHaveBeenCalled();
  });

  it('defers child business rules until formal submission', async () => {
    const h = harness();
    h.rows.forEach((row) => (row.name = 'invalid business value'));
    h.tableState.tableFormData[0]![0]!.regList = [{}] as any;
    h.checkRegData.mockReturnValue(false);

    expect(await h.handleSubmit(true)).toBe(true);
    expect(h.checkRegData).not.toHaveBeenCalled();
    h.submit.mockClear();

    expect(await h.handleSubmit()).toBe(false);
    expect(h.checkRegData).toHaveBeenCalledOnce();
    expect(h.submit).not.toHaveBeenCalled();
  });

  it.each([true, false])('blocks saving when attachment preparation fails (draft=%s)', async (isSave) => {
    const h = harness();
    h.prepare.mockRejectedValue(new Error('附件仍在编辑'));

    expect(await h.handleSubmit(isSave)).toBe(false);

    expect(h.toast).toHaveBeenCalledWith('附件仍在编辑');
    expect(h.submit).not.toHaveBeenCalled();
  });

  it('preserves validation, pre-submit, approval check and signature for formal submission', async () => {
    const h = harness();
    h.rows.forEach((row) => (row.name = 'complete'));
    h.validate.mockResolvedValue({ title: 'complete' });
    const approvalCheck = vi.fn(async () => true);

    expect(await h.handleSubmit(false, approvalCheck)).toBe(true);

    expect(h.validate).toHaveBeenCalledOnce();
    expect(h.beforeSubmit).toHaveBeenCalledOnce();
    expect(approvalCheck).toHaveBeenCalledOnce();
    expect(h.confirmSign).toHaveBeenCalledOnce();
    expect(h.submit).toHaveBeenCalledExactlyOnceWith('signature');
  });
});
