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

const mocks = vi.hoisted(() => {
  const app = { mount: vi.fn(), provide: vi.fn(), use: vi.fn() };
  app.use.mockReturnValue(app);
  app.provide.mockReturnValue(app);
  return { antd: {}, app, bridge: {}, createApp: vi.fn(() => app), createBridge: vi.fn(), createPinia: vi.fn(() => ({ pinia: true })) };
});

vi.mock('vue', async (importOriginal) => ({ ...(await importOriginal<typeof import('vue')>()), createApp: mocks.createApp }));
vi.mock('pinia', async (importOriginal) => ({ ...(await importOriginal<typeof import('pinia')>()), createPinia: mocks.createPinia }));
vi.mock('ant-design-vue', () => ({ default: mocks.antd }));
vi.mock('../src/onlyoffice/bridge', () => ({ createOnlyOfficeBridge: mocks.createBridge }));

describe('main', () => {
  const validPlugin = () => ({
    callCommand: vi.fn(),
    executeCommand: vi.fn(),
    executeMethod: vi.fn(),
    onThemeChangedBase: vi.fn(),
  });

  beforeEach(() => {
    vi.resetModules();
    vi.clearAllMocks();
    mocks.app.use.mockReturnValue(mocks.app);
    mocks.app.provide.mockReturnValue(mocks.app);
    delete window.Asc;
  });

  it('创建 bridge、安装依赖、注入后挂载', async () => {
    const asc = { plugin: validPlugin(), scope: {} };
    window.Asc = asc as never;
    mocks.createBridge.mockReturnValue(mocks.bridge);

    await import('../src/main');
    const { default: App } = await import('../src/App.vue');

    expect(mocks.createApp).toHaveBeenCalledWith(App);
    expect(mocks.createBridge).toHaveBeenCalledWith(asc);
    expect(mocks.app.use.mock.calls).toEqual([[{ pinia: true }], [mocks.antd]]);
    expect(mocks.app.provide).toHaveBeenCalledWith('onlyOfficeBridge', mocks.bridge);
    expect(mocks.app.mount).toHaveBeenCalledWith('#app');
  });

  it('Asc 缺失时注入 null 且仍然挂载', async () => {
    await import('../src/main');

    expect(mocks.createBridge).not.toHaveBeenCalled();
    expect(mocks.app.provide).toHaveBeenCalledWith('onlyOfficeBridge', null);
    expect(mocks.app.mount).toHaveBeenCalledWith('#app');
  });

  it('SDK 握手前 plugin 方法尚未挂载时仍创建 bridge', async () => {
    const asc = { plugin: { attachEvent: vi.fn() }, scope: {} };
    window.Asc = asc as never;
    mocks.createBridge.mockReturnValue(mocks.bridge);

    await import('../src/main');

    expect(mocks.createBridge).toHaveBeenCalledWith(asc);
    expect(mocks.app.provide).toHaveBeenCalledWith('onlyOfficeBridge', mocks.bridge);
    expect(mocks.app.mount).toHaveBeenCalledWith('#app');
  });

  it('没有 SDK bootstrap 特征的空 plugin 注入 null', async () => {
    window.Asc = { plugin: {}, scope: {} } as never;

    await import('../src/main');

    expect(mocks.createBridge).not.toHaveBeenCalled();
    expect(mocks.app.provide).toHaveBeenCalledWith('onlyOfficeBridge', null);
  });

  it.each(['callCommand', 'executeMethod', 'executeCommand', 'onThemeChangedBase'] as const)(
    'Asc.plugin.%s 已存在但不是函数时不得创建 bridge',
    async (method) => {
      const plugin: Record<string, unknown> = validPlugin();
      plugin[method] = 'invalid';
      window.Asc = { plugin, scope: {} } as never;

      await import('../src/main');

      expect(mocks.createBridge).not.toHaveBeenCalled();
      expect(mocks.app.provide).toHaveBeenCalledWith('onlyOfficeBridge', null);
      expect(mocks.app.mount).toHaveBeenCalledWith('#app');
    },
  );
});
