import type { Column } from './DataTable';
import { describe, expect, test } from 'bun:test';
import { render, screen, fireEvent, within } from '@testing-library/react';
import { DataTable, useDataControl } from './DataTable';

type TestItem = {
  id: number;
  name: string;
  status: 'active' | 'inactive';
  value: number;
};

const mockData: TestItem[] = [
  { id: 1, name: 'Alpha', status: 'active', value: 100 },
  { id: 2, name: 'Beta', status: 'inactive', value: 200 },
  { id: 3, name: 'Gamma', status: 'active', value: 150 },
  { id: 4, name: 'Delta', status: 'inactive', value: 50 },
  { id: 5, name: 'Epsilon', status: 'active', value: 300 },
  { id: 6, name: 'Zeta', status: 'inactive', value: 250 },
  { id: 7, name: 'Eta', status: 'active', value: 175 },
];

const columns: Column<TestItem>[] = [
  { field: 'id', label: 'ID', sortKey: 'id' },
  { field: 'name', label: 'Name', sortKey: 'name' },
  { field: 'status', label: 'Status', sortKey: 'status' },
  { field: 'value', label: 'Value', sortKey: 'value' },
];

const TestWrapper = ({
  data = mockData,
  cols = columns,
  pageSize,
  noDataMessage,
  noDataCTA,
}: {
  data?: TestItem[] | null;
  cols?: Column<TestItem>[];
  pageSize?: number;
  noDataMessage?: string;
  noDataCTA?: React.ReactNode;
}) => {
  const controller = useDataControl<TestItem>(data || []);
  return (
    <DataTable
      dataController={controller}
      columns={cols}
      pageSize={pageSize}
      noDataMessage={noDataMessage}
      noDataCTA={noDataCTA}
    />
  );
};

