export const HOST = 'https://www.remotion.pro';
import type {NoReactInternals} from 'remotion/no-react';
import {isNetworkError} from './is-network-error';

const DEFAULT_MAX_RETRIES = 3;

export const exponentialBackoffMs = (attempt: number): number => {
	return 1000 * 2 ** (attempt - 1);
};

export const sleep = (ms: number): Promise<void> => {
	return new Promise((resolve) => {
		setTimeout(resolve, ms);
	});
};

type ApiResponse =
	| {
			success: true;
			billable: boolean;
			classification: UsageEventClassification;
	  }
	| {
			success: false;
			error: string;
	  };

export type RegisterUsageEventResponse = {
	billable: boolean;
	classification: UsageEventClassification;
};

type UsageEventType =
	| 'web-render'
	| 'cloud-render'
	/**
	 * @deprecated Use `'web-render'` instead
	 */
	| 'webcodec-conversion';

export type UsageEventClassification = 'billable' | 'development' | 'failed';

export type EitherApiKeyOrLicenseKey =
	true extends typeof NoReactInternals.ENABLE_V5_BREAKING_CHANGES
		? {
				licenseKey: string | null;
			}
		:
				| {
						/**
						 * @deprecated Use `licenseKey` instead
						 */
						apiKey: string | null;
				  }
				| {
						licenseKey: string | null;
				  };

type RegisterUsageEventMandatoryOptions = {
	host: string | null;
	succeeded: boolean;
	event: UsageEventType;
};

type OptionalRegisterUsageEventOptional = {
	isStill: boolean;
	isProduction: boolean;
};

type InternalRegisterUsageEventOptions = RegisterUsageEventMandatoryOptions &
	OptionalRegisterUsageEventOptional & {licenseKey: string | null};

type RegisterUsageEventOptions = RegisterUsageEventMandatoryOptions &
	EitherApiKeyOrLicenseKey &
	Partial<OptionalRegisterUsageEventOptional>;

export const internalRegisterUsageEvent = async ({
	host,
	succeeded,
	event,
	isStill,
	isProduction,
	licenseKey,
}: InternalRegisterUsageEventOptions): Promise<RegisterUsageEventResponse> => {
	let lastError: Error | undefined;
	const totalAttempts = DEFAULT_MAX_RETRIES + 1;

	for (let attempt = 1; attempt <= totalAttempts; attempt++) {
		const abortController = new AbortController();
		const timeout = setTimeout(() => {
			abortController.abort();
		}, 10000);

		try {
			const res = await fetch(`${HOST}/api/track/register-usage-point`, {
				method: 'POST',
				body: JSON.stringify({
					event,
					apiKey: licenseKey,
					host,
					succeeded,
					isStill,
					isProduction,
				}),
				headers: {
					'Content-Type': 'application/json',
				},
				signal: abortController.signal,
			});
			clearTimeout(timeout);

			const json = (await res.json()) as ApiResponse;

			if (json.success) {
				return {
					billable: json.billable,
					classification: json.classification,
				};
			}

			if (!res.ok) {
				throw new Error(json.error);
			}

			throw new Error(
				`Unexpected response from server: ${JSON.stringify(json)}`,
			);
		} catch (err) {
			clearTimeout(timeout);

			const error = err as Error;
			const isTimeout = error.name === 'AbortError';
			const isRetryable = isNetworkError(error) || isTimeout;

			if (!isRetryable) {
				throw err;
			}

			lastError = isTimeout
				? new Error('Request timed out after 10 seconds')
				: error;

			if (attempt < totalAttempts) {
				const backoffMs = exponentialBackoffMs(attempt);
				// eslint-disable-next-line no-console
				console.log(
					`Failed to send usage event (attempt ${attempt}/${totalAttempts}), retrying in ${backoffMs}ms...`,
					err,
				);
				await sleep(backoffMs);
			}
		}
	}

	throw lastError;
};

export const registerUsageEvent = (
	options: RegisterUsageEventOptions,
): Promise<RegisterUsageEventResponse> => {
	const licenseKey = 'licenseKey' in options ? options.licenseKey : null;
	const apiKey = 'apiKey' in options ? options.apiKey : null;

	return internalRegisterUsageEvent({
		...options,
		isStill: options.isStill ?? false,
		isProduction: options.isProduction ?? true,
		licenseKey: licenseKey ?? apiKey ?? null,
	});
};
