import React from 'react';
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
vi.mock('@tanstack/react-query', () => import('@nexuscrm/test-real-react-query'));
vi.mock('react-router-dom', () => import('@nexuscrm/test-real-router'));
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';

const request = vi.hoisted(() => vi.fn());
const ids = vi.hoisted(() => ({ next: 0 }));
vi.mock('@nexuscrm/host-sdk', async original => ({
    ...await original<Record<string, unknown>>(), generateUUID: () => `create-route-${++ids.next}`,
}));
vi.mock('../src/platform/infrastructure/api/client', () => ({
    apiClient: { getSessionConnectionKind: () => 'backend', request },
}));
vi.mock('../src/platform/infrastructure/api/metadata', () => ({
    metadataAPI: { getSchema: async (name: string) => ({ schema: { name, label: name, fields: [] } }) },
}));
vi.mock('../src/platform/core/navigation/setupPagePaths', () => ({
    buildSetupCapabilityChildPath: (_app: unknown, _capability: unknown, child: string) => `/setup/objects/${child}`,
    tryBuildSetupCapabilityChildPath: (_app: unknown, _capability: unknown, child: string) => `/setup/objects/${child}`,
}));
import { LayoutEditor } from '../src/platform/features/admin/components/LayoutEditor';
import type { StoredAuthoringLayout } from '../src/platform/infrastructure/api/layoutAuthoring';
import { stored, workspace } from './helpers/layoutAuthoringFixture';

const newPath = '/setup/objects/Account/layout/new';
const savedPath = (id: string) => `/setup/objects/Account/layout/${id}`;
const clients: QueryClient[] = [];
beforeEach(() => {
    request.mockReset();
    ids.next = 0;
    vi.spyOn(window, 'confirm').mockReturnValue(true);
});
afterEach(() => { cleanup(); clients.splice(0).forEach(client => client.clear()); vi.restoreAllMocks(); });

function LocationControls() {
    const location = useLocation();
    const navigate = useNavigate();
    return <><output data-testid="location">{location.pathname}</output>
        <output data-testid="location-key">{location.key}</output>
        <button onClick={() => navigate('/elsewhere')}>Leave editor</button>
        <button onClick={() => navigate(newPath)}>Open another new layout</button>
        <button onClick={() => navigate(-1)}>History back</button></>;
}
function renderEditor(path = newPath) {
    const client = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: Infinity } } });
    clients.push(client);
    return render(<QueryClientProvider client={client}>
        <MemoryRouter initialEntries={['/home', path]} initialIndex={1}>
            <LocationControls />
            <Routes><Route path="/setup/objects/:objectApiName/layout/:layoutId" element={<LayoutEditor />} />
                <Route path="*" element={<p>Outside editor</p>} /></Routes>
        </MemoryRouter>
    </QueryClientProvider>);
}

function transport() {
    let revisionNumber = 0;
    const rows = new Map<string, StoredAuthoringLayout>();
    const mutations: Record<string, any>[] = [];
    const control = { reject: false, holdRead: undefined as Promise<void> | undefined };
    const revision = () => `sha256:${String.fromCharCode(97 + revisionNumber).repeat(64)}`;
    request.mockImplementation(async (path: string, input: { body: any }) => {
        if (path.endsWith('/workspace')) return {
            ...workspace, layoutRevision: revision(),
            layouts: [...workspace.layouts, ...[...rows.values()].map(value => ({
                layout_id: value.document.layout_id, object_id: 'Account', name: value.document.name,
                type: 'Detail', is_default: false,
            }))],
        };
        if (path.endsWith('/stored')) {
            await control.holdRead;
            const result = rows.get(input.body.layoutId);
            if (!result) throw new Error('Unknown layout');
            return { ...result, layoutRevision: revision() };
        }
        if (!path.endsWith('/mutate')) throw new Error(`Unexpected request ${path}`);
        if (control.reject) throw new Error('Rejected by server');
        const operation = input.body.operation as Record<string, any>;
        mutations.push(operation);
        const current = operation.create
            ? operation.create.document
            : rows.get(operation.update.layout_id)?.document;
        if (!current) throw new Error('Unknown mutation target');
        const document = operation.create ? current : {
            ...current,
            name: operation.update.changes.name.mode === 'SET' ? operation.update.changes.name.value : current.name,
        };
        revisionNumber++;
        rows.set(document.layout_id, {
            ...stored, layoutId: document.layout_id, objectId: 'Account', layoutRevision: revision(),
            document, requiredCreateFields: [],
        });
        return { changed: true, layoutRevision: revision(), layoutId: document.layout_id };
    });
    return { rows, mutations, control };
}
async function saveNew(name: string) {
    fireEvent.change(await screen.findByDisplayValue('Account Layout'), { target: { value: name } });
    fireEvent.click(screen.getByRole('button', { name: 'Save Layout' }));
}