describe('DataTable', () => {
  describe('rendering', () => {
    test('renders table with data', () => {
      render(<TestWrapper />);

      expect(screen.getByRole('table')).toBeDefined();
      expect(screen.getByText('Alpha')).toBeDefined();
      expect(screen.getByText('Beta')).toBeDefined();
    });

    test('renders column headers', () => {
      render(<TestWrapper />);

      // Component renders both desktop and mobile versions (hidden via CSS)
      // so we use getAllByText and verify at least one exists
      expect(screen.getAllByText('ID').length).toBeGreaterThan(0);
      expect(screen.getAllByText('Name').length).toBeGreaterThan(0);
      expect(screen.getAllByText('Status').length).toBeGreaterThan(0);
      expect(screen.getAllByText('Value').length).toBeGreaterThan(0);
    });

    test('renders correct number of rows based on page size', () => {
      render(<TestWrapper pageSize={3} />);

      const rows = screen.getAllByRole('row');
      // 1 header row + 3 data rows
      expect(rows.length).toBe(4);
    });

    test('shows no data message when data is empty', () => {
      render(<TestWrapper data={[]} noDataMessage="No items found" />);

      expect(screen.getByText('No items found')).toBeDefined();
      expect(screen.queryByRole('table')).toBeNull();
    });

    test('shows default no data message', () => {
      render(<TestWrapper data={[]} />);

      expect(screen.getByText('No Data Found.')).toBeDefined();
    });

    test('renders noDataCTA when provided', () => {
      render(<TestWrapper data={[]} noDataCTA={<button>Add Item</button>} />);

      expect(screen.getByRole('button', { name: 'Add Item' })).toBeDefined();
    });

    test('returns null when data is null', () => {
      const NullWrapper = () => {
        const controller = useDataControl<TestItem>(null);
        return <DataTable dataController={controller} columns={columns} />;
      };

      const { container } = render(<NullWrapper />);
      expect(container.innerHTML).toBe('');
    });
  });

  describe('auto-generated columns', () => {
    test('generates columns from data keys when columns not provided', () => {
      const AutoColumnsWrapper = () => {
        const controller = useDataControl(mockData);
        return <DataTable dataController={controller} />;
      };

      render(<AutoColumnsWrapper />);

      // Should generate columns from object keys (rendered for both mobile/desktop)
      expect(screen.getAllByText('Id').length).toBeGreaterThan(0);
      expect(screen.getAllByText('Name').length).toBeGreaterThan(0);
      expect(screen.getAllByText('Status').length).toBeGreaterThan(0);
      expect(screen.getAllByText('Value').length).toBeGreaterThan(0);
    });
  });

  describe('sorting', () => {
    test('clicking sortable column header triggers sort', () => {
      render(<TestWrapper pageSize={10} />);

      // Get the first "Name" element (desktop version)
      const nameHeaders = screen.getAllByText('Name');
      fireEvent.click(nameHeaders[0]!);

      // After sorting by name ascending, Alpha should still be first
      const rows = screen.getAllByRole('row');
      const firstDataRow = rows[1]!;
      expect(within(firstDataRow).getByText('Alpha')).toBeDefined();
    });

    test('clicking same column twice reverses sort order', () => {
      render(<TestWrapper pageSize={10} />);

      const nameHeaders = screen.getAllByText('Name');
      fireEvent.click(nameHeaders[0]!); // asc
      fireEvent.click(nameHeaders[0]!); // desc

      // After sorting by name descending, Zeta should be first
      const rows = screen.getAllByRole('row');
      const firstDataRow = rows[1]!;
      expect(within(firstDataRow).getByText('Zeta')).toBeDefined();
    });

    test('sort icon is displayed for sortable columns', () => {
      render(<TestWrapper />);

      // Each sortable column should have the ArrowUpDown icon
      const headers = screen.getAllByRole('columnheader');
      headers.forEach((header) => {
        const svg = header.querySelector('svg');
        expect(svg).toBeDefined();
      });
    });
  });

  describe('pagination', () => {
    test('shows pagination bar when data exceeds page size', () => {
      render(<TestWrapper pageSize={5} />);

      expect(screen.getByText(/Showing page 1 of 2/)).toBeDefined();
    });

    test('does not show pagination when all data fits on one page', () => {
      render(<TestWrapper pageSize={10} />);

      expect(screen.queryByText(/Showing page/)).toBeNull();
    });

    test('next button navigates to next page', () => {
      render(<TestWrapper pageSize={5} />);

      const nextButton = screen.getByRole('button', { name: 'Next' });
      fireEvent.click(nextButton);

      expect(screen.getByText(/Showing page 2 of 2/)).toBeDefined();
      // Zeta (id 6) and Eta (id 7) should now be visible
      expect(screen.getByText('Zeta')).toBeDefined();
    });

    test('previous button navigates to previous page', () => {
      render(<TestWrapper pageSize={5} />);

      const nextButton = screen.getByRole('button', { name: 'Next' });
      fireEvent.click(nextButton);

      const prevButton = screen.getByRole('button', { name: 'Prev' });
      fireEvent.click(prevButton);

      expect(screen.getByText(/Showing page 1 of 2/)).toBeDefined();
      expect(screen.getByText('Alpha')).toBeDefined();
    });

    test('first button goes to first page', () => {
      render(<TestWrapper pageSize={5} />);

      const nextButton = screen.getByRole('button', { name: 'Next' });
      fireEvent.click(nextButton);

      const firstButton = screen.getByRole('button', { name: 'First' });
      fireEvent.click(firstButton);

      expect(screen.getByText(/Showing page 1 of 2/)).toBeDefined();
    });

    test('last button goes to last page', () => {
      render(<TestWrapper pageSize={5} />);

      const lastButton = screen.getByRole('button', { name: 'Last' });
      fireEvent.click(lastButton);

      expect(screen.getByText(/Showing page 2 of 2/)).toBeDefined();
    });

    test('navigation buttons are disabled at boundaries', () => {
      render(<TestWrapper pageSize={5} />);

      const firstBtn = screen.getByRole('button', { name: 'First' });
      const prevBtn = screen.getByRole('button', { name: 'Prev' });
      const nextBtn = screen.getByRole('button', { name: 'Next' });
      const lastBtn = screen.getByRole('button', { name: 'Last' });

      // On first page, prev and first should be disabled
      expect(firstBtn.hasAttribute('disabled')).toBe(true);
      expect(prevBtn.hasAttribute('disabled')).toBe(true);
      expect(nextBtn.hasAttribute('disabled')).toBe(false);
      expect(lastBtn.hasAttribute('disabled')).toBe(false);

      // Go to last page
      fireEvent.click(lastBtn);

      // On last page, next and last should be disabled
      expect(firstBtn.hasAttribute('disabled')).toBe(false);
      expect(prevBtn.hasAttribute('disabled')).toBe(false);
      expect(nextBtn.hasAttribute('disabled')).toBe(true);
      expect(lastBtn.hasAttribute('disabled')).toBe(true);
    });
  });

  describe('conditional columns', () => {
    test('hides column when condition is false', () => {
      const conditionalColumns: Column<TestItem>[] = [
        { field: 'id', label: 'ID' },
        { field: 'name', label: 'Name' },
        { field: 'status', label: 'Status', condition: false },
      ];

      render(<TestWrapper cols={conditionalColumns} />);

      expect(screen.getAllByText('ID').length).toBeGreaterThan(0);
      expect(screen.getAllByText('Name').length).toBeGreaterThan(0);
      expect(screen.queryByText('Status')).toBeNull();
    });

    test('shows column when condition is true', () => {
      const conditionalColumns: Column<TestItem>[] = [
        { field: 'id', label: 'ID' },
        { field: 'status', label: 'Status', condition: true },
      ];

      render(<TestWrapper cols={conditionalColumns} />);

      expect(screen.getAllByText('Status').length).toBeGreaterThan(0);
    });

    test('evaluates function condition', () => {
      let showStatus = false;
      const conditionalColumns: Column<TestItem>[] = [
        { field: 'id', label: 'ID' },
        { field: 'status', label: 'Status', condition: () => showStatus },
      ];

      const { rerender } = render(<TestWrapper cols={conditionalColumns} />);
      expect(screen.queryByText('Status')).toBeNull();

      showStatus = true;
      rerender(<TestWrapper cols={conditionalColumns} />);
      expect(screen.getAllByText('Status').length).toBeGreaterThan(0);
    });
  });

  describe('custom formatting', () => {
    test('uses format function to render cell content', () => {
      const formattedColumns: Column<TestItem>[] = [
        { field: 'id', label: 'ID' },
        {
          field: 'name',
          label: 'Name',
          format: ({ value }) => <strong data-testid="formatted-name">{String(value)}</strong>,
        },
      ];

      render(<TestWrapper cols={formattedColumns} />);

      const formattedNames = screen.getAllByTestId('formatted-name');
      expect(formattedNames.length).toBeGreaterThan(0);
      expect(formattedNames[0]?.tagName).toBe('STRONG');
    });

    test('format function receives item and idx', () => {
      const formattedColumns: Column<TestItem>[] = [
        {
          field: 'name',
          label: 'Name',
          format: ({ item, idx }) => <span data-testid={`item-${item.id}`}>{`${item.name}-${idx}`}</span>,
        },
      ];

      render(<TestWrapper cols={formattedColumns} />);

      // Each row should have a unique test ID based on item.id
      expect(screen.getByTestId('item-1')).toBeDefined();
      expect(screen.getByTestId('item-2')).toBeDefined();
      // First item (Alpha, id=1) should have idx=0
      expect(screen.getByText('Alpha-0')).toBeDefined();
    });

    test('format function can render without field', () => {
      const actionColumns: Column<TestItem>[] = [
        { field: 'id', label: 'ID' },
        {
          label: 'Actions',
          format: ({ item }) => <button data-testid={`action-${item.id}`}>Edit {item.name}</button>,
        },
      ];

      render(<TestWrapper cols={actionColumns} />);

      expect(screen.getByTestId('action-1')).toBeDefined();
      expect(screen.getByText('Edit Alpha')).toBeDefined();
    });
  });

  describe('tooltips', () => {
    test('renders tooltip indicator when tooltip is provided', () => {
      const tooltipColumns: Column<TestItem>[] = [
        { field: 'id', label: 'ID', tooltip: 'Unique identifier' },
        { field: 'name', label: 'Name' },
      ];

      render(<TestWrapper cols={tooltipColumns} />);

      // The tooltip indicator (ⓘ) should be present in the ID column headers
      const idHeaders = screen.getAllByText(/ID/);
      const hasTooltipIndicator = idHeaders.some((header) => header.textContent?.includes('ⓘ'));
      expect(hasTooltipIndicator).toBe(true);
    });
  });

  describe('mobile labels', () => {
    test('renders labelMobile when provided', () => {
      const mobileColumns: Column<TestItem>[] = [
        { field: 'id', label: 'Identifier', labelMobile: 'ID' },
        { field: 'name', label: 'Full Name', labelMobile: 'Name' },
      ];

      render(<TestWrapper cols={mobileColumns} />);

      // Both desktop and mobile labels should be in the DOM (hidden/shown via CSS)
      expect(screen.getAllByText('Identifier').length).toBeGreaterThan(0);
      expect(screen.getAllByText('ID').length).toBeGreaterThan(0);
      expect(screen.getAllByText('Full Name').length).toBeGreaterThan(0);
      expect(screen.getAllByText('Name').length).toBeGreaterThan(0);
    });
  });

  describe('integration with useDataControl', () => {
    test('search filters table data', () => {
      const SearchableTable = () => {
        const controller = useDataControl(mockData);
        return (
          <div>
            <input
              data-testid="search-input"
              value={controller.searchQuery}
              onChange={(ev) => {
                controller.setSearchKeys(['name']);
                controller.setSearchQuery(ev.target.value);
              }}
            />
            <DataTable dataController={controller} columns={columns} pageSize={10} />
          </div>
        );
      };

      render(<SearchableTable />);

      const searchInput = screen.getByTestId('search-input');
      fireEvent.change(searchInput, { target: { value: 'Alpha' } });

      // Only Alpha should be visible
      expect(screen.getByText('Alpha')).toBeDefined();
      expect(screen.queryByText('Beta')).toBeNull();
      expect(screen.queryByText('Gamma')).toBeNull();
    });

    test('external sort control works', () => {
      const ExternalSortTable = () => {
        const controller = useDataControl(mockData);
        return (
          <div>
            <button data-testid="sort-btn" onClick={() => controller.setSortedBy('value-desc')}>
              Sort by Value Desc
            </button>
            <DataTable dataController={controller} columns={columns} pageSize={10} />
          </div>
        );
      };

      render(<ExternalSortTable />);

      fireEvent.click(screen.getByTestId('sort-btn'));

      // After sorting by value descending, Epsilon (300) should be first
      const rows = screen.getAllByRole('row');
      const firstDataRow = rows[1]!;
      expect(within(firstDataRow).getByText('Epsilon')).toBeDefined();
    });
  });
});
