/**
 * Tests for Results Table Component
 */

import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { ResultsTable } from '../../src/components/query/ResultsTable';
import type { SelectResults } from '../../src/lib/sparql/client';

describe('ResultsTable', () => {
  const mockResults: SelectResults = {
    head: { vars: ['subject', 'predicate', 'object'] },
    results: {
      bindings: [
        {
          subject: { type: 'uri', value: 'http://example.org/subject1' },
          predicate: { type: 'uri', value: 'http://example.org/pred1' },
          object: { type: 'literal', value: 'Object 1' },
        },
        {
          subject: { type: 'uri', value: 'http://example.org/subject2' },
          predicate: { type: 'uri', value: 'http://example.org/pred2' },
          object: { type: 'literal', value: 'Object 2', 'xml:lang': 'en' },
        },
        {
          subject: { type: 'bnode', value: '_:b1' },
          predicate: { type: 'uri', value: 'http://example.org/pred3' },
          object: {
            type: 'literal',
            value: '42',
            datatype: 'http://www.w3.org/2001/XMLSchema#integer',
          },
        },
      ],
    },
  };

  describe('rendering', () => {
    it('should render table with headers', () => {
      render(<ResultsTable results={mockResults} />);

      expect(screen.getByText('subject')).toBeInTheDocument();
      expect(screen.getByText('predicate')).toBeInTheDocument();
      expect(screen.getByText('object')).toBeInTheDocument();
    });

    it('should render all result rows', () => {
      render(<ResultsTable results={mockResults} />);

      expect(screen.getByText(/subject1/)).toBeInTheDocument();
      expect(screen.getByText(/subject2/)).toBeInTheDocument();
      expect(screen.getByText('_:b1')).toBeInTheDocument();
    });

    it('should display results summary', () => {
      const { container } = render(<ResultsTable results={mockResults} />);

      const summary = container.querySelector('.results-summary');
      expect(summary).toHaveTextContent('3');
      expect(summary).toHaveTextContent('result');
      expect(summary).toHaveTextContent('3 columns');
    });

    it('should render empty state when no results', () => {
      const emptyResults: SelectResults = {
        head: { vars: [] },
        results: { bindings: [] },
      };

      render(<ResultsTable results={emptyResults} />);

      expect(screen.getByText('No results found')).toBeInTheDocument();
    });
  });

  describe('cell rendering', () => {
    it('should render URI cells as links', () => {
      render(<ResultsTable results={mockResults} />);

      const uriLinks = screen.getAllByRole('link');
      expect(uriLinks.length).toBeGreaterThan(0);
      expect(uriLinks[0]).toHaveAttribute('target', '_blank');
    });

    it('should render literal cells with language tag', () => {
      render(<ResultsTable results={mockResults} />);

      expect(screen.getByText('Object 2')).toBeInTheDocument();
      expect(screen.getByText('@en')).toBeInTheDocument();
    });

    it('should render literal cells with datatype', () => {
      render(<ResultsTable results={mockResults} />);

      expect(screen.getByText('42')).toBeInTheDocument();
      expect(screen.getByText(/integer/)).toBeInTheDocument();
    });

    it('should render blank nodes', () => {
      render(<ResultsTable results={mockResults} />);

      expect(screen.getByText('_:b1')).toBeInTheDocument();
    });

    it('should render empty cells with placeholder', () => {
      const resultsWithEmpty: SelectResults = {
        head: { vars: ['a', 'b'] },
        results: {
          bindings: [
            { a: { type: 'uri', value: 'http://test' } },
            // b is missing
          ],
        },
      };

      render(<ResultsTable results={resultsWithEmpty} />);
      expect(screen.getByText('—')).toBeInTheDocument();
    });
  });

  describe('sorting', () => {
    it('should sort ascending when clicking column header', () => {
      render(<ResultsTable results={mockResults} />);

      const objectHeader = screen.getByText('object').closest('th')!;
      fireEvent.click(objectHeader);

      // Check for ascending indicator
      expect(screen.getByText('↑')).toBeInTheDocument();
    });

    it('should sort descending on second click', () => {
      render(<ResultsTable results={mockResults} />);

      const objectHeader = screen.getByText('object').closest('th')!;
      fireEvent.click(objectHeader);
      fireEvent.click(objectHeader);

      // Check for descending indicator
      expect(screen.getByText('↓')).toBeInTheDocument();
    });

    it('should clear sort on third click', () => {
      render(<ResultsTable results={mockResults} />);

      const objectHeader = screen.getByText('object').closest('th')!;
      fireEvent.click(objectHeader);
      fireEvent.click(objectHeader);
      fireEvent.click(objectHeader);

      // No sort indicator should be visible
      expect(screen.queryByText('↑')).not.toBeInTheDocument();
      expect(screen.queryByText('↓')).not.toBeInTheDocument();
    });
  });

  describe('pagination', () => {
    // Create large dataset (150 rows, should paginate)
    const largeResults: SelectResults = {
      head: { vars: ['id'] },
      results: {
        bindings: Array.from({ length: 150 }, (_, i) => ({
          id: { type: 'literal', value: `Item ${i + 1}` },
        })),
      },
    };

    it('should show pagination controls for large datasets', () => {
      render(<ResultsTable results={largeResults} />);

      expect(screen.getByText('Next →')).toBeInTheDocument();
      expect(screen.getByText('← Previous')).toBeInTheDocument();
    });

    it('should navigate to next page', () => {
      render(<ResultsTable results={largeResults} />);

      const nextButton = screen.getByText('Next →');
      fireEvent.click(nextButton);

      expect(screen.getByText(/Page 2 of 2/)).toBeInTheDocument();
    });

    it('should navigate to previous page', () => {
      render(<ResultsTable results={largeResults} />);

      // Go to page 2 first
      fireEvent.click(screen.getByText('Next →'));

      // Then go back
      fireEvent.click(screen.getByText('← Previous'));

      expect(screen.getByText(/Page 1 of 2/)).toBeInTheDocument();
    });

    it('should disable previous button on first page', () => {
      render(<ResultsTable results={largeResults} />);

      const prevButton = screen.getByText('← Previous');
      expect(prevButton).toBeDisabled();
    });

    it('should disable next button on last page', () => {
      render(<ResultsTable results={largeResults} />);

      // Navigate to last page
      fireEvent.click(screen.getByText('Next →'));

      const nextButton = screen.getByText('Next →');
      expect(nextButton).toBeDisabled();
    });

    it('should not show pagination for small datasets', () => {
      render(<ResultsTable results={mockResults} />);

      expect(screen.queryByText('Next →')).not.toBeInTheDocument();
    });
  });

  describe('copy functionality', () => {
    // Mock clipboard API
    beforeEach(() => {
      Object.assign(navigator, {
        clipboard: {
          writeText: vi.fn(() => Promise.resolve()),
        },
      });
    });

    it('should copy cell value to clipboard', async () => {
      render(<ResultsTable results={mockResults} />);

      const copyButtons = screen.getAllByLabelText('Copy value');
      fireEvent.click(copyButtons[0]);

      await waitFor(() => {
        expect(navigator.clipboard.writeText).toHaveBeenCalled();
      });
    });

    it('should show copied indicator after copy', async () => {
      render(<ResultsTable results={mockResults} />);

      const copyButtons = screen.getAllByLabelText('Copy value');
      fireEvent.click(copyButtons[0]);

      await waitFor(() => {
        expect(copyButtons[0]).toHaveTextContent('✓');
      });
    });
  });

  describe('export functionality', () => {
    it('should render export buttons when onExport provided', () => {
      const onExport = vi.fn();
      render(<ResultsTable results={mockResults} onExport={onExport} />);

      expect(screen.getByText('Export CSV')).toBeInTheDocument();
      expect(screen.getByText('Export JSON')).toBeInTheDocument();
      expect(screen.getByText('Export JSON-LD')).toBeInTheDocument();
    });

    it('should call onExport with correct format', () => {
      const onExport = vi.fn();
      render(<ResultsTable results={mockResults} onExport={onExport} />);

      fireEvent.click(screen.getByText('Export CSV'));
      expect(onExport).toHaveBeenCalledWith('csv');

      fireEvent.click(screen.getByText('Export JSON'));
      expect(onExport).toHaveBeenCalledWith('json');

      fireEvent.click(screen.getByText('Export JSON-LD'));
      expect(onExport).toHaveBeenCalledWith('jsonld');
    });

    it('should not render export buttons when onExport not provided', () => {
      render(<ResultsTable results={mockResults} />);

      expect(screen.queryByText('Export CSV')).not.toBeInTheDocument();
    });
  });

  describe('custom max height', () => {
    it('should apply custom max height', () => {
      const { container } = render(
        <ResultsTable results={mockResults} maxHeight="400px" />
      );

      const wrapper = container.querySelector('.results-table-wrapper');
      expect(wrapper).toHaveStyle({ maxHeight: '400px' });
    });
  });
});
