import React from 'react';
import {
    render,
    screen,
    fireEvent,
    waitFor,
    act,
} from '@testing-library/react';
import { EditableMetadata } from '@theorchard/suite-components';
import { EditableCell } from '../editable-cell';

jest.useFakeTimers();

// Mock EditableMetadata so we can trigger onConfirm easily
jest.mock('@theorchard/suite-components', () => ({
    EditableMetadata: jest.fn(
        ({ value, onConfirm, processing, 'data-testid': dataTestId }) => (
            <div>
                <span data-testid="mock-value">{value}</span>
                <span data-testid="mock-processing">{processing?.type}</span>
                <button
                    data-testid="mock-confirm-btn"
                    onClick={() => onConfirm('new value')}
                >
                    Confirm
                </button>
                {dataTestId && <div data-testid={dataTestId} />}
            </div>
        )
    ),
}));

describe('EditableCell', () => {
    beforeEach(() => {
        jest.clearAllMocks();
    });

    it('renders with initial value', () => {
        render(<EditableCell value="Initial" onSave={jest.fn()} />);
        expect(screen.getByTestId('mock-value')).toHaveTextContent('Initial');
    });

    it('passes data-testid to EditableMetadata', () => {
        render(
            <EditableCell
                value="test"
                onSave={jest.fn()}
                data-testid="editable-test-id"
            />
        );
        expect(screen.getByTestId('editable-test-id')).toBeInTheDocument();
    });

    it('calls onSave and updates value on success, then clears success after timeout', async () => {
        const onSaveMock = jest.fn().mockResolvedValue(undefined);
        const afterSaveMock = jest.fn();

        render(
            <EditableCell
                value="Initial"
                onSave={onSaveMock}
                afterSave={afterSaveMock}
            />
        );

        fireEvent.click(screen.getByTestId('mock-confirm-btn'));

        await waitFor(() => {
            expect(onSaveMock).toHaveBeenCalledWith('new value');
        });

        // Should show success processing state
        await waitFor(() => {
            expect(screen.getByTestId('mock-processing')).toHaveTextContent(
                'success'
            );
        });
        expect(screen.getByTestId('mock-value')).toHaveTextContent('new value');

        // Fast-forward timers to simulate SUCCESS_TIMEOUT_MS
        act(() => {
            jest.advanceTimersByTime(2000);
        });

        await waitFor(() => {
            // Success state cleared
            expect(screen.getByTestId('mock-processing')).toBeEmptyDOMElement();
        });
        // afterSave should be called
        expect(afterSaveMock).toHaveBeenCalled();
    });

    it('sets error processing state on save failure', async () => {
        const onSaveMock = jest.fn().mockRejectedValue(new Error('fail'));
        render(<EditableCell value="Initial" onSave={onSaveMock} />);

        fireEvent.click(screen.getByTestId('mock-confirm-btn'));

        await waitFor(() => {
            expect(onSaveMock).toHaveBeenCalledWith('new value');
        });

        // We can't directly see processing state here because it's passed to EditableMetadata
        // But we can verify that EditableMetadata was called with type=error in one of its renders
        await waitFor(() => {
            const calls = (EditableMetadata as jest.Mock).mock.calls;
            const processingArgs = calls[calls.length - 1][0].processing;
            expect(processingArgs?.type).toBe('error');
        });
    });

    it('updates internal value when prop changes', () => {
        const { rerender } = render(
            <EditableCell value="Initial" onSave={jest.fn()} />
        );
        rerender(<EditableCell value="Updated" onSave={jest.fn()} />);
        expect(screen.getByTestId('mock-value')).toHaveTextContent('Updated');
    });

    it('does not update state if unmounted before save resolves', async () => {
        jest.spyOn(console, 'error').mockImplementation(() => {}); // Suppress React warnings
        const onSaveMock = jest
            .fn()
            .mockImplementation(
                () => new Promise(resolve => setTimeout(resolve, 50))
            );

        const { unmount } = render(
            <EditableCell value="Initial" onSave={onSaveMock} />
        );
        fireEvent.click(screen.getByTestId('mock-confirm-btn'));
        unmount();

        // advance timers to resolve the promise
        await act(async () => {
            jest.advanceTimersByTime(50);
            await Promise.resolve(); // allow pending microtasks
        });

        expect(onSaveMock).toHaveBeenCalledWith('new value');
        (console.error as jest.Mock).mockRestore();

        jest.useRealTimers(); // restore real timers
    });

    it('passes validation props through to EditableMetadata', () => {
        const validationFn = jest.fn();
        render(
            <EditableCell
                value="Initial"
                onSave={jest.fn()}
                validationFn={validationFn}
                validationError="Invalid value"
            />
        );

        const lastCallProps = (
            EditableMetadata as jest.Mock
        ).mock.calls.pop()?.[0];
        expect(lastCallProps?.validationFn).toBe(validationFn);
        expect(lastCallProps?.validationError).toBe('Invalid value');
    });
});
