import type { OnlyOfficeBridge } from '../src/onlyoffice/bridge';

import { mount } from '@vue/test-utils';
import Antd, { Checkbox, Input, Modal } from 'ant-design-vue';
import { createPinia } from 'pinia';
import { afterEach, expect, it, vi } from 'vitest';

import App from '../src/App.vue';

afterEach(() => {
  vi.restoreAllMocks();
  vi.unstubAllGlobals();
});

it('reapplies a pending empty clear on reopening without requesting another synchronization', async () => {
  const fetch = vi.fn(async () => new Response(JSON.stringify({
    fields: { 'eln.field.sample': { type: 'text', value: null } }, repeatGroups: [],
    pendingSourceSync: {
      changedCount: 1, changedFieldCodes: ['sample'], differences: [], operationCode: 'source-sync-pending',
      recalculatedCount: 0, recordId: 'record-1', recordVersion: 5, revisionId: 'revision-1',
    },
  }), { status: 200 }));
  vi.stubGlobal('fetch', fetch);
  let ready = () => {};
  const bridge: OnlyOfficeBridge = {
    applyFields: vi.fn(async (changes: Parameters<OnlyOfficeBridge['applyFields']>[0]) => changes.map(({ tag }) => ({ ok: true, tag }))),
    appendRepeatRow: vi.fn().mockResolvedValue([]),
    bindLifecycle: (callbacks) => { ready = callbacks.onReady; return () => {}; },
    getRuntimeOptions: () => ({
      apiBaseUrl: 'http://localhost:30000', bizDataId: 'record-1', bizModule: 'eln.record',
      bizScene: 'eln.record.fill', canFill: true, documentId: 'file-1', pluginVersion: '0.1.0',
      scope: 'onlyoffice:eln-record-fill', ticket: 'test-ticket',
    }),
    inspectFields: vi.fn().mockResolvedValue([
      { alias: '供试品名称', currentValue: '', internalId: '1', options: [], tag: 'eln.field.sample', type: 'text' },
    ]),
    removeRepeatRow: vi.fn().mockResolvedValue([]), selectControl: vi.fn().mockResolvedValue(undefined),
    setFillOnlyMode: vi.fn().mockResolvedValue(undefined),
  };
  const wrapper = mount(App, { global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: bridge } } });
  try {
    ready();
    await vi.waitFor(() => expect(wrapper.get('[data-test="connection-status"]').text()).toContain('已连接'));
    expect(bridge.applyFields).toHaveBeenCalledWith([{ tag: 'eln.field.sample', type: 'text', value: '' }]);
    expect(fetch).toHaveBeenCalledTimes(1);
    expect(fetch).toHaveBeenCalledWith(expect.stringMatching(/\/recordFill\/data$/u), expect.anything());
  } finally {
    wrapper.unmount();
  }
});

