import React from 'react';
import { spawn } from 'node:child_process';
import { once } from 'node:events';
import path from 'node:path';
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import { apiClient } from '../src/platform/infrastructure/api/client';
import { hostRoutes, setHostSessionTransportBridgeForTest } from '@nexuscrm/host-sdk';
import { STORAGE_KEYS } from '../src/platform/infrastructure/storage/storageKeys';

const session = vi.hoisted(() => ({ frontendAppId: 'crm' as string | null }));

vi.mock('react-router-dom', () => import('@nexuscrm/test-real-router'));
vi.mock('@nexuscrm/host-sdk', async original => ({
    ...await original<typeof import('@nexuscrm/host-sdk')>(),
    useApp: () => ({ currentFrontendAppId: session.frontendAppId, refreshApps: async () => {} }),
}));
vi.mock('../src/platform/infrastructure/api/metadata', async original => {
    const actual = await original<typeof import('../src/platform/infrastructure/api/metadata')>();
    return { ...actual, metadataAPI: { ...actual.metadataAPI,
        getSchemas: async () => ({ schemas: [] }), getDashboards: async () => ({ dashboards: [] }),
    } };
});
vi.mock('../src/platform/features/studio/components/StudioObjectEditor', () => ({ StudioObjectEditor: () => null }));
vi.mock('../src/platform/features/studio/components/StudioDashboardEditor', () => ({ StudioDashboardEditor: () => null }));
vi.mock('../src/platform/features/studio/components/StudioPermissions', () => ({ StudioPermissions: () => null }));
import { metadataAPI } from '../src/platform/infrastructure/api/metadata';
import { AppManager } from '../src/platform/pages/AppManager';
import { AppStudio } from '../src/platform/pages/AppStudio';
import { clearAppNavigationCapabilitiesForTest, registerAppNavigationCapabilities } from '../src/platform/core/navigation/appNavigationCapabilities';

beforeEach(() => {
    session.frontendAppId = 'crm';
    const values = new Map<string, string>();
    vi.stubGlobal('localStorage', {
        getItem: (key: string) => values.get(key) ?? null,
        setItem: (key: string, value: string) => values.set(key, value),
        removeItem: (key: string) => values.delete(key), clear: () => values.clear(),
        key: (index: number) => [...values.keys()][index] ?? null,
        get length() { return values.size; },
    });
    apiClient.clearSession();
});
afterEach(() => { hostRoutes.clearForTest(); clearAppNavigationCapabilitiesForTest(); setHostSessionTransportBridgeForTest(undefined); apiClient.clearSession(); localStorage.clear(); vi.unstubAllGlobals(); });

it('keeps settings independent of navigation admission failures', async () => {
    // The actual Go AppService supplies both UI Apps and administrative reads.
    // Replacing getApps with a successful stub would conceal the original bug.
    const httpFetch = globalThis.fetch;
    const binary = process.env.NEXUSCRM_STUDIO_HTTP_FIXTURE_BINARY;
    if (!binary) throw new Error('Run npm --prefix frontend run check:appservice-integration to prepare the AppService fixture.');
    const fixture = spawn(binary, ['-test.v', '-test.timeout=90s', '-test.run=^TestStudioApplicationDefinitionsHTTPFixture$'], {
        cwd: path.resolve(__dirname, '../../../appservices/sfcrm'),
        env: { ...process.env, GOWORK: 'off', NEXUSCRM_STUDIO_HTTP_FIXTURE: '1' },
        stdio: ['pipe', 'pipe', 'pipe'],
    });
    const closed = once(fixture, 'close');
    let output = '';
    const ready = new Promise<Record<string, string>>((resolve, reject) => {
        const timeout = setTimeout(() => { fixture.kill(); reject(new Error(`AppService fixture timed out: ${output}`)); }, 60000);
        fixture.on('error', error => { clearTimeout(timeout); reject(error); });
        fixture.stderr.on('data', chunk => { output = (output + chunk).slice(-16000); });
        fixture.stdout.on('data', chunk => {
            output = (output + chunk).slice(-16000);
            const match = output.match(/STUDIO_HTTP_FIXTURE=(\{[^\n]+\})/);
            if (match) { clearTimeout(timeout); resolve(JSON.parse(match[1])); }
        });
        fixture.on('close', code => { clearTimeout(timeout); reject(new Error(`AppService fixture exited ${code}: ${output}`)); });
    });
    try {
        const servers = await ready;
        hostRoutes.register('sfcrm.studio', { id: 'sfcrm.studio', path: '/studio/:appId', slot: 'standalone', component: () => null });
        registerAppNavigationCapabilities('sfcrm', [{ capability: 'studio', routeId: 'sfcrm.studio' }]);
        for (const status of [403, 503]) {
            localStorage.setItem(STORAGE_KEYS.CONNECTION_MODE, 'backend');
            apiClient.setSession('backend-test-token', '');
            setHostSessionTransportBridgeForTest({ prepareRequest: request => ({
                route: 'host-backend', credentials: 'include',
                headers: { ...request.headers, Authorization: 'Bearer backend-test-token' }, consumeResponse: () => {},
            }) });
            const requests: string[] = [];
            vi.stubGlobal('fetch', (url: string | URL | Request, options: RequestInit) => {
                const requested = new URL(String(url), servers[status]);
                requests.push(requested.pathname);
                return httpFetch(servers[status] + requested.pathname + requested.search, options);
            });
            await expect(metadataAPI.getApps()).rejects.toMatchObject({ status });
            requests.length = 0;
            render(<MemoryRouter initialEntries={['/manager']}><Routes>
                <Route path="/manager" element={<AppManager />} />
                <Route path="/studio/:appId" element={<AppStudio dashboardWidgets={{} as any} />} />
            </Routes></MemoryRouter>);
            fireEvent.click(await screen.findByRole('link', { name: 'Open in Studio' }));
            await screen.findByText(/Navigation editing is unavailable/);
            expect(screen.getByRole('button', { name: 'Save navigation' })).toBeDisabled();
            fireEvent.click(screen.getByRole('button', { name: 'App Settings' }));
            expect(await screen.findByLabelText('App Name')).toHaveValue('Sales');
            expect(screen.getByLabelText('App Name')).toBeDisabled();
            expect(screen.queryByRole('button', { name: 'Save settings' })).not.toBeInTheDocument();
            expect(requests).toEqual([
                '/services/data/v63.0/metadata/applications',
                '/services/data/v63.0/metadata/applications',
                '/services/data/v63.0/app-navigation/Sales',
            ]);
            cleanup();
        }
    } finally {
        fixture.stdin.end();
        const killTimeout = setTimeout(() => fixture.kill(), 10000);
        const [code] = await closed;
        clearTimeout(killTimeout);
        if (code !== 0) throw new Error(`AppService fixture failed ${code}: ${output}`);
    }
}, 80000);
