import type { HmrContext, Plugin, ViteDevServer } from 'vite';

import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import process from 'node:process';

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

import { createDevelopmentManifest, onlyOfficeDevelopmentPlugin, resolveOnlyOfficeDevOptions } from '../../vite/onlyoffice-plugin';

const now = new Date('2026-08-11T01:02:03Z');
const manifestPath = resolve(process.cwd(), 'config.json');

describe('onlyoffice 开发配置', () => {
  it('接受独立 HMR path', () => {
    expect(resolveOnlyOfficeDevOptions({ ONLYOFFICE_PLUGIN_HMR_PATH: '/__hmr/eln-template-annotator' }, now).hmrPath).toBe('/__hmr/eln-template-annotator');
  });

  it.each(['__hmr/plugin', '/', '/bad path', '/bad?query'])('拒绝非法 HMR path %s', (hmrPath) => {
    expect(() => resolveOnlyOfficeDevOptions({ ONLYOFFICE_PLUGIN_HMR_PATH: hmrPath }, now)).toThrow('ONLYOFFICE_PLUGIN_HMR_PATH');
  });

  it('生成保持 0.1.0 的默认开发配置', () => {
    const options = resolveOnlyOfficeDevOptions({}, now);

    expect(options).toEqual({
      basePath: '/onlyoffice-plugins/0.1.0-dev/eln-template-annotator',
      docsUrl: 'http://localhost:20000',
      hmrClientPort: 4173,
      hmrHost: 'localhost',
      hmrProtocol: 'ws',
      host: '0.0.0.0',
      manifestUrl: 'http://localhost:4173/onlyoffice-plugins/0.1.0-dev/eln-template-annotator/config.json?v=20260811T010203',
      port: 4173,
      publicHost: 'localhost',
      release: '0.1.0-dev',
      startupId: '20260811T010203',
    });
  });

  it('解析服务端环境变量并由 HMR 协议生成公开 URL', () => {
    const options = resolveOnlyOfficeDevOptions(
      {
        ONLYOFFICE_DOCS_URL: 'https://docs.example.com/office///',
        ONLYOFFICE_PLUGIN_DEV_HOST: '127.0.0.1',
        ONLYOFFICE_PLUGIN_DEV_PORT: '5174',
        ONLYOFFICE_PLUGIN_HMR_CLIENT_PORT: '443',
        ONLYOFFICE_PLUGIN_HMR_HOST: 'hmr.example.com',
        ONLYOFFICE_PLUGIN_HMR_PROTOCOL: 'wss',
        ONLYOFFICE_PLUGIN_PUBLIC_HOST: 'plugins.example.com',
      },
      now,
    );

    expect(options).toMatchObject({
      docsUrl: 'https://docs.example.com/office',
      hmrClientPort: 443,
      hmrHost: 'hmr.example.com',
      hmrProtocol: 'wss',
      host: '127.0.0.1',
      manifestUrl: 'https://plugins.example.com:5174/onlyoffice-plugins/0.1.0-dev/eln-template-annotator/config.json?v=20260811T010203',
      port: 5174,
      publicHost: 'plugins.example.com',
    });
  });

  it('让 HMR host 和 client port 默认跟随公开 host 与开发端口', () => {
    const options = resolveOnlyOfficeDevOptions(
      {
        ONLYOFFICE_PLUGIN_DEV_PORT: '5174',
        ONLYOFFICE_PLUGIN_PUBLIC_HOST: 'plugins.example.com',
      },
      now,
    );

    expect(options.hmrHost).toBe('plugins.example.com');
    expect(options.hmrClientPort).toBe(5174);
  });

  it('使用 UTC 生成固定长度的启动标识并支持 bracket IPv6 公开 host', () => {
    const options = resolveOnlyOfficeDevOptions({ ONLYOFFICE_PLUGIN_PUBLIC_HOST: '[2001:db8::1]' }, new Date('2026-01-02T03:04:05+08:00'));

    expect(options.startupId).toBe('20260101T190405');
    expect(options.manifestUrl).toBe('http://[2001:db8::1]:4173/onlyoffice-plugins/0.1.0-dev/eln-template-annotator/config.json?v=20260101T190405');
  });

  it('监听 host 使用裸 IPv6，URL host 使用 bracket IPv6', () => {
    const options = resolveOnlyOfficeDevOptions(
      {
        ONLYOFFICE_PLUGIN_DEV_HOST: '::1',
        ONLYOFFICE_PLUGIN_HMR_HOST: '[2001:db8::2]',
        ONLYOFFICE_PLUGIN_PUBLIC_HOST: '[2001:db8::1]',
      },
      now,
    );

    expect(options.host).toBe('::1');
    expect(options.hmrHost).toBe('[2001:db8::2]');
    expect(options.publicHost).toBe('[2001:db8::1]');
  });

  it('拒绝带方括号的监听 IPv6 host', () => {
    expect(() => resolveOnlyOfficeDevOptions({ ONLYOFFICE_PLUGIN_DEV_HOST: '[::1]' }, now)).toThrow('ONLYOFFICE_PLUGIN_DEV_HOST');
  });

  it.each([
    ['ONLYOFFICE_PLUGIN_DEV_PORT', '0'],
    ['ONLYOFFICE_PLUGIN_DEV_PORT', '65536'],
    ['ONLYOFFICE_PLUGIN_DEV_PORT', '1.5'],
    ['ONLYOFFICE_PLUGIN_DEV_PORT', ' 4173'],
    ['ONLYOFFICE_PLUGIN_HMR_CLIENT_PORT', ''],
    ['ONLYOFFICE_PLUGIN_HMR_CLIENT_PORT', '01'],
  ])('拒绝非法端口 %s=%j', (name, value) => {
    expect(() => resolveOnlyOfficeDevOptions({ [name]: value }, now)).toThrow(name);
  });

  it.each(['file', 'http', 'WS', ''])('拒绝非法 HMR 协议 %j', (protocol) => {
    expect(() => resolveOnlyOfficeDevOptions({ ONLYOFFICE_PLUGIN_HMR_PROTOCOL: protocol }, now)).toThrow('ONLYOFFICE_PLUGIN_HMR_PROTOCOL');
  });

  it.each([
    ['ONLYOFFICE_PLUGIN_PUBLIC_HOST', 'http://localhost'],
    ['ONLYOFFICE_PLUGIN_PUBLIC_HOST', 'localhost/path'],
    ['ONLYOFFICE_PLUGIN_PUBLIC_HOST', 'local host'],
    ['ONLYOFFICE_PLUGIN_PUBLIC_HOST', 'localhost:4173'],
    ['ONLYOFFICE_PLUGIN_PUBLIC_HOST', '2001:db8::1'],
    ['ONLYOFFICE_PLUGIN_HMR_HOST', 'hmr.example.com/path'],
    ['ONLYOFFICE_PLUGIN_DEV_HOST', '127.0.0.1\ninvalid'],
  ])('拒绝非法 host %s=%j', (name, value) => {
    expect(() => resolveOnlyOfficeDevOptions({ [name]: value }, now)).toThrow(name);
  });

  it.each([
    'file:///tmp/sdk',
    'ftp://docs.example.com',
    'https://user:secret@docs.example.com',
    'https://docs.example.com/office?tenant=test',
    'https://docs.example.com/office#sdk',
    'not a URL',
  ])('拒绝非法 Docs URL %j', (docsUrl) => {
    expect(() => resolveOnlyOfficeDevOptions({ ONLYOFFICE_DOCS_URL: docsUrl }, now)).toThrow('ONLYOFFICE_DOCS_URL');
  });

  it('拒绝无效启动时间', () => {
    expect(() => resolveOnlyOfficeDevOptions({}, new Date(Number.NaN))).toThrow('now');
  });
});