it('adopts the created identity, reloads it, and updates the same Layout', async () => {
    const server = transport();
    const page = renderEditor();
    await saveNew('First saved layout');
    await waitFor(() => expect(server.rows.size).toBe(1));
    const id = [...server.rows.keys()][0];
    await waitFor(() => expect(screen.getByTestId('location').textContent).toBe(savedPath(id)));
    await screen.findByDisplayValue('First saved layout');
    fireEvent.change(screen.getByDisplayValue('First saved layout'), { target: { value: 'Second saved name' } });
    fireEvent.click(screen.getByRole('button', { name: 'Save Layout' }));
    await waitFor(() => expect(server.rows.get(id)?.document.name).toBe('Second saved name'));
    expect(server.mutations).toHaveLength(2);
    expect(server.mutations[1].update.layout_id).toBe(id);
    page.unmount();
    renderEditor(savedPath(id));
    await screen.findByDisplayValue('Second saved name');
    expect(server.rows.size).toBe(1);
});

it('replaces the completed new route in browser history', async () => {
    transport(); renderEditor(); await saveNew('Saved once');
    await waitFor(() => expect(screen.getByTestId('location').textContent).not.toBe(newPath));
    fireEvent.click(screen.getByRole('button', { name: 'History back' }));
    expect(screen.getByTestId('location').textContent).toBe('/home');
});

it('waits for committed readback before adopting the created route', async () => {
    const server = transport();
    let release!: () => void;
    server.control.holdRead = new Promise<void>(resolve => { release = resolve; });
    renderEditor(); await saveNew('Delayed readback');
    await waitFor(() => expect(request.mock.calls.some(([path]) => path.endsWith('/stored'))).toBe(true));
    expect(screen.getByTestId('location').textContent).toBe(newPath);
    await act(async () => release());
    const id = [...server.rows.keys()][0];
    await waitFor(() => expect(screen.getByTestId('location').textContent).toBe(savedPath(id)));
});

it('does not redirect a different page when readback finishes after leaving', async () => {
    const server = transport();
    let release!: () => void;
    server.control.holdRead = new Promise<void>(resolve => { release = resolve; });
    renderEditor(); await saveNew('Creation before leaving');
    await waitFor(() => expect(request.mock.calls.some(([path]) => path.endsWith('/stored'))).toBe(true));
    fireEvent.click(screen.getByRole('button', { name: 'Leave editor' }));
    await act(async () => release());
    expect(screen.getByTestId('location').textContent).toBe('/elsewhere');
});

it('does not adopt a rejected create', async () => {
    const server = transport(); server.control.reject = true;
    renderEditor(); await saveNew('Rejected layout');
    await screen.findByText('Rejected by server');
    expect(screen.getByTestId('location').textContent).toBe(newPath);
    expect(server.rows.size).toBe(0);
});

it('resets the draft when navigating to a distinct new history entry', async () => {
    transport(); renderEditor();
    const firstKey = screen.getByTestId('location-key').textContent;
    fireEvent.change(await screen.findByDisplayValue('Account Layout'), { target: { value: 'Unsaved first entry' } });
    fireEvent.click(screen.getByRole('button', { name: 'Open another new layout' }));
    await screen.findByDisplayValue('Account Layout');
    expect(screen.getByTestId('location-key').textContent).not.toBe(firstKey);
});
