import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { useState } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { DatePicker } from '../../src/components/DatePicker';

type ControlledProps = {
    type: 'date' | 'time' | 'date-time';
    initialValue: string;
    format?: string;
    min?: string;
    max?: string;
    onValueChange?: (value: string) => void;
    disabled?: boolean;
};

function ControlledDatePicker({
    type,
    initialValue,
    format,
    min,
    max,
    onValueChange,
    disabled = false,
}: Readonly<ControlledProps>) {
    const [value, setValue] = useState(initialValue);

    return (
        <DatePicker
            label="Schedule"
            type={type}
            value={value}
            format={format}
            min={min}
            max={max}
            disabled={disabled}
            onChange={(event) => {
                setValue(event.target.value);
                onValueChange?.(event.target.value);
            }}
        />
    );
}

function pickCurrentMonthDay(label: string): void {
    const dayButtons = Array.from(document.querySelectorAll('.datepicker-day')) as HTMLButtonElement[];
    const targetButton = dayButtons.find(
        (button) => button.textContent === label && !button.classList.contains('is-outside-month'),
    );

    expect(targetButton).toBeDefined();
    fireEvent.click(targetButton as HTMLButtonElement);
}

function getCurrentMonthDayButton(label: string): HTMLButtonElement {
    const dayButtons = Array.from(document.querySelectorAll('.datepicker-day')) as HTMLButtonElement[];
    const targetButton = dayButtons.find(
        (button) => button.textContent === label && !button.classList.contains('is-outside-month'),
    );

    if (!targetButton) {
        throw new Error(`Could not find day button for ${label}`);
    }

    return targetButton;
}

