/** @vitest-environment jsdom */
/**
 * Replay actually starts when an already-consented shopper opens the modal.
 *
 * This is the ordinary case — most shoppers on a store whose CMP already
 * granted never fire a consent-change event at all — and it is the one no
 * existing test covered. `replay.test.ts` drives `startReplay` directly with an
 * artificial `stillAllowed: () => true`, which is precisely the condition the
 * widget was failing to satisfy.
 *
 * The real module is used here, not a mock: what has to be proved is that
 * `startSessionRecording` is *reached*, and only once the modal overlay exists.
 * Recording before the overlay is in the DOM would take a snapshot in which the
 * confinement target does not exist — every node blocked, a recording of
 * nothing, and no error anywhere.
 */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
import { App } from './App';
import { MODAL_OVERLAY_CLASS, isRecording, resetReplayForTests } from './replay';
import { en } from './i18n/en';
import type { WidgetConfig } from './types';

vi.mock( './imageUtils', () => ( {
	fetchImageAsBase64: vi.fn().mockResolvedValue( 'data:image/png;base64,G' ),
	downloadImage: vi.fn(),
	fileToBase64: vi.fn(),
	resizeAvatarImage: vi.fn(),
	isValidImageFile: () => true,
} ) );

const config = {
	apiBaseUrl: 'https://brain.example.com/api/storefront/tryon',
	publishableKey: 'pk_test',
	productImage: 'https://cdn.example.com/shirt.jpg',
	productTitle: 'Linen Shirt',
	productId: '555',
	locale: 'en',
	analytics: {
		replayKey: 'phc_browser',
		ingestHost: 'https://ingest.aisthetix.fyi',
		replayEnabled: true,
		environment: 'staging',
		storeId: 'store_42',
	},
} as unknown as WidgetConfig;

interface FakeSdk {
	init: ReturnType< typeof vi.fn >;
	startSessionRecording: ReturnType< typeof vi.fn >;
	stopSessionRecording: ReturnType< typeof vi.fn >;
	register: ReturnType< typeof vi.fn >;
	opt_out_capturing: ReturnType< typeof vi.fn >;
	opt_in_capturing: ReturnType< typeof vi.fn >;
}

let sdk: FakeSdk;
/** Whether the overlay was in the DOM at the moment recording started. */
let overlayAtStart: boolean | null;
/** The injected script tag, so the test decides when the SDK "arrives". */
let pending: HTMLScriptElement | null;

function interceptScriptInjection() {
	pending = null;
	const original = document.head.appendChild.bind( document.head );
	vi.spyOn( document.head, 'appendChild' ).mockImplementation( ( ( node: Node ) => {
		if ( node instanceof HTMLScriptElement ) {
			pending = node;
			return node;
		}
		return original( node );
	} ) as typeof document.head.appendChild );
}

function deliverSdk() {
	( window as unknown as { posthog?: FakeSdk } ).posthog = sdk;
	pending!.onload?.( new Event( 'load' ) );
}

/** The bootstrap's answer, which is the only consent signal this widget has. */
function setConsent( allowed: boolean ) {
	( window as unknown as { aisthetixProductAnalyticsAllowed?: () => boolean } )
		.aisthetixProductAnalyticsAllowed = () => allowed;
}

beforeEach( () => {
	localStorage.clear();
	sessionStorage.clear();
	document.body.innerHTML = '';
	resetReplayForTests();
	delete ( window as unknown as { posthog?: unknown } ).posthog;
	overlayAtStart = null;
	sdk = {
		// posthog-js's `init(key, options, name)` returns the NAMED instance we
		// record through — never the merchant's default one.
		init: vi.fn( () => sdk ),
		startSessionRecording: vi.fn( () => {
			overlayAtStart = document.querySelector( `.${ MODAL_OVERLAY_CLASS }` ) !== null;
		} ),
		stopSessionRecording: vi.fn(),
		register: vi.fn(),
		opt_out_capturing: vi.fn(),
		opt_in_capturing: vi.fn(),
	};
	setConsent( true );
} );

afterEach( () => {
	cleanup();
	document.body.innerHTML = '';
	delete ( window as unknown as { aisthetixProductAnalyticsAllowed?: unknown } )
		.aisthetixProductAnalyticsAllowed;
	vi.restoreAllMocks();
} );

async function openModal() {
	const trigger = document.createElement( 'button' );
	trigger.id = 'aisthetix-tryon-trigger';
	document.body.appendChild( trigger );
	render( <App config={ config } /> );
	fireEvent.click( trigger );
	// The overlay is the widget being open, and it is also what the recording is
	// confined to — so waiting on it is waiting on exactly the right thing.
	await waitFor( () =>
		expect( document.querySelector( `.${ MODAL_OVERLAY_CLASS }` ) ).not.toBeNull()
	);
	await waitFor( () => expect( screen.getAllByText( en.upload.title ).length ).toBeGreaterThan( 0 ) );
}

describe( 'an already-consented shopper opening the modal', () => {
	it( 'starts recording — the case no consent-change event ever announces', async () => {
		interceptScriptInjection();
		await openModal();

		await waitFor( () => expect( pending, 'the recorder was never even requested' ).not.toBeNull() );
		deliverSdk();

		await waitFor( () => expect( sdk.startSessionRecording ).toHaveBeenCalledTimes( 1 ) );
		expect( isRecording() ).toBe( true );
	} );

	it( 'does not record before the overlay it is confined to exists', async () => {
		interceptScriptInjection();
		await openModal();
		await waitFor( () => expect( pending ).not.toBeNull() );
		deliverSdk();

		await waitFor( () => expect( sdk.startSessionRecording ).toHaveBeenCalled() );
		expect( overlayAtStart, 'the confinement target must be in the DOM first' ).toBe( true );
	} );

	it( 'does not start when the shopper closes the modal before the SDK arrives', async () => {
		interceptScriptInjection();
		await openModal();
		await waitFor( () => expect( pending ).not.toBeNull() );

		fireEvent.click( screen.getByLabelText( 'Close' ) );
		await waitFor( () =>
			expect( document.querySelector( `.${ MODAL_OVERLAY_CLASS }` ) ).toBeNull()
		);
		deliverSdk();

		await new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
		expect( sdk.startSessionRecording ).not.toHaveBeenCalled();
		expect( isRecording() ).toBe( false );
	} );

	it( 'requests nothing at all when the shopper has not consented', async () => {
		setConsent( false );
		interceptScriptInjection();
		await openModal();

		await new Promise( ( resolve ) => setTimeout( resolve, 0 ) );
		expect( pending, 'loading the SDK is itself a request they never agreed to' ).toBeNull();
	} );
} );
