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

import { copyTextToClipboard } from '../clipboard';

describe('copyTextToClipboard', () => {
  const originalClipboard = Object.getOwnPropertyDescriptor(
    navigator,
    'clipboard',
  );
  const originalExecCommand = Object.getOwnPropertyDescriptor(
    document,
    'execCommand',
  );
  const originalSecureContext = Object.getOwnPropertyDescriptor(
    globalThis,
    'isSecureContext',
  );

  beforeEach(() => {
    Object.defineProperty(document, 'execCommand', {
      configurable: true,
      value: vi.fn(() => true),
    });
  });

  afterEach(() => {
    vi.restoreAllMocks();
    restoreProperty(navigator, 'clipboard', originalClipboard);
    restoreProperty(document, 'execCommand', originalExecCommand);
    restoreProperty(globalThis, 'isSecureContext', originalSecureContext);
    document
      .querySelectorAll('textarea[aria-hidden="true"]')
      .forEach((node) => node.remove());
  });

  it('uses Clipboard API in a secure context', async () => {
    const writeText = vi.fn().mockResolvedValue(undefined);
    stubSecureContext(true);
    stubClipboard(writeText);

    await expect(copyTextToClipboard('测试内容')).resolves.toBe(
      'clipboard-api',
    );
    expect(writeText).toHaveBeenCalledWith('测试内容');
    expect(document.execCommand).not.toHaveBeenCalled();
  });

  it('falls back to execCommand in an insecure context', async () => {
    const writeText = vi.fn().mockResolvedValue(undefined);
    stubSecureContext(false);
    stubClipboard(writeText);

    await expect(copyTextToClipboard('Chrome 90')).resolves.toBe(
      'exec-command',
    );
    expect(writeText).not.toHaveBeenCalled();
    expect(document.execCommand).toHaveBeenCalledWith('copy');
    expect(document.querySelector('textarea[aria-hidden="true"]')).toBeNull();
  });

  it('falls back when Clipboard API is denied', async () => {
    const writeText = vi.fn().mockRejectedValue(new DOMException('denied'));
    stubSecureContext(true);
    stubClipboard(writeText);

    await expect(copyTextToClipboard('兼容内容')).resolves.toBe('exec-command');
    expect(document.execCommand).toHaveBeenCalledWith('copy');
  });

  it('throws and cleans up when all copy methods fail', async () => {
    stubSecureContext(false);
    Object.defineProperty(document, 'execCommand', {
      configurable: true,
      value: vi.fn(() => false),
    });

    await expect(copyTextToClipboard('失败内容')).rejects.toThrow(
      '浏览器拒绝了复制操作',
    );
    expect(document.querySelector('textarea[aria-hidden="true"]')).toBeNull();
  });

  function stubClipboard(writeText: ReturnType<typeof vi.fn>) {
    Object.defineProperty(navigator, 'clipboard', {
      configurable: true,
      value: { writeText },
    });
  }

  function stubSecureContext(value: boolean) {
    Object.defineProperty(globalThis, 'isSecureContext', {
      configurable: true,
      value,
    });
  }
});

function restoreProperty(
  target: object,
  property: PropertyKey,
  descriptor?: PropertyDescriptor,
) {
  if (descriptor) {
    Object.defineProperty(target, property, descriptor);
    return;
  }
  Reflect.deleteProperty(target, property);
}