describe('onlyoffice 开发 manifest', () => {
  const options = resolveOnlyOfficeDevOptions({}, now);

  function readSourceManifest(): Record<string, unknown> {
    return JSON.parse(readFileSync(manifestPath, 'utf8')) as Record<string, unknown>;
  }

  it('克隆正式 manifest 并只改写开发 iframe URL', () => {
    const source = readSourceManifest();
    const sourceSnapshot = structuredClone(source);
    const manifest = createDevelopmentManifest(source, options);

    expect(manifest).not.toBe(source);
    expect(manifest).toMatchObject({
      guid: source.guid,
      minVersion: '9.4.0',
      version: '0.1.0',
    });
    expect(manifest.variations).not.toBe(source.variations);
    expect(manifest).toMatchObject({
      variations: [
        {
          url: 'index.html?v=20260811T010203',
        },
      ],
    });
    expect(source).toEqual(sourceSnapshot);
  });

  it('拒绝错误 GUID', () => {
    const source = readSourceManifest();
    source.guid = 'asc.{00000000-0000-0000-0000-000000000000}';

    expect(() => createDevelopmentManifest(source, options)).toThrow('GUID');
  });

  it('拒绝错误正式版本', () => {
    const source = readSourceManifest();
    source.version = '0.1.1';

    expect(() => createDevelopmentManifest(source, options)).toThrow('version');
  });

  it.each([undefined, [], [null], [{}], [{ url: 42 }]])('拒绝无有效首个 variation URL 的 manifest：%j', (variations) => {
    const source = readSourceManifest();
    source.variations = variations;

    expect(() => createDevelopmentManifest(source, options)).toThrow('variation');
  });
});