describe('DatePicker', () => {
    it('opens popup from icon button and closes with Escape', () => {
        render(<DatePicker label="Schedule" type="date-time" value="2031/05/20 14:30" onChange={() => {}} />);

        const input = screen.getByLabelText('Schedule') as HTMLInputElement;
        expect(input.type).toBe('text');

        fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));
        expect(
            screen.getByRole('dialog', { name: 'Date and time picker popup' }),
        ).toBeInTheDocument();

        fireEvent.keyDown(document, { key: 'Escape' });
        expect(
            screen.queryByRole('dialog', { name: 'Date and time picker popup' }),
        ).not.toBeInTheDocument();
    });

    it('supports segment-by-segment editing with custom format and auto-advance', async () => {
        const onValueChange = vi.fn();
        render(
            <ControlledDatePicker
                type="date-time"
                format="dd/mm/yyyy HH:mm"
                initialValue="22/02/2026 14:30"
                onValueChange={onValueChange}
            />, 
        );

        const input = screen.getByLabelText('Schedule') as HTMLInputElement;
        fireEvent.focus(input);

        await waitFor(() => {
            expect(input.selectionStart).toBe(0);
            expect(input.selectionEnd).toBe(2);
        });

        fireEvent.keyDown(input, { key: '1' });
        await waitFor(() => {
            expect(input.value).toBe('01/02/2026 14:30');
            expect(input.selectionStart).toBe(0);
            expect(input.selectionEnd).toBe(2);
        });
        fireEvent.keyDown(input, { key: '1' });
        await waitFor(() => {
            expect(input.value).toBe('11/02/2026 14:30');
            expect(input.selectionStart).toBe(3);
            expect(input.selectionEnd).toBe(5);
        });

        fireEvent.keyDown(input, { key: '0' });
        fireEvent.keyDown(input, { key: '3' });
        await waitFor(() => {
            expect(input.value).toBe('11/03/2026 14:30');
            expect(input.selectionStart).toBe(6);
            expect(input.selectionEnd).toBe(10);
        });

        fireEvent.keyDown(input, { key: '2' });
        fireEvent.keyDown(input, { key: '0' });
        fireEvent.keyDown(input, { key: '2' });
        fireEvent.keyDown(input, { key: '7' });
        await waitFor(() => {
            expect(input.value).toBe('11/03/2027 14:30');
            expect(input.selectionStart).toBe(11);
            expect(input.selectionEnd).toBe(13);
        });

        fireEvent.keyDown(input, { key: '1' });
        fireEvent.keyDown(input, { key: '6' });
        await waitFor(() => {
            expect(input.value).toBe('11/03/2027 16:30');
            expect(input.selectionStart).toBe(14);
            expect(input.selectionEnd).toBe(16);
        });

        fireEvent.keyDown(input, { key: '4' });
        fireEvent.keyDown(input, { key: '5' });
        await waitFor(() => {
            expect(input.value).toBe('11/03/2027 16:45');
        });

        expect(onValueChange).toHaveBeenCalled();
    });

    it('preserves year 0000 while editing and does not coerce to 19xx', async () => {
        render(
            <ControlledDatePicker
                type="date-time"
                format="dd/mm/yyyy HH:mm"
                initialValue="22/02/2026 14:30"
            />,
        );

        const input = screen.getByLabelText('Schedule') as HTMLInputElement;
        fireEvent.focus(input);

        await waitFor(() => {
            expect(input.selectionStart).toBe(0);
            expect(input.selectionEnd).toBe(2);
        });

        fireEvent.keyDown(input, { key: '/' });
        await waitFor(() => {
            expect(input.selectionStart).toBe(3);
            expect(input.selectionEnd).toBe(5);
        });

        fireEvent.keyDown(input, { key: '/' });
        await waitFor(() => {
            expect(input.selectionStart).toBe(6);
            expect(input.selectionEnd).toBe(10);
        });

        fireEvent.keyDown(input, { key: '0' });
        await waitFor(() => {
            expect(input.value).toBe('22/02/0000 14:30');
            expect(input.selectionStart).toBe(6);
            expect(input.selectionEnd).toBe(10);
        });

        fireEvent.keyDown(input, { key: '0' });
        fireEvent.keyDown(input, { key: '0' });
        fireEvent.keyDown(input, { key: '0' });
        await waitFor(() => {
            expect(input.value).toBe('22/02/0000 14:30');
            expect(input.selectionStart).toBe(11);
            expect(input.selectionEnd).toBe(13);
        });
    });

    it('uses separator keys to move between editable segments', async () => {
        render(
            <ControlledDatePicker
                type="date-time"
                format="dd/mm/yyyy HH:mm"
                initialValue="22/02/2026 14:30"
            />,
        );

        const input = screen.getByLabelText('Schedule') as HTMLInputElement;
        fireEvent.focus(input);

        await waitFor(() => {
            expect(input.selectionStart).toBe(0);
            expect(input.selectionEnd).toBe(2);
        });

        fireEvent.keyDown(input, { key: '/' });
        await waitFor(() => {
            expect(input.selectionStart).toBe(3);
            expect(input.selectionEnd).toBe(5);
        });

        fireEvent.keyDown(input, { key: '/' });
        await waitFor(() => {
            expect(input.selectionStart).toBe(6);
            expect(input.selectionEnd).toBe(10);
        });

        fireEvent.keyDown(input, { key: ' ' });
        await waitFor(() => {
            expect(input.selectionStart).toBe(11);
            expect(input.selectionEnd).toBe(13);
        });

        fireEvent.keyDown(input, { key: ':' });
        await waitFor(() => {
            expect(input.selectionStart).toBe(14);
            expect(input.selectionEnd).toBe(16);
        });
    });

    it('applies min/max constraints to calendar and time options', () => {
        render(
            <ControlledDatePicker
                type="date-time"
                initialValue="2026/03/10 10:00"
                min="2026/03/10 09:30"
                max="2026/03/10 10:15"
            />,
        );

        fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));

        expect(getCurrentMonthDayButton('9')).toBeDisabled();
        expect(getCurrentMonthDayButton('10')).toBeEnabled();
        expect(getCurrentMonthDayButton('11')).toBeDisabled();

        const hoursList = screen.getByRole('listbox', { name: 'Hours' });
        expect(within(hoursList).getByRole('button', { name: '08' })).toBeDisabled();
        expect(within(hoursList).getByRole('button', { name: '09' })).toBeEnabled();
        expect(within(hoursList).getByRole('button', { name: '10' })).toBeEnabled();
        expect(within(hoursList).getByRole('button', { name: '11' })).toBeDisabled();

        const minutesList = screen.getByRole('listbox', { name: 'Minutes' });
        expect(within(minutesList).getByRole('button', { name: '15' })).toBeEnabled();
        expect(within(minutesList).getByRole('button', { name: '20' })).toBeDisabled();
    });

    it('renders date mode calendar only and auto-closes after day selection', () => {
        const onValueChange = vi.fn();
        render(
            <ControlledDatePicker
                type="date"
                initialValue="2031/05/20"
                onValueChange={onValueChange}
            />,
        );

        fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));

        expect(screen.getByRole('dialog', { name: 'Date picker popup' })).toBeInTheDocument();
        expect(screen.queryByText('Hours')).not.toBeInTheDocument();
        expect(screen.queryByText('Minutes')).not.toBeInTheDocument();

        pickCurrentMonthDay('15');

        const input = screen.getByLabelText('Schedule') as HTMLInputElement;
        expect(input.value).toMatch(/^\d{4}\/\d{2}\/15$/);
        expect(onValueChange).toHaveBeenCalled();
        expect(screen.queryByRole('dialog', { name: 'Date picker popup' })).not.toBeInTheDocument();
    });

    it('renders time mode selectors only and keeps popup open after selection', () => {
        const onValueChange = vi.fn();
        render(<ControlledDatePicker type="time" initialValue="09:00" onValueChange={onValueChange} />);

        fireEvent.click(screen.getByRole('button', { name: 'Open date picker' }));

        expect(screen.getByRole('dialog', { name: 'Time picker popup' })).toBeInTheDocument();
        expect(screen.queryByRole('grid')).not.toBeInTheDocument();

        const hoursList = screen.getByRole('listbox', { name: 'Hours' });
        const minutesList = screen.getByRole('listbox', { name: 'Minutes' });

        fireEvent.click(within(hoursList).getByRole('button', { name: '13' }));
        fireEvent.click(within(minutesList).getByRole('button', { name: '45' }));

        const input = screen.getByLabelText('Schedule') as HTMLInputElement;
        expect(input.value).toBe('13:45');
        expect(onValueChange).toHaveBeenCalledTimes(2);
        expect(screen.getByRole('dialog', { name: 'Time picker popup' })).toBeInTheDocument();
    });

    it('blocks popup interactions while disabled', () => {
        render(<ControlledDatePicker type="date" initialValue="2031/05/20" disabled />);

        const input = screen.getByLabelText('Schedule') as HTMLInputElement;
        const iconButton = screen.getByRole('button', { name: 'Open date picker' });

        expect(input).toBeDisabled();
        expect(iconButton).toBeDisabled();

        fireEvent.click(iconButton);

        expect(screen.queryByRole('dialog', { name: 'Date picker popup' })).not.toBeInTheDocument();
    });

    it('renders right icon and error message', () => {
        const { container } = render(
            <DatePicker
                label="Schedule"
                type="date-time"
                value="2031/05/20 14:30"
                onChange={() => {}}
                rightIcon={<span data-testid="right-icon">R</span>}
                error="Invalid date"
                inputClassName="custom-input"
            />,
        );

        const input = container.querySelector('input');
        expect(input).toBeInstanceOf(HTMLInputElement);
        expect(screen.getByTestId('right-icon')).toBeInTheDocument();
        expect(input).toHaveClass('pr-10');
        expect(input).toHaveClass('custom-input');
        expect(screen.getByText('Invalid date')).toBeInTheDocument();
    });

    it('supports inline layout', () => {
        const { container } = render(
            <DatePicker label="Start time" type="time" value="09:00" onChange={() => {}} layout="inline" />,
        );

        expect(container.querySelector('label')).toHaveClass('inline-flex');
        expect(container.querySelector('label > div')).not.toHaveClass('mt-1');
    });
});
