/**
 * Clipboard Utility Tests
 *
 * Tests for copyToClipboard function covering:
 * - Successful copy with modern API
 * - Fallback for older browsers
 * - Error handling
 */

import { vi, beforeEach, afterEach, describe, it, expect } from 'vitest';
import { copyToClipboard } from './clipboard';

// Mock navigator.clipboard for Node.js environment
const mockClipboard = {
  writeText: vi.fn(),
};

// Mock document.execCommand for fallback testing
const mockExecCommand = vi.fn();

describe('copyToClipboard', () => {
  beforeEach(() => {
    // Reset mocks before each test
    vi.clearAllMocks();

    // Mock navigator.clipboard (not available in Node)
    Object.assign(navigator, {
      clipboard: mockClipboard,
    });

    // Mock document.execCommand
    document.execCommand = mockExecCommand;
  });

  afterEach(() => {
    // Clean up
    vi.restoreAllMocks();
  });

  describe('Modern Clipboard API', () => {
    it('should copy text successfully using clipboard API', async () => {
      mockClipboard.writeText.mockResolvedValue(undefined);

      const result = await copyToClipboard('Test content');

      expect(result).toBe(true);
      expect(mockClipboard.writeText).toHaveBeenCalledWith('Test content');
      expect(mockExecCommand).not.toHaveBeenCalled();
    });

    it('should handle Markdown formatted text', async () => {
      const markdown = `# Title

This is **bold** and *italic*.

- List item 1
- List item 2
`;

      mockClipboard.writeText.mockResolvedValue(undefined);

      const result = await copyToClipboard(markdown);

      expect(result).toBe(true);
      expect(mockClipboard.writeText).toHaveBeenCalledWith(markdown);
    });
  });

  describe('Fallback for older browsers', () => {
    it('should use fallback when clipboard API fails', async () => {
      // Simulate clipboard API failure
      mockClipboard.writeText.mockRejectedValue(new Error('Clipboard API not available'));

      // Mock successful execCommand fallback
      mockExecCommand.mockReturnValue(true);

      const result = await copyToClipboard('Fallback test');

      expect(result).toBe(true);
      expect(mockExecCommand).toHaveBeenCalledWith('copy');
    });

    it('should create textarea for fallback copy', async () => {
      mockClipboard.writeText.mockRejectedValue(new Error('Not available'));
      mockExecCommand.mockReturnValue(true);

      const createElementSpy = vi.spyOn(document, 'createElement');
      const appendChildSpy = vi.spyOn(document.body, 'appendChild');
      const removeChildSpy = vi.spyOn(document.body, 'removeChild');

      await copyToClipboard('Test');

      expect(createElementSpy).toHaveBeenCalledWith('textarea');
      expect(appendChildSpy).toHaveBeenCalled();
      expect(removeChildSpy).toHaveBeenCalled();

      createElementSpy.mockRestore();
      appendChildSpy.mockRestore();
      removeChildSpy.mockRestore();
    });

    it('should position textarea off-screen for fallback', async () => {
      mockClipboard.writeText.mockRejectedValue(new Error('Not available'));
      mockExecCommand.mockReturnValue(true);

      const createElementSpy = vi.spyOn(document, 'createElement').mockReturnValue({
        value: '',
        style: {},
        focus: vi.fn(),
        select: vi.fn(),
      } as any);

      await copyToClipboard('Test');

      const textarea = createElementSpy.mock.results[0].value;
      expect(textarea.style.position).toBe('fixed');
      expect(textarea.style.left).toBe('-9999px');

      createElementSpy.mockRestore();
    });
  });

  describe('Error handling', () => {
    it('should return false when both clipboard API and fallback fail', async () => {
      mockClipboard.writeText.mockRejectedValue(new Error('Clipboard failed'));
      mockExecCommand.mockReturnValue(false);

      const result = await copyToClipboard('Test');

      expect(result).toBe(false);
    });

    it('should handle clipboard API throwing exception', async () => {
      mockClipboard.writeText.mockImplementation(() => {
        throw new Error('Security error');
      });
      mockExecCommand.mockReturnValue(true);

      const result = await copyToClipboard('Test');

      expect(result).toBe(true); // Fallback should succeed
    });

    it('should handle fallback throwing exception', async () => {
      mockClipboard.writeText.mockRejectedValue(new Error('API failed'));
      mockExecCommand.mockImplementation(() => {
        throw new Error('execCommand failed');
      });

      const result = await copyToClipboard('Test');

      expect(result).toBe(false);
    });
  });

  describe('Edge cases', () => {
    it('should handle empty string', async () => {
      mockClipboard.writeText.mockResolvedValue(undefined);

      const result = await copyToClipboard('');

      expect(result).toBe(true);
      expect(mockClipboard.writeText).toHaveBeenCalledWith('');
    });

    it('should handle very long content', async () => {
      const longContent = 'A'.repeat(100000); // 100KB
      mockClipboard.writeText.mockResolvedValue(undefined);

      const result = await copyToClipboard(longContent);

      expect(result).toBe(true);
    });

    it('should handle special characters', async () => {
      const specialChars = '!@#$%^&*()_+-=[]{}|;:\'",.<>?/~`';
      mockClipboard.writeText.mockResolvedValue(undefined);

      const result = await copyToClipboard(specialChars);

      expect(result).toBe(true);
      expect(mockClipboard.writeText).toHaveBeenCalledWith(specialChars);
    });

    it('should handle Unicode characters', async () => {
      const unicode = 'Hello 世界 🌍 Привет мир';
      mockClipboard.writeText.mockResolvedValue(undefined);

      const result = await copyToClipboard(unicode);

      expect(result).toBe(true);
      expect(mockClipboard.writeText).toHaveBeenCalledWith(unicode);
    });
  });
});
