import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, act } from '@testing-library/react';
import CodeBlock from '../CodeBlock';

describe('CodeBlock', () => {
  // Mock clipboard API
  const originalClipboard = navigator.clipboard;
  const mockWriteText = vi.fn();

  beforeEach(() => {
    Object.assign(navigator, {
      clipboard: {
        writeText: mockWriteText,
      },
    });
    vi.useFakeTimers();
  });

  afterEach(() => {
    Object.assign(navigator, {
      clipboard: originalClipboard,
    });
    vi.restoreAllMocks();
    vi.useRealTimers();
  });

  describe('Rendering', () => {
    it('renders code content correctly', () => {
      const code = 'print("Hello, World!")';
      const { container } = render(<CodeBlock code={code} />);

      // Check that the code content is rendered (text is within code element)
      const codeElement = container.querySelector('code');
      expect(codeElement?.textContent).toContain('print(');
      // Due to string splitting in regex, checking for function call
      expect(container.querySelector('.text-blue-400')?.textContent).toContain('print(');
    });

    it('renders multi-line code with line numbers', () => {
      const code = 'line 1\nline 2\nline 3';
      render(<CodeBlock code={code} />);

      // Check line numbers
      expect(screen.getByText('1')).toBeInTheDocument();
      expect(screen.getByText('2')).toBeInTheDocument();
      expect(screen.getByText('3')).toBeInTheDocument();

      // Check content
      expect(screen.getByText(/line 1/)).toBeInTheDocument();
      expect(screen.getByText(/line 2/)).toBeInTheDocument();
      expect(screen.getByText(/line 3/)).toBeInTheDocument();
    });

    it('renders terminal-style dots', () => {
      render(<CodeBlock code="test code" />);

      const container = screen.getByRole('button', { name: /copy code/i }).closest('div');
      expect(container).toBeInTheDocument();

      // Check for the presence of the terminal-style dots container
      const dotsContainer = container?.querySelector('.flex.gap-1\\.5');
      expect(dotsContainer).toBeInTheDocument();
      expect(dotsContainer?.children).toHaveLength(3);
    });
  });

  describe('Language and Filename', () => {
    it('applies default language class when no language specified', () => {
      const code = 'test code';
      render(<CodeBlock code={code} />);

      const codeElement = screen.getByText(/test code/).closest('code');
      expect(codeElement).toHaveClass('language-python');
    });

    it('applies custom language class when specified', () => {
      const code = 'const x = 1;';
      render(<CodeBlock code={code} language="javascript" />);

      const codeElement = screen.getByText(/const x = 1/).closest('code');
      expect(codeElement).toHaveClass('language-javascript');
    });

    it('displays filename when provided', () => {
      const code = 'test code';
      const filename = 'example.py';
      render(<CodeBlock code={code} filename={filename} />);

      expect(screen.getByText(filename)).toBeInTheDocument();
    });

    it('does not display filename when not provided', () => {
      const code = 'test code';
      render(<CodeBlock code={code} />);

      // The filename element should not exist in the DOM
      const filenameElement = screen.queryByText(/\.py$/);
      expect(filenameElement).not.toBeInTheDocument();
    });
  });

  describe('Copy Functionality', () => {
    it('renders copy button', () => {
      render(<CodeBlock code="test code" />);

      const copyButton = screen.getByRole('button', { name: /copy code/i });
      expect(copyButton).toBeInTheDocument();
    });

    it('copies code to clipboard when copy button is clicked', async () => {
      const code = 'print("Copy me!")';
      mockWriteText.mockResolvedValue(undefined);

      render(<CodeBlock code={code} />);

      const copyButton = screen.getByRole('button', { name: /copy code/i });
      fireEvent.click(copyButton);

      expect(mockWriteText).toHaveBeenCalledWith(code);
    });

    it('shows check icon after successful copy', async () => {
      const code = 'test code';
      mockWriteText.mockResolvedValue(undefined);

      const { container } = render(<CodeBlock code={code} />);

      const copyButton = screen.getByRole('button', { name: /copy code/i });

      // Initially should show Copy icon
      let copyIcon = copyButton.querySelector('svg');
      expect(copyIcon).toBeInTheDocument();

      // Click to copy
      fireEvent.click(copyButton);

      // Should immediately show Check icon (synchronous state update)
      const checkIcon = container.querySelector('.text-green-400');
      expect(checkIcon).toBeInTheDocument();
    });

    it('reverts to copy icon after 2 seconds', async () => {
      const code = 'test code';
      mockWriteText.mockResolvedValue(undefined);

      const { container } = render(<CodeBlock code={code} />);

      const copyButton = screen.getByRole('button', { name: /copy code/i });

      // Click to copy
      await act(async () => {
        fireEvent.click(copyButton);
      });

      // Should show Check icon
      let checkIcon = container.querySelector('.text-green-400');
      expect(checkIcon).toBeInTheDocument();

      // Fast-forward 2 seconds using act to wrap state updates
      await act(async () => {
        vi.advanceTimersByTime(2000);
      });

      // Should revert to Copy icon (check icon should be gone)
      checkIcon = container.querySelector('.text-green-400');
      expect(checkIcon).not.toBeInTheDocument();
    });
  });

  describe('Syntax Highlighting', () => {
    it('highlights Python comments', () => {
      const code = '# This is a comment';
      render(<CodeBlock code={code} language="python" />);

      const commentElement = screen.getByText(/This is a comment/);
      expect(commentElement).toBeInTheDocument();
      expect(commentElement).toHaveClass('text-gray-500');
    });

    it('highlights JavaScript comments', () => {
      const code = '// This is a comment';
      render(<CodeBlock code={code} language="javascript" />);

      const commentElement = screen.getByText(/This is a comment/);
      expect(commentElement).toBeInTheDocument();
      expect(commentElement).toHaveClass('text-gray-500');
    });

    it('highlights string literals', () => {
      const code = 'print("Hello World")';
      const { container } = render(<CodeBlock code={code} />);

      const stringElements = container.querySelectorAll('.text-green-400');
      expect(stringElements.length).toBeGreaterThan(0);
    });

    it('highlights Python keywords', () => {
      const code = 'def my_function():';
      const { container } = render(<CodeBlock code={code} language="python" />);

      const keywordElements = container.querySelectorAll('.text-purple-400');
      expect(keywordElements.length).toBeGreaterThan(0);
    });

    it('highlights function calls', () => {
      const code = 'print("test")';
      const { container } = render(<CodeBlock code={code} />);

      const functionElements = container.querySelectorAll('.text-blue-400');
      expect(functionElements.length).toBeGreaterThan(0);
    });

    it('highlights multiple keywords in a line', () => {
      const code = 'if True return None';
      const { container } = render(<CodeBlock code={code} />);

      const keywordElements = container.querySelectorAll('.text-purple-400');
      // Should highlight 'if', 'True', 'return', and 'None'
      expect(keywordElements.length).toBeGreaterThanOrEqual(3);
    });

    it('does not highlight non-keyword words', () => {
      const code = 'my_variable = 42';
      render(<CodeBlock code={code} />);

      const codeText = screen.getByText(/my_variable/);
      expect(codeText).toBeInTheDocument();
    });
  });

  describe('Complex Code Examples', () => {
    it('handles Python code with multiple syntax elements', () => {
      const code = `def greet(name):
    # Print a greeting
    return "Hello, " + name`;

      render(<CodeBlock code={code} language="python" />);

      // Check that all lines are rendered
      expect(screen.getByText(/def/)).toBeInTheDocument();
      expect(screen.getByText(/Print a greeting/)).toBeInTheDocument();
      expect(screen.getByText(/return/)).toBeInTheDocument();
    });

    it('handles JavaScript code', () => {
      const code = `const greeting = "Hello";
// Log the greeting
console.log(greeting);`;

      render(<CodeBlock code={code} language="javascript" />);

      expect(screen.getByText(/const greeting =/)).toBeInTheDocument();
      expect(screen.getByText(/Log the greeting/)).toBeInTheDocument();
      expect(screen.getByText(/console.log/)).toBeInTheDocument();
    });

    it('preserves indentation and whitespace', () => {
      const code = `def test():
    if True:
        return 1`;

      const { container } = render(<CodeBlock code={code} />);

      // Check for whitespace-pre class which preserves whitespace
      const codeLines = container.querySelectorAll('.whitespace-pre');
      expect(codeLines.length).toBeGreaterThan(0);
    });
  });

  describe('Edge Cases', () => {
    it('handles empty code string', () => {
      render(<CodeBlock code="" />);

      const copyButton = screen.getByRole('button', { name: /copy code/i });
      expect(copyButton).toBeInTheDocument();
    });

    it('handles code with only whitespace', () => {
      const code = '   \n   \n   ';
      render(<CodeBlock code={code} />);

      // Should still render line numbers
      expect(screen.getByText('1')).toBeInTheDocument();
    });

    it('handles very long single line', () => {
      const code = 'x = ' + 'a'.repeat(1000);
      render(<CodeBlock code={code} />);

      expect(screen.getByText('1')).toBeInTheDocument();
    });

    it('handles special characters in code', () => {
      const code = 'const regex = /[a-z]+/g;';
      render(<CodeBlock code={code} language="javascript" />);

      expect(screen.getByText(/regex/)).toBeInTheDocument();
    });

    it('handles quotes within strings', () => {
      const code = 'const msg = "test message";';
      const { container } = render(<CodeBlock code={code} language="javascript" />);

      // Code should be rendered
      expect(container.querySelector('code')).toBeInTheDocument();
      // Should have string highlighting
      expect(container.querySelectorAll('.text-green-400').length).toBeGreaterThan(0);
    });
  });

  describe('Accessibility', () => {
    it('has accessible copy button with title', () => {
      render(<CodeBlock code="test" />);

      const copyButton = screen.getByRole('button', { name: /copy code/i });
      expect(copyButton).toHaveAttribute('title', 'Copy code');
    });

    it('uses semantic HTML elements', () => {
      const { container } = render(<CodeBlock code="test" />);

      const preElement = container.querySelector('pre');
      const codeElement = container.querySelector('code');

      expect(preElement).toBeInTheDocument();
      expect(codeElement).toBeInTheDocument();
    });

    it('line numbers are not selectable', () => {
      const { container } = render(<CodeBlock code="line 1\nline 2" />);

      const lineNumbers = container.querySelectorAll('.select-none');
      expect(lineNumbers.length).toBeGreaterThan(0);
    });
  });

  describe('Styling', () => {
    it('applies dark theme styling', () => {
      const { container } = render(<CodeBlock code="test" />);

      const mainContainer = container.querySelector('.bg-gray-900');
      expect(mainContainer).toBeInTheDocument();
    });

    it('applies proper border and shadow', () => {
      const { container } = render(<CodeBlock code="test" />);

      const mainContainer = container.querySelector('.border-gray-800.shadow-2xl');
      expect(mainContainer).toBeInTheDocument();
    });

    it('applies monospace font to code', () => {
      const { container } = render(<CodeBlock code="test" />);

      const preElement = container.querySelector('pre.font-mono');
      expect(preElement).toBeInTheDocument();
    });

    it('applies correct text colors', () => {
      const { container } = render(<CodeBlock code="test" />);

      const codeText = container.querySelector('.text-gray-300');
      expect(codeText).toBeInTheDocument();
    });
  });
});
