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

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

import { useTemplateAnnotator } from '../../src/composables/useTemplateAnnotator';
import type { OfficeContentControl } from '../../src/onlyoffice/types';

interface Deferred<T> {
  promise: Promise<T>;
  reject: (reason?: unknown) => void;
  resolve: (value: T) => void;
}

function deferred<T>(): Deferred<T> {
  let resolve!: (value: T) => void;
  let reject!: (reason?: unknown) => void;
  const promise = new Promise<T>((resolvePromise, rejectPromise) => {
    resolve = resolvePromise;
    reject = rejectPromise;
  });
  return { promise, reject, resolve };
}

function control(Tag: string, InternalId = `id-${Tag}`): OfficeContentControl {
  return { Alias: Tag, InternalId, Tag };
}

function definition(): FieldDefinition {
  return {
    mode: 'single',
    controlType: 'text',
    fieldCode: 'result',
    alias: '结果',
    groupCode: '',
    rowId: '',
    tag: 'eln.field.result',
    color: { R: 23, G: 109, B: 92 },
    placeholder: '请输入结果',
    defaultValue: '',
    defaultChecked: false,
  };
}

const validOptions = {
  apiBaseUrl: 'http://localhost:30000',
    documentId: 'file-1',
    bizDataId: 'record-1',
    bizModule: 'eln.template',
    bizScene: 'eln.template.annotate',
    pluginVersion: '0.1.0',
    canAnnotate: true,
};

function createBridge() {
  let callbacks: Parameters<OnlyOfficeBridge['bindLifecycle']>[0] | undefined;
  const cleanup = vi.fn();
  const bridge: OnlyOfficeBridge = {
    bindLifecycle: vi.fn((value) => {
      callbacks = value;
      return cleanup;
    }),
    close: vi.fn(),
    deleteControl: vi.fn(),
    disableTrackRevisions: vi.fn().mockResolvedValue(undefined),
    getRuntimeOptions: vi.fn(() => validOptions),
    insertField: vi.fn(),
    listControls: vi.fn(),
    selectControl: vi.fn(),
  };
  return { bridge, cleanup, getCallbacks: () => callbacks };
}

