import { mount, type VueWrapper } from '@vue/test-utils';
import Antd from 'ant-design-vue';
import { createPinia } from 'pinia';
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';

import App from '../src/App.vue';
import type { OnlyOfficeBridge } from '../src/onlyoffice/bridge';
import type { OfficeContentControl } from '../src/onlyoffice/types';

beforeAll(() => {
  window.matchMedia ??= () =>
    ({
      addEventListener() {},
      addListener() {},
      dispatchEvent: () => false,
      matches: false,
      media: '',
      onchange: null,
      removeEventListener() {},
      removeListener() {},
    }) as MediaQueryList;
  globalThis.ResizeObserver ??= class ResizeObserver {
    disconnect() {}
    observe() {}
    unobserve() {}
  };
});

const wrappers: VueWrapper[] = [];
afterEach(() => wrappers.splice(0).forEach((wrapper) => wrapper.unmount()));

function deferred<T>() {
  let resolve!: (value: T) => void;
  const promise = new Promise<T>((done) => {
    resolve = done;
  });
  return { promise, resolve };
}

function createBridge(canAnnotate = true) {
  let lifecycle: Parameters<OnlyOfficeBridge['bindLifecycle']>[0] | undefined;
  const cleanup = vi.fn();
  const bridge: OnlyOfficeBridge = {
    bindLifecycle: vi.fn((callbacks) => {
      lifecycle = callbacks;
      return cleanup;
    }),
    close: vi.fn(),
    deleteControl: vi.fn(),
    disableTrackRevisions: vi.fn().mockResolvedValue(undefined),
    getRuntimeOptions: vi.fn(() => ({
      apiBaseUrl: 'http://localhost:30000',
      documentId: 'file-1',
      bizDataId: 'record-1',
      bizModule: 'eln.template',
      bizScene: 'eln.template.annotate',
      pluginVersion: '0.1.0',
      canAnnotate,
    })),
    insertField: vi.fn(),
    listControls: vi.fn(),
    selectControl: vi.fn(),
  };
  return { bridge, cleanup, triggerReady: () => lifecycle?.onReady() };
}

function mountApp(bridge: OnlyOfficeBridge | null) {
  const wrapper = mount(App, { global: { plugins: [createPinia(), Antd], provide: { onlyOfficeBridge: bridge } } });
  wrappers.push(wrapper);
  return wrapper;
}

