import { HttpMethod, RequestMatcher, ShorthandRequestMatcher } from './types';
import { BrowserRequest } from './controllers/BrowserController';
import { parse } from 'url';

export function tryJsonParse(data: any): any | undefined {
    try {
        return JSON.parse(data);
    } catch (e) {
        return data;
    }
}

export class TimeoutError extends Error {}

export function waitFor(fn: () => boolean, timeout = 100): Promise<void> {
    const timeStart = Date.now();
    return new Promise((resolve, reject) => {
        const intervalId = setInterval(() => {
            if (fn()) {
                clearInterval(intervalId);
                return resolve();
            } else if (Date.now() - timeStart > timeout) {
                clearInterval(intervalId);
                return reject(
                    new TimeoutError(
                        `waitFor timeout - provided function did not return true in ${timeout}ms`
                    )
                );
            }
        });
    });
}

export function printRequest(request: BrowserRequest): string {
    return `type=${request.type} method=${request.method} url=${request.url}`;
}

export function nth(d: number): string {
    if (d > 3 && d < 21) return `${d}th`;
    switch (d % 10) {
        case 1:
            return `${d}st`;
        case 2:
            return `${d}nd`;
        case 3:
            return `${d}rd`;
        default:
            return `${d}th`;
    }
}

export function addMockByPriority<T extends { options: { priority: number } }>(
    mockArr: T[],
    mock: T
) {
    const index = mockArr.findIndex(
        (item: T) => item.options.priority <= mock.options.priority
    );
    const calculatedIndex = index === -1 ? mockArr.length : index;
    mockArr.splice(calculatedIndex, 0, mock);
    return mockArr;
}

export function createRequestMatcher(
    input: ShorthandRequestMatcher,
    method: HttpMethod
): RequestMatcher {
    if (typeof input === 'string') {
        return {
            method,
            url: input,
        };
    } else {
        return {
            method,
            ...input,
        };
    }
}

export function getCorsHeaders(
    request: BrowserRequest
): Record<string, string> {
    const requestHeaders = request.headers;
    const origin = request.sourceOrigin;
    const headers = {
        'Access-Control-Allow-Origin': origin,
        'Access-Control-Allow-Credentials': 'true',
        'Access-Control-Allow-Methods':
            requestHeaders['access-control-request-method'],
        'Access-Control-Allow-Headers':
            requestHeaders['access-control-request-headers'],
    };

    return headers;
}

/**
 * Sanitize headers object from empty or null values
 * because they make request hang indefinitely in puppeteer
 */
export function sanitizeHeaders(
    headers: Record<string, string | undefined>
): Record<string, string> {
    return Object.keys(headers).reduce<Record<string, string>>((acc, key) => {
        if (Boolean(headers[key])) {
            acc[key] = headers[key] as string;
        }
        return acc;
    }, {});
}

export function printResponse(
    status: number,
    headers: Record<string, string>,
    body: Buffer
): string {
    const headersStr = JSON.stringify(headers);
    const bodyStr = body.toString('utf8');
    return `status=${status} headers=${headersStr} body=${bodyStr}`;
}

export function getOrigin(originUrl?: string) {
    const { protocol, host } = parse(originUrl ?? '');
    if (protocol && host) {
        return `${protocol}//${host}`;
    }
    return '';
}