import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { Table, type TableHeader } from '../../src/components/Table';
import type { SortState } from '../../src/types/sort';

type Row = {
    id: string;
    name: string;
    email: string;
};

const headers: TableHeader<Row>[] = [
    { label: 'Name', id: 'name', value: (row) => row.name, headerClassName: 'head-name' },
    { label: 'Email', id: 'email', value: (row) => row.email, cellClassName: 'cell-email' },
    { label: 'Static', id: 'static', value: 'Always', sortable: false },
];

const data: Row[] = [{ id: '1', name: 'Jane', email: 'jane@example.com' }];

describe('Table', () => {
    it('renders loading state', () => {
        render(<Table headers={headers} data={[]} isLoading rowKey={(row) => row.id} />);

        expect(document.querySelector('.animate-spin')).toBeTruthy();
    });

    it('renders empty state message when no rows are available', () => {
        render(
            <Table
                headers={headers}
                data={[]}
                emptyMessage="Nothing here"
                rowKey={(row) => row.id}
            />,
        );

        expect(screen.getByText('Nothing here')).toBeInTheDocument();
    });

    it('renders row values from function and static header values', () => {
        const rowKey = vi.fn((row: Row) => row.id);
        render(<Table headers={headers} data={data} rowKey={rowKey} />);

        expect(screen.getByText('Jane')).toBeInTheDocument();
        expect(screen.getByText('jane@example.com')).toBeInTheDocument();
        expect(screen.getByText('Always')).toBeInTheDocument();
        expect(screen.getByText('Name').closest('th')).toHaveClass('head-name');
        expect(screen.getByText('jane@example.com').closest('td')).toHaveClass('cell-email');
        expect(rowKey).toHaveBeenCalledWith(data[0], 0);
    });

    it('shows sortable buttons only when sort config is complete', () => {
        const onSortChange = vi.fn();
        const sortableHeaders: TableHeader<Row>[] = [
            { label: 'No field', id: 'a', sortable: true, value: (row) => row.name },
            { label: 'Empty field', id: 'b', sortable: true, sortField: '', value: (row) => row.name },
            { label: 'Name', id: 'c', sortable: true, sortField: 'name', value: (row) => row.name },
        ];

        const { rerender } = render(
            <Table headers={sortableHeaders} data={data} rowKey={(row) => row.id} onSortChange={onSortChange} />,
        );

        expect(screen.queryByRole('button', { name: 'Sort by No field' })).not.toBeInTheDocument();
        expect(screen.queryByRole('button', { name: 'Sort by Empty field' })).not.toBeInTheDocument();
        expect(screen.getByRole('button', { name: 'Sort by Name' })).toBeInTheDocument();

        rerender(<Table headers={sortableHeaders} data={data} rowKey={(row) => row.id} />);
        expect(screen.queryByRole('button', { name: 'Sort by Name' })).not.toBeInTheDocument();
    });

    it('renders sort states and notifies callback on header click', () => {
        const onSortChange = vi.fn();
        const sortableHeaders: TableHeader<Row>[] = [
            { label: 'Name', id: 'name', sortable: true, sortField: 'name', value: (row) => row.name },
            { label: 'Email', id: 'email', sortable: true, sortField: 'email', value: (row) => row.email },
        ];
        const sorting: SortState = { field: 'name', direction: 'asc' };

        const { rerender } = render(
            <Table
                headers={sortableHeaders}
                data={data}
                rowKey={(row) => row.id}
                sorting={sorting}
                onSortChange={onSortChange}
            />,
        );

        const nameSort = screen.getByRole('button', { name: 'Sort by Name' });
        const emailSort = screen.getByRole('button', { name: 'Sort by Email' });
        expect(nameSort.querySelector('[data-sort-state]')).toHaveAttribute('data-sort-state', 'asc');
        expect(emailSort.querySelector('[data-sort-state]')).toHaveAttribute('data-sort-state', 'none');

        fireEvent.click(nameSort);
        expect(onSortChange).toHaveBeenCalledWith('name');

        rerender(
            <Table
                headers={sortableHeaders}
                data={data}
                rowKey={(row) => row.id}
                sorting={{ field: 'name', direction: 'desc' }}
                onSortChange={onSortChange}
            />,
        );
        expect(nameSort.querySelector('[data-sort-state]')).toHaveAttribute('data-sort-state', 'desc');
    });

    it('supports pagination controls and page-size changes', () => {
        const onPageChange = vi.fn();
        const onPageSizeChange = vi.fn();

        render(
            <Table
                headers={headers}
                data={data}
                rowKey={(row) => row.id}
                pagination={{
                    page: 2,
                    pageSize: 10,
                    total: 21,
                    totalPages: 3,
                    onPageChange,
                    onPageSizeChange,
                }}
            />,
        );

        fireEvent.click(screen.getByRole('button', { name: 'Previous page' }));
        fireEvent.click(screen.getByRole('button', { name: 'Next page' }));
        fireEvent.change(screen.getByLabelText('Rows'), { target: { value: '20' } });

        expect(onPageChange).toHaveBeenNthCalledWith(1, 1);
        expect(onPageChange).toHaveBeenNthCalledWith(2, 3);
        expect(onPageSizeChange).toHaveBeenCalledWith(20);
    });

    it('hides rows selector when onPageSizeChange is absent and clamps page count display', () => {
        render(
            <Table
                headers={headers}
                data={data}
                rowKey={(row) => row.id}
                pagination={{
                    page: 1,
                    pageSize: 10,
                    total: 1,
                    totalPages: 0,
                    onPageChange: vi.fn(),
                }}
            />,
        );

        expect(screen.queryByLabelText('Rows')).not.toBeInTheDocument();
        expect(screen.getByText('Page 1 of 1')).toBeInTheDocument();
    });

    it('disables prev/next at bounds or while loading', () => {
        const { rerender } = render(
            <Table
                headers={headers}
                data={data}
                rowKey={(row) => row.id}
                pagination={{
                    page: 1,
                    pageSize: 10,
                    total: 10,
                    totalPages: 1,
                    onPageChange: vi.fn(),
                }}
            />,
        );

        expect(screen.getByRole('button', { name: 'Previous page' })).toBeDisabled();
        expect(screen.getByRole('button', { name: 'Next page' })).toBeDisabled();

        rerender(
            <Table
                headers={headers}
                data={data}
                rowKey={(row) => row.id}
                isLoading
                pagination={{
                    page: 2,
                    pageSize: 10,
                    total: 100,
                    totalPages: 10,
                    onPageChange: vi.fn(),
                    onPageSizeChange: vi.fn(),
                }}
            />,
        );

        expect(screen.getByRole('button', { name: 'Previous page' })).toBeDisabled();
        expect(screen.getByRole('button', { name: 'Next page' })).toBeDisabled();
        expect(screen.getByLabelText('Rows')).toBeDisabled();
    });
});