describe('App', () => {
  it('初始显示版本、加载状态并禁用编辑', () => {
    const fixture = createBridge();
    const wrapper = mountApp(fixture.bridge);

    expect(wrapper.text()).not.toContain('ELN 模板标注');
    expect(wrapper.get('[data-test="plugin-version"]').text()).toBe('v0.1.0');
    expect(wrapper.get('[data-test="connection-status"]').text()).toBe('加载中');
    expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
  });

  it('SDK ready 后刷新列表并进入已连接状态', async () => {
    const fixture = createBridge();
    const controls = [{ Alias: '样品编号', InternalId: 'control-1', Tag: 'eln.field.sample_id' }];
    vi.mocked(fixture.bridge.listControls).mockResolvedValue(controls);
    const wrapper = mountApp(fixture.bridge);

    fixture.triggerReady();

    await vi.waitFor(() => expect(wrapper.get('[data-test="connection-status"]').text()).toBe('已连接'));
    expect(wrapper.get('[data-test="control-item"]').text()).toContain('样品编号');
  });

  it('只读上下文禁用插入但允许刷新文档字段', async () => {
    const fixture = createBridge(false);
    vi.mocked(fixture.bridge.listControls).mockResolvedValue([]);
    const wrapper = mountApp(fixture.bridge);

    fixture.triggerReady();

    await vi.waitFor(() => expect(wrapper.get('[data-test="connection-status"]').text()).toBe('已连接'));
    expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
    expect(wrapper.get('[data-test="refresh-controls"]').attributes('disabled')).toBeUndefined();
  });

  it('FieldEditor 提交经 bridge 插入并更新列表和成功状态', async () => {
    const fixture = createBridge();
    vi.mocked(fixture.bridge.listControls).mockResolvedValue([]);
    vi.mocked(fixture.bridge.insertField).mockImplementation(async (definition) => [{ Alias: definition.alias, InternalId: 'new-1', Tag: definition.tag }]);
    const wrapper = mountApp(fixture.bridge);
    fixture.triggerReady();
    await vi.waitFor(() => expect(wrapper.get('[data-test="connection-status"]').text()).toBe('已连接'));

    await wrapper.get('[data-test="submit-field"]').trigger('click');

    await vi.waitFor(() => expect(fixture.bridge.insertField).toHaveBeenCalledOnce());
    expect(wrapper.get('[data-test="control-item"]').text()).toContain('样品编号');
    expect(wrapper.text()).toContain('已创建字段：样品编号');
  });

  it('刷新和定位事件分别调用 bridge', async () => {
    const fixture = createBridge();
    const controls: OfficeContentControl[] = [{ Alias: '结果', InternalId: 'control-1', Tag: 'eln.field.result' }];
    vi.mocked(fixture.bridge.listControls).mockResolvedValue(controls);
    vi.mocked(fixture.bridge.selectControl).mockResolvedValue();
    const wrapper = mountApp(fixture.bridge);
    fixture.triggerReady();
    await vi.waitFor(() => expect(wrapper.find('[data-test="control-item"]').exists()).toBe(true));

    await wrapper.get('[data-test="refresh-controls"]').trigger('click');
    await vi.waitFor(() => expect(fixture.bridge.listControls).toHaveBeenCalledTimes(2));
    await wrapper.get('[data-test="control-item"]').trigger('click');
    await vi.waitFor(() => expect(fixture.bridge.selectControl).toHaveBeenCalledWith('control-1'));
  });

  it('定位字段期间保持页面已连接且不禁用其他内容', async () => {
    const fixture = createBridge();
    const controls: OfficeContentControl[] = [{ Alias: '结果', InternalId: 'control-1', Tag: 'eln.field.result' }];
    const pending = deferred<void>();
    vi.mocked(fixture.bridge.listControls).mockResolvedValue(controls);
    vi.mocked(fixture.bridge.selectControl).mockReturnValue(pending.promise);
    const wrapper = mountApp(fixture.bridge);
    fixture.triggerReady();
    await vi.waitFor(() => expect(wrapper.find('[data-test="control-item"]').exists()).toBe(true));

    await wrapper.get('[data-test="control-item"]').trigger('click');

    expect(wrapper.get('[data-test="connection-status"]').text()).toBe('已连接');
    expect(wrapper.find('[data-test="runtime-message"]').exists()).toBe(false);
    expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeUndefined();
    expect(wrapper.get('[data-test="control-item"]').attributes('disabled')).toBeUndefined();

    pending.resolve();
    await vi.waitFor(() => expect(fixture.bridge.selectControl).toHaveBeenCalledWith('control-1'));
  });

  it('确认解除标注后调用 bridge 并更新列表', async () => {
    const fixture = createBridge();
    const controls: OfficeContentControl[] = [{ Alias: '结果', InternalId: 'control-1', Tag: 'eln.field.result' }];
    vi.mocked(fixture.bridge.listControls).mockResolvedValue(controls);
    vi.mocked(fixture.bridge.deleteControl).mockResolvedValue([]);
    const wrapper = mountApp(fixture.bridge);
    fixture.triggerReady();
    await vi.waitFor(() => expect(wrapper.find('[data-test="delete-control"]').exists()).toBe(true));

    wrapper.getComponent({ name: 'APopconfirm' }).vm.$emit('confirm');

    await vi.waitFor(() => expect(fixture.bridge.deleteControl).toHaveBeenCalledWith('control-1'));
    await vi.waitFor(() => expect(wrapper.text()).toContain('标注已解除'));
    expect(wrapper.find('[data-test="control-item"]').exists()).toBe(false);
  });

  it('working 时禁用编辑、刷新与定位', async () => {
    const fixture = createBridge();
    const pending = deferred<OfficeContentControl[]>();
    vi.mocked(fixture.bridge.listControls).mockReturnValue(pending.promise);
    const wrapper = mountApp(fixture.bridge);
    fixture.triggerReady();

    await vi.waitFor(() => expect(wrapper.text()).toContain('正在读取文档字段...'));
    expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
    expect(wrapper.get('[data-test="refresh-controls"]').attributes('disabled')).toBeDefined();
    pending.resolve([]);
  });

  it('异步错误显示 Alert 且事件处理不产生未处理拒绝', async () => {
    const fixture = createBridge();
    vi.mocked(fixture.bridge.listControls).mockResolvedValueOnce([]).mockRejectedValueOnce(new Error('刷新失败'));
    const wrapper = mountApp(fixture.bridge);
    fixture.triggerReady();
    await vi.waitFor(() => expect(wrapper.get('[data-test="connection-status"]').text()).toBe('已连接'));

    await wrapper.get('[data-test="refresh-controls"]').trigger('click');

    await vi.waitFor(() => expect(wrapper.text()).toContain('读取内容控件失败：刷新失败'));
    expect(wrapper.find('.ant-alert-error').exists()).toBe(true);
  });

  it('初始化失败后的重试再次失败时保持 fatal 并继续禁用编辑', async () => {
    const fixture = createBridge();
    vi.mocked(fixture.bridge.listControls).mockRejectedValueOnce(new Error('SDK 初始化失败')).mockRejectedValueOnce(new Error('SDK 仍不可用'));
    const wrapper = mountApp(fixture.bridge);
    fixture.triggerReady();
    await vi.waitFor(() => expect(wrapper.get('[data-test="connection-status"]').text()).toBe('连接异常'));

    expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
    expect(wrapper.text()).toContain('读取内容控件失败：SDK 初始化失败');
    await wrapper.get('[data-test="refresh-controls"]').trigger('click');

    await vi.waitFor(() => expect(fixture.bridge.listControls).toHaveBeenCalledTimes(2));
    await vi.waitFor(() => expect(wrapper.text()).toContain('读取内容控件失败：SDK 仍不可用'));
    expect(wrapper.get('[data-test="connection-status"]').text()).toBe('连接异常');
    expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
  });

  it('卸载时清理 lifecycle', () => {
    const fixture = createBridge();
    const wrapper = mountApp(fixture.bridge);

    wrapper.unmount();

    expect(fixture.cleanup).toHaveBeenCalledOnce();
  });

  it('SDK 缺失时显示 fatal、禁用操作且不绑定 lifecycle', () => {
    const wrapper = mountApp(null);

    expect(wrapper.text()).toContain('ONLYOFFICE SDK 未加载，无法初始化插件');
    expect(wrapper.get('[data-test="submit-field"]').attributes('disabled')).toBeDefined();
    expect(wrapper.get('[data-test="refresh-controls"]').attributes('disabled')).toBeDefined();
  });
});
