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

import {
  appendRepeatRow,
  deleteRepeatRow,
  fetchFillData,
  FillDataError,
  parseFillData,
  previewSourceSync,
  restoreRepeatRow,
  synchronizeSource,
} from '../../src/api/record-fill';

describe('record fill api', () => {
  afterEach(() => vi.unstubAllGlobals());

  it('sends the opaque ticket in the Authorization header', async () => {
    const fetchMock = vi.fn().mockResolvedValue(new Response('{"fields":{}}', { status: 200 }));
    vi.stubGlobal('fetch', fetchMock);
    await fetchFillData({ apiBaseUrl: 'https://api.example.com', ticket: 'secret' });
    expect(fetchMock).toHaveBeenCalledWith('https://api.example.com/api/eln/onlyoffice/recordFill/data', {
      headers: { Authorization: 'Ticket secret' },
    });
  });

  it.each([
    [401, '填写凭证已过期，请重新打开文档'],
    [403, '无权读取填写数据'],
    [404, '填写数据不存在'],
    [500, '填写数据加载失败'],
  ])('maps HTTP %s', async (status, message) => {
    vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('', { status })));
    await expect(fetchFillData({ apiBaseUrl: 'https://api.example.com', ticket: 'secret' })).rejects.toEqual(new FillDataError(message, status));
  });

  it('normalizes null checkbox to false and validates field values', () => {
    expect(parseFillData({ fields: { 'eln.field.ok': {
      formulaMessage: '缺少输入', formulaStatus: 'MISSING_INPUT', required: true, type: 'checkbox', value: null,
    } } }).fields['eln.field.ok']).toEqual({
      formulaMessage: '缺少输入',
      formulaStatus: 'MISSING_INPUT',
      required: true,
      type: 'checkbox',
      value: false,
    });
    expect(() => parseFillData({ fields: { bad: { type: 'unknown', value: '' } } })).toThrow('填写数据格式无效');
  });

  it('parses checkbox groups for editable scalar and consistent repeat fields', () => {
    const parsed = parseFillData({
      fields: {
        'eln.field.comment': { type: 'text', value: '' },
        'eln.field.passed': {
          checkboxGroupCode: 'inspection_result', checkboxGroupMaxSelected: 2, checkboxGroupMinSelected: 1, type: 'checkbox', value: true,
        },
        'eln.repeat.results.row-1.qualified': {
          checkboxGroupCode: 'row_result', checkboxGroupMaxSelected: null, checkboxGroupMinSelected: 0, type: 'checkbox', value: true,
        },
        'eln.repeat.results.row-2.qualified': {
          checkboxGroupCode: 'row_result', checkboxGroupMaxSelected: null, checkboxGroupMinSelected: 0, type: 'checkbox', value: false,
        },
      },
    });

    expect(parsed.fields['eln.field.passed']?.checkboxGroupCode).toBe('inspection_result');
    expect(parsed.fields['eln.field.passed']?.checkboxGroupMinSelected).toBe(1);
    expect(parsed.fields['eln.field.passed']?.checkboxGroupMaxSelected).toBe(2);
    expect(parsed.fields['eln.repeat.results.row-1.qualified']?.checkboxGroupCode).toBe('row_result');
    expect(parsed.fields['eln.repeat.results.row-1.qualified']?.checkboxGroupMaxSelected).toBeNull();
    expect(parsed.fields['eln.field.comment']).not.toHaveProperty('checkboxGroupCode');
  });

  it.each([
    ['非法编码', { 'eln.field.passed': { checkboxGroupCode: '结果组', type: 'checkbox', value: true } }],
    ['非复选框', { 'eln.field.comment': { checkboxGroupCode: 'inspection_result', type: 'text', value: '' } }],
    ['只读复选框', { 'eln.field.source': { checkboxGroupCode: 'inspection_result', readOnly: true, type: 'checkbox', value: false } }],
    ['引用复选框', { 'eln.ref.passed.copy-1': { checkboxGroupCode: 'inspection_result', type: 'checkbox', value: false } }],
    ['重复列组不一致', {
      'eln.repeat.results.row-1.qualified': { checkboxGroupCode: 'row_result', type: 'checkbox', value: true },
      'eln.repeat.results.row-2.qualified': { checkboxGroupCode: 'other_result', type: 'checkbox', value: false },
    }],
    ['重复列部分缺组', {
      'eln.repeat.results.row-1.qualified': { checkboxGroupCode: 'row_result', type: 'checkbox', value: true },
      'eln.repeat.results.row-2.qualified': { type: 'checkbox', value: false },
    }],
    ['最少数为负数', {
      'eln.field.passed': { checkboxGroupCode: 'inspection_result', checkboxGroupMaxSelected: 2, checkboxGroupMinSelected: -1, type: 'checkbox', value: true },
    }],
    ['最多数为零', {
      'eln.field.passed': { checkboxGroupCode: 'inspection_result', checkboxGroupMaxSelected: 0, checkboxGroupMinSelected: 0, type: 'checkbox', value: true },
    }],
    ['最少数超过最多数', {
      'eln.field.passed': { checkboxGroupCode: 'inspection_result', checkboxGroupMaxSelected: 1, checkboxGroupMinSelected: 2, type: 'checkbox', value: true },
    }],
    ['未分组字段携带数量规则', {
      'eln.field.passed': { checkboxGroupMaxSelected: 2, checkboxGroupMinSelected: 0, type: 'checkbox', value: true },
    }],
    ['重复列数量规则不一致', {
      'eln.repeat.results.row-1.qualified': { checkboxGroupCode: 'row_result', checkboxGroupMaxSelected: 2, checkboxGroupMinSelected: 0, type: 'checkbox', value: true },
      'eln.repeat.results.row-2.qualified': { checkboxGroupCode: 'row_result', checkboxGroupMaxSelected: 3, checkboxGroupMinSelected: 0, type: 'checkbox', value: false },
    }],
  ])('rejects invalid checkbox group response: %s', (_caseName, fields) => {
    expect(() => parseFillData({ fields })).toThrow('填写数据格式无效');
  });

  it('parses readonly cross-template field metadata', () => {
    expect(parseFillData({
      fields: {
        'eln.field.source_result': {
          readOnly: true,
          type: 'text',
          value: '合格',
          valueSource: 'CROSS_TEMPLATE',
        },
      },
    }).fields['eln.field.source_result']).toEqual({
      readOnly: true,
      required: false,
      type: 'text',
      value: '合格',
      valueSource: 'CROSS_TEMPLATE',
    });
  });

  it('parses pending source synchronization from fill data', () => {
    const pendingSourceSync = {
      changedCount: 1, changedFieldCodes: ['source'], differences: [],
      operationCode: 'source-sync-1', recalculatedCount: 0,
      recordId: 'record-1', recordVersion: 5, revisionId: 'revision-1',
    };

    expect(parseFillData({ fields: {}, pendingSourceSync }).pendingSourceSync)
      .toEqual(pendingSourceSync);
  });

  it('parses repeat group limits and pending delete metadata', () => {
    const parsed = parseFillData({
      fields: { 'eln.repeat.results.row-1.value': { type: 'text', value: '待恢复' } },
      repeatGroups: [{
        activeRowCount: 1,
        checkboxGroups: { qualified: 'row_result' },
        groupCode: 'results',
        maxRows: 3,
        minRows: 1,
        rows: [{
          deleteOperationCode: 'delete-1',
          deleteReason: '录入错误',
          groupCode: 'results',
          rowId: 'row-1',
          rowOrder: 1,
          status: 'DELETE_PREPARED',
        }],
      }],
    });

    expect(parsed.repeatGroups[0]).toMatchObject({
      activeRowCount: 1,
      checkboxGroups: { qualified: 'row_result' },
      minRows: 1,
      maxRows: 3,
    });
    expect(parsed.repeatGroups[0]?.rows[0]).toMatchObject({
      deleteOperationCode: 'delete-1', deleteReason: '录入错误', status: 'DELETE_PREPARED',
    });
  });

  it('parses step structure and ignores legacy stage metadata', () => {
    const parsed = parseFillData({
      fields: {},
      repeatGroups: [],
      stepStructure: {
        configuration: {
          singleStep: false,
          stages: [{ id: 'stage-prepare', name: '准备阶段', order: 1 }],
          steps: [
            { id: 'step-prepare', stageId: 'stage-prepare', name: '样品准备', order: 1 },
            { id: 'step-test', name: '检验', order: 2 },
          ],
        },
        controlSteps: { 'eln.field.sample_name': 'step-prepare' },
        repeatGroupSteps: { results: 'step-test' },
      },
    });

    expect(parsed.stepStructure).toEqual({
      configuration: {
        singleStep: false,
        steps: [
          { id: 'step-prepare', name: '样品准备', order: 1 },
          { id: 'step-test', name: '检验', order: 2 },
        ],
      },
      controlSteps: { 'eln.field.sample_name': 'step-prepare' },
      repeatGroupSteps: { results: 'step-test' },
    });
  });

  it('submits repeat group and operation code with the opaque ticket', async () => {
    const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({
      groupCode: 'results', rowId: 'row-1', rowOrder: 1, status: 'PREPARED',
    }), { status: 200 }));
    vi.stubGlobal('fetch', fetchMock);

    await expect(appendRepeatRow(
      { apiBaseUrl: 'https://api.example.com', ticket: 'secret' },
      { groupCode: 'results', operationCode: 'operation-1' },
    )).resolves.toMatchObject({ rowId: 'row-1', status: 'PREPARED' });
    expect(fetchMock).toHaveBeenCalledWith('https://api.example.com/api/eln/onlyoffice/recordFill/repeatRows', {
      body: JSON.stringify({ groupCode: 'results', operationCode: 'operation-1' }),
      headers: { Authorization: 'Ticket secret', 'Content-Type': 'application/json' },
      method: 'POST',
    });
  });

  it('previews and confirms source sync with only the opaque fill ticket', async () => {
    const preview = {
      changedCount: 1,
      differences: [{
        bindingId: 'binding-source', changed: true, currentValue: 'GYDW', dataType: 'TEXT',
        fieldCode: 'source', fieldName: '来源', previousValue: null,
        sourceProperty: 'quality_standard.source', sourceType: 'LIMS_TASK',
      }],
      previewToken: 'preview-token', recordId: 'record-1', recordVersion: 4, revisionId: 'revision-1',
    };
    const synchronized = {
      changedCount: preview.changedCount,
      changedFieldCodes: ['source'],
      differences: preview.differences,
      operationCode: 'source-sync-1',
      recalculatedCount: 0,
      recordId: preview.recordId,
      recordVersion: preview.recordVersion,
      revisionId: preview.revisionId,
    };
    const fetchMock = vi.fn()
      .mockResolvedValueOnce(new Response(JSON.stringify(preview), { status: 200 }))
      .mockResolvedValueOnce(new Response(JSON.stringify(synchronized), { status: 200 }));
    vi.stubGlobal('fetch', fetchMock);
    const context = { apiBaseUrl: 'https://api.example.com', ticket: 'secret' };

    await expect(previewSourceSync(context)).resolves.toEqual(preview);
    await expect(synchronizeSource(context, {
      expectedRecordVersion: 4, operationCode: 'source-sync-1', previewToken: 'preview-token',
    })).resolves.toEqual(synchronized);

    expect(fetchMock).toHaveBeenNthCalledWith(1,
      'https://api.example.com/api/eln/onlyoffice/recordFill/sourceSync/preview', {
        headers: { Authorization: 'Ticket secret' }, method: 'POST',
      });
    expect(fetchMock).toHaveBeenNthCalledWith(2,
      'https://api.example.com/api/eln/onlyoffice/recordFill/sourceSync', {
        body: JSON.stringify({ expectedRecordVersion: 4, operationCode: 'source-sync-1', previewToken: 'preview-token' }),
        headers: { Authorization: 'Ticket secret', 'Content-Type': 'application/json' }, method: 'POST',
      });
  });

  it('parses cross-template source lineage in sync differences', async () => {
    const preview = {
      changedCount: 1,
      differences: [{
        bindingId: 'binding-source', changed: true, currentValue: '复测合格', dataType: 'TEXT',
        fieldCode: 'source_result', fieldName: '源检验结果', previousValue: '合格',
        sourceFieldVersionId: 'field-version-2', sourceFetchedAt: '2026-09-02T08:30:00Z',
        sourceProperty: null, sourceRecordId: 'source-record-1',
        sourceRevisionId: 'source-revision-2', sourceType: 'CROSS_TEMPLATE', sourceValueId: 'source-value-2',
      }],
      previewToken: 'preview-token', recordId: 'record-1', recordVersion: 4, revisionId: 'revision-1',
    };
    vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify(preview), { status: 200 })));

    await expect(previewSourceSync({ apiBaseUrl: 'https://api.example.com', ticket: 'secret' })).resolves.toEqual(preview);
  });

  it('registers and restores repeat row deletion with the same operation code', async () => {
    const response = {
      deleteOperationCode: 'delete-1', deleteReason: '录入错误', groupCode: 'results',
      rowId: 'row-1', rowOrder: 1, status: 'DELETE_PREPARED',
    };
    const fetchMock = vi.fn().mockImplementation(async () => new Response(JSON.stringify(response), { status: 200 }));
    vi.stubGlobal('fetch', fetchMock);
    const context = { apiBaseUrl: 'https://api.example.com', ticket: 'secret' };

    await deleteRepeatRow(context, {
      groupCode: 'results', operationCode: 'delete-1', reason: '录入错误', rowId: 'row-1',
    });
    await restoreRepeatRow(context, { groupCode: 'results', operationCode: 'delete-1', rowId: 'row-1' });

    expect(fetchMock).toHaveBeenNthCalledWith(1,
      'https://api.example.com/api/eln/onlyoffice/recordFill/repeatRows/delete', expect.objectContaining({ method: 'POST' }));
    expect(fetchMock).toHaveBeenNthCalledWith(2,
      'https://api.example.com/api/eln/onlyoffice/recordFill/repeatRows/restore', expect.objectContaining({ method: 'POST' }));
  });
});
