import { fireEvent, render, screen } from '@testing-library/react';
import { act } from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
    LeftMenuProvider,
    type LeftMenuContent,
    useLeftMenu,
} from '../../src/contexts/LeftMenuContext';

function buildDefaultContent(label = 'Default menu'): LeftMenuContent {
    return {
        ariaLabel: label,
        render: ({ collapsed }) => <div>{collapsed ? `${label} (collapsed)` : `${label} (expanded)`}</div>,
    };
}

function setViewportWidth(width: number) {
    Object.defineProperty(window, 'innerWidth', {
        configurable: true,
        writable: true,
        value: width,
    });
    Object.defineProperty(window, 'matchMedia', {
        configurable: true,
        writable: true,
        value: undefined,
    });
}

function renderLeftMenuHarness(initialPathname = '/users') {
    let currentPathname = initialPathname;
    let currentValue: ReturnType<typeof useLeftMenu> | null = null;
    const defaultContent = buildDefaultContent();

    function Probe() {
        currentValue = useLeftMenu();
        const renderState = {
            collapsed: currentValue.collapsed,
            mobileOpen: currentValue.mobileOpen,
            isDesktop: window.innerWidth >= 1024,
            closeMenu: currentValue.closeMenu,
        };

        return <div data-testid="left-menu-content">{currentValue.content.render(renderState)}</div>;
    }

    function Wrapper({ pathname }: Readonly<{ pathname: string }>) {
        return (
            <LeftMenuProvider defaultContent={defaultContent} closeOnPathname={pathname}>
                <Probe />
            </LeftMenuProvider>
        );
    }

    const rendered = render(<Wrapper pathname={currentPathname} />);

    return {
        getCurrent() {
            if (!currentValue) {
                throw new Error('Left menu context value not initialized');
            }
            return currentValue;
        },
        reroute(nextPathname: string) {
            currentPathname = nextPathname;
            rendered.rerender(<Wrapper pathname={currentPathname} />);
        },
    };
}

describe('LeftMenuContext', () => {
    beforeEach(() => {
        localStorage.removeItem('authSidebarWidth');
        localStorage.removeItem('authSidebarCollapsed');
        setViewportWidth(1024);
    });

    it('throws when useLeftMenu is used outside provider', () => {
        function Invalid() {
            useLeftMenu();
            return null;
        }

        expect(() => render(<Invalid />)).toThrow('useLeftMenu must be used within LeftMenuProvider');
    });

    it('supports desktop collapse/expand/toggle semantics', () => {
        const harness = renderLeftMenuHarness('/users');

        expect(harness.getCurrent().collapsed).toBe(false);

        act(() => {
            harness.getCurrent().closeMenu();
        });
        expect(harness.getCurrent().collapsed).toBe(true);

        act(() => {
            harness.getCurrent().openMenu();
        });
        expect(harness.getCurrent().collapsed).toBe(false);

        act(() => {
            harness.getCurrent().toggleMenu();
        });
        expect(harness.getCurrent().collapsed).toBe(true);
    });

    it('restores collapsed state from storage and clamps persisted width', () => {
        localStorage.setItem('authSidebarCollapsed', '1');
        localStorage.setItem('authSidebarWidth', '999');
        const harness = renderLeftMenuHarness('/users');

        expect(harness.getCurrent().collapsed).toBe(true);
        expect(harness.getCurrent().desktopMenuStyle['--auth-sidebar-width']).toBe('56px');
    });

    it('locks body scroll on mobile open and unlocks on close', () => {
        setViewportWidth(768);

        const harness = renderLeftMenuHarness('/users');

        act(() => {
            harness.getCurrent().openMenu();
        });
        expect(harness.getCurrent().mobileOpen).toBe(true);
        expect(document.body.style.overflow).toBe('hidden');

        act(() => {
            harness.getCurrent().closeMenu();
        });
        expect(harness.getCurrent().mobileOpen).toBe(false);
        expect(document.body.style.overflow).toBe('');
    });

    it('reads, clamps, and persists sidebar width through pointer resize', async () => {
        localStorage.setItem('authSidebarWidth', '999');
        const harness = renderLeftMenuHarness('/users');

        expect(harness.getCurrent().desktopMenuStyle['--auth-sidebar-width']).toBe('420px');

        const preventDefault = vi.fn();
        act(() => {
            harness.getCurrent().startResize({
                clientX: 420,
                preventDefault,
            } as never);
        });

        act(() => {
            fireEvent.pointerMove(window, { clientX: -1000 });
            fireEvent.pointerUp(window);
        });

        await act(async () => {
            await Promise.resolve();
        });

        expect(preventDefault).toHaveBeenCalledTimes(1);
        expect(localStorage.getItem('authSidebarWidth')).toBe('220');
        expect(harness.getCurrent().desktopMenuStyle['--auth-sidebar-width']).toBe('220px');
    });

    it('accepts custom content and resets to default on route changes', () => {
        setViewportWidth(768);
        const harness = renderLeftMenuHarness('/users');

        act(() => {
            harness.getCurrent().openMenu();
            harness.getCurrent().setMenuContent({
                ariaLabel: 'Custom menu',
                render: () => <div>Custom menu content</div>,
            });
        });

        expect(harness.getCurrent().mobileOpen).toBe(true);
        expect(screen.getByText('Custom menu content')).toBeInTheDocument();

        harness.reroute('/posts');

        expect(harness.getCurrent().mobileOpen).toBe(false);
        expect(screen.queryByText('Custom menu content')).not.toBeInTheDocument();
        expect(screen.getByText('Default menu (expanded)')).toBeInTheDocument();
    });

    it('applies provided content in openMenu/toggleMenu and toggles mobile open state', () => {
        setViewportWidth(768);
        const harness = renderLeftMenuHarness('/users');

        act(() => {
            harness.getCurrent().openMenu({
                render: () => <div>Open payload</div>,
            });
        });
        expect(screen.getByText('Open payload')).toBeInTheDocument();
        expect(harness.getCurrent().mobileOpen).toBe(true);

        act(() => {
            harness.getCurrent().toggleMenu({
                render: () => <div>Toggle payload</div>,
            });
        });
        expect(screen.getByText('Toggle payload')).toBeInTheDocument();
        expect(harness.getCurrent().mobileOpen).toBe(false);
    });

    it('updates default content only when current content is still default', () => {
        function Harness() {
            const menu = useLeftMenu();
            const state = {
                collapsed: menu.collapsed,
                mobileOpen: menu.mobileOpen,
                isDesktop: true,
                closeMenu: menu.closeMenu,
            };

            return (
                <div>
                    <button type="button" onClick={() => menu.setMenuContent({ render: () => <div>Custom</div> })}>
                        custom
                    </button>
                    <div data-testid="content">{menu.content.render(state)}</div>
                </div>
            );
        }

        function Wrapper({ label }: Readonly<{ label: string }>) {
            return (
                <LeftMenuProvider defaultContent={buildDefaultContent(label)}>
                    <Harness />
                </LeftMenuProvider>
            );
        }

        const rendered = render(<Wrapper label="Menu A" />);
        expect(screen.getByTestId('content')).toHaveTextContent('Menu A (expanded)');

        rendered.rerender(<Wrapper label="Menu B" />);
        expect(screen.getByTestId('content')).toHaveTextContent('Menu B (expanded)');

        fireEvent.click(screen.getByRole('button', { name: 'custom' }));
        expect(screen.getByTestId('content')).toHaveTextContent('Custom');

        rendered.rerender(<Wrapper label="Menu C" />);
        expect(screen.getByTestId('content')).toHaveTextContent('Custom');
    });
});