describe('onlyoffice 开发服务生命周期', () => {
  const options = resolveOnlyOfficeDevOptions({}, now);

  function getConfigureServerHook(plugin: Plugin): (server: ViteDevServer) => unknown {
    const hook = plugin.configureServer;
    if (!hook) throw new Error('缺少 configureServer hook');
    return (typeof hook === 'function' ? hook : hook.handler) as (server: ViteDevServer) => unknown;
  }

  function getHandleHotUpdateHook(plugin: Plugin): (context: HmrContext) => unknown {
    const hook = plugin.handleHotUpdate;
    if (!hook) throw new Error('缺少 handleHotUpdate hook');
    return (typeof hook === 'function' ? hook : hook.handler) as (context: HmrContext) => unknown;
  }

  it('http server 开始监听后只打印一次完整 manifest URL', () => {
    const plugin = onlyOfficeDevelopmentPlugin(options);
    let listeningHandler: (() => void) | undefined;
    const httpServer = {
      once(event: string, handler: () => void) {
        if (event === 'listening') listeningHandler = handler;
      },
    };
    const info = vi.fn();
    const server = {
      config: { logger: { info } },
      httpServer,
      middlewares: { use: vi.fn() },
    } as unknown as ViteDevServer;

    getConfigureServerHook(plugin)(server);
    expect(info).not.toHaveBeenCalled();

    listeningHandler?.();
    listeningHandler?.();

    expect(info).toHaveBeenCalledTimes(1);
    expect(info).toHaveBeenCalledWith(options.manifestUrl);
  });

  it.each(['src/main.ts', 'src/onlyoffice/bridge.ts', 'src/onlyoffice/commands.ts', 'src/onlyoffice/types.ts', 'src/composables/useTemplateAnnotator.ts'])(
    '%s 变化时完整刷新 iframe 并阻止旧模块继续热更新',
    (relativePath) => {
      const plugin = onlyOfficeDevelopmentPlugin(options);
      const send = vi.fn();
      const context = {
        file: resolve(process.cwd(), relativePath),
        server: { ws: { send } },
      } as unknown as HmrContext;

      const result = getHandleHotUpdateHook(plugin)(context);

      expect(result).toEqual([]);
      expect(send).toHaveBeenCalledOnce();
      expect(send).toHaveBeenCalledWith({ path: '*', type: 'full-reload' });
    },
  );

  it.each(['src/App.vue', 'src/components/FieldEditor.vue', 'src/styles.css'])('%s 交给 Vite 默认 HMR，不主动完整刷新', (relativePath) => {
    const plugin = onlyOfficeDevelopmentPlugin(options);
    const send = vi.fn();
    const context = {
      file: resolve(process.cwd(), relativePath),
      server: { ws: { send } },
    } as unknown as HmrContext;

    const result = getHandleHotUpdateHook(plugin)(context);

    expect(result).toBeUndefined();
    expect(send).not.toHaveBeenCalled();
  });

  it('config.json 变化时提示重新打开会话、完整刷新且保持本次 startup ID', () => {
    const plugin = onlyOfficeDevelopmentPlugin(options);
    const send = vi.fn();
    const warn = vi.fn();
    const context = {
      file: resolve(process.cwd(), 'config.json'),
      server: { config: { logger: { warn } }, ws: { send } },
    } as unknown as HmrContext;

    const result = getHandleHotUpdateHook(plugin)(context);

    expect(result).toEqual([]);
    expect(send).toHaveBeenCalledWith({ path: '*', type: 'full-reload' });
    expect(warn).toHaveBeenCalledWith('config.json 已变化，请重启 Dev Server 后重新打开编辑器会话。');
    expect(options.startupId).toBe('20260811T010203');
    expect(options.manifestUrl).toContain(`v=${options.startupId}`);
  });
});
// @vitest-environment node