describe('useTemplateAnnotator', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
  });

  it('由 SDK ready 回调初始化并写入字段列表', async () => {
    const fixture = createBridge();
    const controls = [control('eln.field.result')];
    vi.mocked(fixture.bridge.listControls).mockResolvedValue(controls);
    const api = useTemplateAnnotator(fixture.bridge);

    fixture.getCallbacks()?.onReady();

    await vi.waitFor(() => expect(api.store.status).toBe('ready'));
    expect(fixture.bridge.disableTrackRevisions).toHaveBeenCalledOnce();
    expect(api.store.controls).toEqual(controls);
    expect(api.store.operation).toEqual({ kind: 'success', message: '文档字段已刷新' });
  });

  it('SDK ready 时上下文非法则进入 fatal 且不读取控件', async () => {
    const fixture = createBridge();
    vi.mocked(fixture.bridge.getRuntimeOptions).mockReturnValue({});
    const api = useTemplateAnnotator(fixture.bridge);

    fixture.getCallbacks()?.onReady();

    await vi.waitFor(() => expect(api.store.status).toBe('error'));
    expect(api.store.operation).toEqual({ kind: 'error', message: '插件运行上下文无效' });
    expect(fixture.bridge.listControls).not.toHaveBeenCalled();
  });

  it('记录 SDK ready 初始化失败且生命周期调用不产生未处理拒绝', async () => {
    const fixture = createBridge();
    vi.mocked(fixture.bridge.listControls).mockRejectedValue(new Error('SDK 未就绪'));
    const api = useTemplateAnnotator(fixture.bridge);

    expect(() => fixture.getCallbacks()?.onReady()).not.toThrow();

    await vi.waitFor(() => expect(api.store.status).toBe('error'));
    expect(api.store.operation).toEqual({ kind: 'error', message: '读取内容控件失败：SDK 未就绪' });
  });

  it('SDK ready 永不触发时结束加载并进入 fatal error', () => {
    vi.useFakeTimers();
    try {
      const fixture = createBridge();
      const api = useTemplateAnnotator(fixture.bridge);

      vi.advanceTimersByTime(5_000);

      expect(api.store.status).toBe('error');
      expect(api.store.operation).toEqual({ kind: 'error', message: 'ONLYOFFICE SDK 初始化超时' });
      expect(fixture.bridge.listControls).not.toHaveBeenCalled();
    } finally {
      vi.useRealTimers();
    }
  });

  it('initialize 失败进入 fatal error 并继续抛出错误', async () => {
    const fixture = createBridge();
    const error = new Error('SDK 不可用');
    vi.mocked(fixture.bridge.listControls).mockRejectedValue(error);
    const api = useTemplateAnnotator(fixture.bridge);

    await expect(api.initialize()).rejects.toBe(error);
    expect(api.store.status).toBe('error');
    expect(api.store.operation).toEqual({ kind: 'error', message: '读取内容控件失败：SDK 不可用' });
  });

  it('refresh 成功同步列表和成功状态', async () => {
    const fixture = createBridge();
    const controls = [control('eln.field.sample')];
    vi.mocked(fixture.bridge.listControls).mockResolvedValue(controls);
    const api = useTemplateAnnotator(fixture.bridge);

    await api.refresh();

    expect(api.store.controls).toEqual(controls);
    expect(api.store.status).toBe('ready');
    expect(api.store.operation).toEqual({ kind: 'success', message: '文档字段已刷新' });
  });

  it('refresh 失败恢复 ready、显示错误并继续抛出', async () => {
    const fixture = createBridge();
    const error = new Error('编辑器未返回内容控件列表');
    vi.mocked(fixture.bridge.listControls).mockRejectedValue(error);
    const api = useTemplateAnnotator(fixture.bridge);

    await expect(api.refresh()).rejects.toBe(error);

    expect(api.store.status).toBe('ready');
    expect(api.store.operation).toEqual({ kind: 'error', message: '读取内容控件失败：编辑器未返回内容控件列表' });
  });

  it('initialize 失败后再次 refresh 失败保持 fatal error', async () => {
    const fixture = createBridge();
    vi.mocked(fixture.bridge.listControls).mockRejectedValueOnce(new Error('初始化失败')).mockRejectedValueOnce(new Error('刷新失败'));
    const api = useTemplateAnnotator(fixture.bridge);

    await expect(api.initialize()).rejects.toThrow('初始化失败');
    expect(api.store.status).toBe('error');

    await expect(api.refresh()).rejects.toThrow('刷新失败');

    expect(api.store.status).toBe('error');
    expect(api.store.operation).toEqual({ kind: 'error', message: '读取内容控件失败：刷新失败' });
  });

  it('insert 成功同步 bridge 返回列表并显示字段别名', async () => {
    const fixture = createBridge();
    const field = definition();
    const controls = [control(field.tag)];
    vi.mocked(fixture.bridge.insertField).mockResolvedValue(controls);
    const api = useTemplateAnnotator(fixture.bridge);

    await api.insert(field);

    expect(fixture.bridge.insertField).toHaveBeenCalledWith(field);
    expect(api.store.controls).toEqual(controls);
    expect(api.store.status).toBe('ready');
    expect(api.store.operation).toEqual({ kind: 'success', message: '已创建字段：结果' });
  });

  it('insert 失败恢复 ready、显示错误并继续抛出', async () => {
    const fixture = createBridge();
    const error = new Error('Tag 重复');
    vi.mocked(fixture.bridge.insertField).mockRejectedValue(error);
    const api = useTemplateAnnotator(fixture.bridge);

    await expect(api.insert(definition())).rejects.toBe(error);

    expect(api.store.status).toBe('ready');
    expect(api.store.operation).toEqual({ kind: 'error', message: '标注失败：Tag 重复' });
  });

  it('deleteControl 成功同步列表并显示标注已解除', async () => {
    const fixture = createBridge();
    const remaining = [control('eln.field.other')];
    vi.mocked(fixture.bridge.deleteControl).mockResolvedValue(remaining);
    const api = useTemplateAnnotator(fixture.bridge);

    await api.deleteControl('control-1');

    expect(fixture.bridge.deleteControl).toHaveBeenCalledWith('control-1');
    expect(api.store.controls).toEqual(remaining);
    expect(api.store.status).toBe('ready');
    expect(api.store.operation).toEqual({ kind: 'success', message: '标注已解除' });
  });

  it('deleteControl 失败恢复 ready、显示错误并继续抛出', async () => {
    const fixture = createBridge();
    const error = new Error('解除后字段仍然存在');
    vi.mocked(fixture.bridge.deleteControl).mockRejectedValue(error);
    const api = useTemplateAnnotator(fixture.bridge);

    await expect(api.deleteControl('control-1')).rejects.toBe(error);

    expect(api.store.status).toBe('ready');
    expect(api.store.operation).toEqual({ kind: 'error', message: '解除标注失败：解除后字段仍然存在' });
  });

  it('select 失败后刷新控件，但保留并抛出原始定位错误', async () => {
    const fixture = createBridge();
    const selectError = new Error('定位目标已不存在，请刷新字段列表');
    const controls = [control('eln.field.current')];
    vi.mocked(fixture.bridge.selectControl).mockRejectedValue(selectError);
    vi.mocked(fixture.bridge.listControls).mockResolvedValue(controls);
    const api = useTemplateAnnotator(fixture.bridge);

    await expect(api.select('missing-id')).rejects.toBe(selectError);

    expect(fixture.bridge.listControls).toHaveBeenCalledOnce();
    expect(api.store.controls).toEqual(controls);
    expect(api.store.status).toBe('ready');
    expect(api.store.operation).toEqual({ kind: 'error', message: '定位失败：定位目标已不存在，请刷新字段列表' });
  });

  it('select 补偿刷新失败不替换原始错误，空 ID 也传给 bridge', async () => {
    const fixture = createBridge();
    const selectError = new Error('该内容控件没有可用的内部 ID');
    vi.mocked(fixture.bridge.selectControl).mockRejectedValue(selectError);
    vi.mocked(fixture.bridge.listControls).mockRejectedValue(new Error('刷新也失败'));
    const api = useTemplateAnnotator(fixture.bridge);

    await expect(api.select('')).rejects.toBe(selectError);

    expect(fixture.bridge.selectControl).toHaveBeenCalledWith('');
    expect(api.store.operation).toEqual({ kind: 'error', message: '定位失败：该内容控件没有可用的内部 ID' });
  });

  it('select 定位期间保持页面状态且继续使用并发锁', async () => {
    const fixture = createBridge();
    const pending = deferred<void>();
    vi.mocked(fixture.bridge.selectControl).mockReturnValue(pending.promise);
    const api = useTemplateAnnotator(fixture.bridge);
    api.store.setStatus('ready');
    api.store.setOperation({ kind: 'success', message: '文档字段已刷新' });

    const operation = api.select('control-1');

    expect(api.store.status).toBe('ready');
    expect(api.store.operation).toEqual({ kind: 'success', message: '文档字段已刷新' });
    await expect(api.refresh()).rejects.toThrow('已有操作正在执行');

    pending.resolve();
    await operation;

    expect(api.store.operation).toEqual({ kind: 'success', message: '文档字段已刷新' });
  });

  it('所有异步操作共用并发锁，重入不覆盖当前状态', async () => {
    const fixture = createBridge();
    const pending = deferred<OfficeContentControl[]>();
    vi.mocked(fixture.bridge.listControls).mockReturnValue(pending.promise);
    const api = useTemplateAnnotator(fixture.bridge);
    const first = api.refresh();

    await expect(api.insert(definition())).rejects.toThrow('已有操作正在执行');
    await expect(api.select('control-1')).rejects.toThrow('已有操作正在执行');
    await expect(api.deleteControl('control-1')).rejects.toThrow('已有操作正在执行');
    expect(api.store.status).toBe('working');
    expect(api.store.operation).toEqual({ kind: 'working', message: '正在读取文档字段...' });

    pending.resolve([]);
    await first;
  });

  it('操作失败后释放并发锁，可再次执行', async () => {
    const fixture = createBridge();
    vi.mocked(fixture.bridge.listControls).mockRejectedValueOnce(new Error('第一次失败')).mockResolvedValueOnce([]);
    const api = useTemplateAnnotator(fixture.bridge);

    await expect(api.refresh()).rejects.toThrow('第一次失败');
    await expect(api.refresh()).resolves.toBeUndefined();

    expect(fixture.bridge.listControls).toHaveBeenCalledTimes(2);
  });

  it('dispose 后忽略 SDK ready，且迟到的异步结果不再写 Store', async () => {
    const fixture = createBridge();
    const pending = deferred<OfficeContentControl[]>();
    vi.mocked(fixture.bridge.listControls).mockReturnValue(pending.promise);
    const api = useTemplateAnnotator(fixture.bridge);
    const operation = api.refresh();
    const snapshot = {
      status: api.store.status,
      controls: [...api.store.controls],
      operation: { ...api.store.operation },
    };

    api.dispose();
    fixture.getCallbacks()?.onReady();
    pending.resolve([control('eln.field.late')]);
    await operation;

    expect(api.store.$state).toEqual(snapshot);
    expect(fixture.bridge.listControls).toHaveBeenCalledOnce();
  });

  it('dispose 幂等调用 lifecycle cleanup', () => {
    const fixture = createBridge();
    const api = useTemplateAnnotator(fixture.bridge);

    api.dispose();
    api.dispose();

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