it.each([
  ['MISSING', '来源缺失，保留原快照', 'Retained sample'],
  ['NOT_APPLICABLE', '来源不适用，保留原快照', 'Retained sample'],
  ['MISSING', '来源缺失，保留原快照', ''],
])('retains %s (%s, previous "%s") and clears only after explicit selection with a reason', async (outcome, message, previousValue) => {
  let cleared = false;
  let confirmationAttempts = 0;
  const fetch = vi.fn(async (url: string, _options?: RequestInit) => {
    if (url.endsWith('/sourceSync')) {
      if (++confirmationAttempts === 1) return new Response(JSON.stringify({ msg: 'temporary failure' }), { status: 503 });
      cleared = true;
      return new Response(JSON.stringify({
        changedCount: 1, changedFieldCodes: ['sample'], differences: [], operationCode: 'source-sync-1',
        recalculatedCount: 0, recordId: 'record-1', recordVersion: 5, revisionId: 'revision-1',
      }), { status: 200 });
    }
    return new Response(JSON.stringify(url.endsWith('/sourceSync/preview') ? {
    changedCount: 0,
    differences: [{
      bindingId: 'binding-sample', changed: false, currentValue: previousValue, dataType: 'TEXT',
      fieldCode: 'sample', fieldName: '供试品名称', previousValue,
      sourceProperty: 'sample.name', sourcePropertyRevision: 1, sourceType: 'LIMS_TASK',
      sourceOutcome: outcome, canClear: true,
      previousFetchedTime: '2026-09-01T08:00:00Z', sourceFetchedAt: '2026-09-06T08:00:00Z',
    }],
    previewToken: 'preview-token', recordId: 'record-1', recordVersion: 4, revisionId: 'revision-1',
  } : { fields: { 'eln.field.sample': { type: 'text', value: cleared ? null : previousValue } }, repeatGroups: [] }), { status: 200 });
  });
  vi.stubGlobal('fetch', fetch);
  let ready = () => {};
  const bridge: OnlyOfficeBridge = {
    applyFields: vi.fn(async (changes: Parameters<OnlyOfficeBridge['applyFields']>[0]) => changes.map(({ tag }) => ({ ok: true, tag }))), appendRepeatRow: vi.fn().mockResolvedValue([]),
    bindLifecycle: (callbacks) => { ready = callbacks.onReady; return () => {}; },
    getRuntimeOptions: () => ({
      apiBaseUrl: 'http://localhost:30000', bizDataId: 'record-1', bizModule: 'eln.record',
      bizScene: 'eln.record.fill', canFill: true, documentId: 'file-1', pluginVersion: '0.1.0',
      scope: 'onlyoffice:eln-record-fill', ticket: 'test-ticket',
    }),
    inspectFields: vi.fn().mockResolvedValue([
      { alias: '供试品名称', currentValue: previousValue, internalId: '1', options: [], tag: 'eln.field.sample', type: 'text' },
    ]), removeRepeatRow: vi.fn().mockResolvedValue([]),
    selectControl: vi.fn().mockResolvedValue(undefined), setFillOnlyMode: vi.fn().mockResolvedValue(undefined),
  };
  const wrapper = mount(App, { global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: bridge } } });
  try {
    ready();
    await vi.waitFor(() => expect(wrapper.get('[data-test="connection-status"]').text()).toContain('已连接'));
    await wrapper.get('[aria-label="重新同步来源快照"]').trigger('click');
    await vi.waitFor(() => expect(document.body.textContent).toContain(message));
    const differences = document.body.querySelector('[data-test="source-sync-differences"]');
    expect(differences?.textContent).toContain(previousValue || '-');
    expect(differences?.textContent).toContain('sample.name@1');
    expect(differences?.textContent).toContain('2026-09-01T08:00:00Z');
    expect(document.body.querySelector('[data-test="source-sync-empty"]')).toBeNull();
    expect(fetch.mock.calls.some(([url]) => url.endsWith('/sourceSync'))).toBe(false);
    wrapper.getComponent(Modal).vm.$emit('cancel');
    await wrapper.vm.$nextTick();
    await wrapper.get('[aria-label="重新同步来源快照"]').trigger('click');
    await vi.waitFor(() => expect(wrapper.getComponent(Modal).props('open')).toBe(true));
    const modal = wrapper.getComponent(Modal);
    const clear = wrapper.getComponent(Checkbox);
    expect(clear.props('checked')).toBe(false);
    clear.vm.$emit('update:checked', true);
    await wrapper.vm.$nextTick();
    expect(modal.props('okButtonProps')).toMatchObject({ disabled: true });
    const reason = wrapper.getComponent(Input.TextArea);
    reason.vm.$emit('update:value', '   ');
    await wrapper.vm.$nextTick();
    expect(modal.props('okButtonProps')).toMatchObject({ disabled: true });
    reason.vm.$emit('update:value', 'x'.repeat(501));
    await wrapper.vm.$nextTick();
    expect(modal.props('okButtonProps')).toMatchObject({ disabled: true });
    reason.vm.$emit('update:value', '  Confirm source no longer applicable  ');
    await wrapper.vm.$nextTick();
    expect(modal.props('okButtonProps')).toMatchObject({ disabled: false });
    modal.vm.$emit('ok');
    await vi.waitFor(() => expect(confirmationAttempts).toBe(1));
    await vi.waitFor(() => expect(modal.props('confirmLoading')).toBe(false));
    expect(modal.props('open')).toBe(true);
    expect(reason.props('value')).toBe('  Confirm source no longer applicable  ');
    modal.vm.$emit('ok');
    await vi.waitFor(() => expect(cleared).toBe(true));
    const request = fetch.mock.calls.find(([url]) => url.endsWith('/sourceSync'));
    expect(JSON.parse(String(request?.[1]?.body))).toMatchObject({
      expectedRecordVersion: 4, previewToken: 'preview-token',
      clearReasons: { 'binding-sample': 'Confirm source no longer applicable' },
    });
    const confirmations = fetch.mock.calls.filter(([url]) => url.endsWith('/sourceSync'));
    expect(confirmations[1]?.[1]?.body).toBe(confirmations[0]?.[1]?.body);
    await vi.waitFor(() => expect(bridge.applyFields).toHaveBeenCalledWith([{ tag: 'eln.field.sample', type: 'text', value: '' }]));
  } finally {
    wrapper.unmount();
  }
});
