import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { testComponent } from 'lib/test-utils/common';
import { EditableMetadata, EditableMetadataProps } from '../editableMetadata';

const CLASS_NAME = 'EditableMetadata';

describe('<EditableMetadata>', () => {
    const defaultValue = 'TestValue';
    const defaultLabel = 'TestLabel';
    const onConfirmMocked = vi.fn();

    const defaultProps: EditableMetadataProps = {
        value: defaultValue,
        label: defaultLabel,
        onConfirm: onConfirmMocked,
    };

    const renderComponent = (props: Partial<EditableMetadataProps> = {}) =>
        render(<EditableMetadata {...defaultProps} {...props} />);

    beforeEach(() => {
        vi.clearAllMocks();
    });

    testComponent(EditableMetadata, defaultProps);

    test('applies style', () => {
        const r = renderComponent({ style: { marginTop: 10 } });
        expect(r.getByTestId(CLASS_NAME)).toHaveStyle({ marginTop: '10px' });
    });

    test('renders with default props', () => {
        const r = renderComponent();
        expect(r.getByText(defaultLabel)).toBeVisible();
        expect(r.getByText(defaultValue)).toBeVisible();
        expect(r.getByTestId('read-input')).toBeVisible();
    });

    test('shows edit button on hover', () => {
        const r = renderComponent();
        const container = r.getByTestId(CLASS_NAME);

        // Edit button should be hidden initially
        const editButton = r.queryByTestId(`${CLASS_NAME}-edit-button`);
        expect(editButton).toBeInTheDocument();

        // Hover over container should show edit button (CSS opacity change)
        fireEvent.mouseEnter(container);
        expect(editButton).toBeVisible();
    });

    test('clicking edit button shows input', () => {
        const r = renderComponent();
        const editButton = r.getByTestId(`${CLASS_NAME}-edit-button`);

        fireEvent.click(editButton!);

        expect(r.getByDisplayValue(defaultValue)).toBeVisible();
        expect(r.getByTestId(`${CLASS_NAME}-save-button`)).toBeVisible();
        expect(r.getByTestId(`${CLASS_NAME}-cancel-button`)).toBeVisible();
    });

    test('clicking on text shows input', () => {
        const r = renderComponent();
        const readInput = r.getByTestId('read-input');

        fireEvent.click(readInput);

        expect(r.getByDisplayValue(defaultValue)).toBeVisible();
        expect(r.getByTestId(`${CLASS_NAME}-save-button`)).toBeVisible();
        expect(r.getByTestId(`${CLASS_NAME}-cancel-button`)).toBeVisible();
    });

    test('full edit workflow - save changes', () => {
        const r = renderComponent();
        const editButton = r.getByTestId(`${CLASS_NAME}-edit-button`);

        // Enter edit mode
        fireEvent.click(editButton!);

        const input = r.getByDisplayValue(defaultValue) as HTMLInputElement;
        const saveButton = r.getByTestId(`${CLASS_NAME}-save-button`);

        // Change value - directly set the value and trigger change
        const newValue = 'New Test Value';
        input.value = newValue;
        fireEvent.change(input, { target: { value: newValue } });

        // Ensure save button is enabled and not null
        expect(saveButton).toBeTruthy();
        expect(saveButton).not.toBeDisabled();

        // Save changes
        fireEvent.click(saveButton!);

        expect(onConfirmMocked).toHaveBeenCalledWith(newValue);
        expect(r.getByText(defaultValue)).toBeVisible(); // Should show original value since we didn't update props
        expect(r.queryByDisplayValue(newValue)).not.toBeInTheDocument();
    });

    test('full edit workflow - cancel changes', () => {
        const r = renderComponent();
        const editButton = r.getByTestId(`${CLASS_NAME}-edit-button`);

        // Enter edit mode
        fireEvent.click(editButton);

        const input = r.getByDisplayValue(defaultValue);
        const cancelButton = r.getByTestId(`${CLASS_NAME}-cancel-button`);

        // Change value
        const newValue = 'ChangedValue';
        fireEvent.change(input, { target: { value: newValue } });

        // Cancel changes
        fireEvent.click(cancelButton!);

        expect(onConfirmMocked).not.toHaveBeenCalled();
        expect(r.getByText(defaultValue)).toBeVisible();
        expect(r.queryByDisplayValue(newValue)).not.toBeInTheDocument();
    });

    test('shows loading processing state', () => {
        const r = renderComponent({
            processing: { type: 'loading', message: 'Saving...' },
        });

        expect(r.getByTestId('LoadingSpinner')).toBeVisible();
        expect(r.queryByTestId(`${CLASS_NAME}-edit-button`)).not.toBeInTheDocument();
    });

    test('shows success processing state', () => {
        const r = renderComponent({
            processing: { type: 'success', message: 'Saved!' },
        });

        expect(r.getByTestId(`${CLASS_NAME}-success`)).toBeVisible();
        expect(r.queryByTestId(`${CLASS_NAME}-edit-button`)).not.toBeInTheDocument();
    });

    test('processing state fade-out transition', async () => {
        const r = renderComponent({
            processing: { type: 'success', message: 'Saved!' },
        });

        expect(r.getByTestId(`${CLASS_NAME}-success`)).toBeVisible();

        // Remove processing state
        r.rerender(<EditableMetadata {...defaultProps} processing={undefined} />);

        // Should still show success icon during fade
        expect(r.getByTestId(`${CLASS_NAME}-success`)).toBeVisible();

        // After fade completes, should show edit button
        await waitFor(
            () => {
                expect(r.getByTestId(`${CLASS_NAME}-edit-button`)).toBeVisible();
            },
            { timeout: 500 }
        );
    });

    test('validation function prevents invalid input', () => {
        const validationFn = vi.fn((value: string) => value.length > 0 && /^[A-Z]/.test(value));
        const validationError = 'Must start with uppercase letter';

        const r = renderComponent({
            validationFn,
            validationError,
        });

        const editButton = r.getByTestId(`${CLASS_NAME}-edit-button`);
        fireEvent.click(editButton!);

        const input = r.getByDisplayValue(defaultValue);
        const saveButton = r.getByTestId(`${CLASS_NAME}-save-button`);

        // Try invalid input
        fireEvent.change(input, { target: { value: 'lowercase' } });

        expect(validationFn).toHaveBeenCalledWith('lowercase');
        expect(saveButton).toBeDisabled();

        // Try valid input
        fireEvent.change(input, { target: { value: 'Uppercase' } });

        expect(validationFn).toHaveBeenCalledWith('Uppercase');
        expect(saveButton).not.toBeDisabled();
    });

    test('shows validation error tooltip', () => {
        const validationFn = vi.fn((value: string) => value.length > 0 && /^[A-Z]/.test(value));
        const validationError = 'Must start with uppercase letter';

        const r = renderComponent({
            validationFn,
            validationError,
        });

        const editButton = r.getByTestId(`${CLASS_NAME}-edit-button`);
        fireEvent.click(editButton!);

        const input = r.getByDisplayValue(defaultValue);

        // Enter invalid input
        fireEvent.change(input, { target: { value: 'lowercase' } });

        // Should show error tooltip (check if tooltip is shown)
        expect(r.getByText(validationError)).toBeInTheDocument();
    });

    test('input focuses and cursor positioned at end when editing', () => {
        const r = renderComponent();
        const editButton = r.getByTestId(`${CLASS_NAME}-edit-button`);

        fireEvent.click(editButton!);

        const input = r.getByDisplayValue(defaultValue) as HTMLInputElement;
        expect(input).toHaveFocus();
        expect(input.selectionStart).toBe(defaultValue.length);
        expect(input.selectionEnd).toBe(defaultValue.length);
    });

    test('updates input value when props change', () => {
        const r = renderComponent();
        const editButton = r.getByTestId(`${CLASS_NAME}-edit-button`);

        fireEvent.click(editButton);

        const newValue = 'Updated Value';
        r.rerender(<EditableMetadata {...defaultProps} value={newValue} />);

        // The input should have been updated by the useEffect
        const input = r.container.querySelector('input') as HTMLInputElement;
        expect(input.value).toBe(newValue);
    });
});
