import { Page } from 'playwright';
import { AxiosResponse, AxiosInstance } from 'axios';
import fs from 'fs';
import path from 'path';

interface MockResponse {
    status: number;
    body: any;
    headers?: { [key: string]: string };
}

class MockApiManager {
    private static instance: MockApiManager;
    private mockResponses: { [url: string]: MockResponse } = {};
    private constructor() { }
    public static referralNumber: string;
    public static referralSequenceNumber: string;
    public static referralCorrelationId: string;
    public static savedSessionId: string;

    public static getInstance(): MockApiManager {
        if (!MockApiManager.instance) {
            MockApiManager.instance = new MockApiManager();
        }
        return MockApiManager.instance;
    }

    /**
     * Loads mock responses from a configuration file.
     * @param scenario - The scenario to load mock responses for.
     */
    private loadMockResponses(scenario: string): void {
        const mockResponsesDir = path.resolve(__dirname, '../../tests/mockResponses');
        const configFilePath = path.join(mockResponsesDir, 'mockResponsesConfig.json');

        if (fs.existsSync(configFilePath)) {
            const config = JSON.parse(fs.readFileSync(configFilePath, 'utf-8'));
            const scenarioConfig = config[scenario];
            const commonConfig = config['common'];

            for (const endpoint in scenarioConfig) {
                this.loadMockResponse(endpoint, scenarioConfig[endpoint], mockResponsesDir);
            }

            for (const endpoint in commonConfig) {
                if (!this.mockResponses[endpoint]) {
                    this.loadMockResponse(endpoint, commonConfig[endpoint], mockResponsesDir);
                }
            }
        } else {
            console.error(`Mock responses configuration file not found at path: ${configFilePath}`);
        }
    }

    /**
     * Loads a single mock response from a file.
     * @param endpoint - The endpoint to load the mock response for.
     * @param mockFilePath - The path to the mock response file.
     * @param mockResponsesDir - The directory containing the mock response files.
     */
    private loadMockResponse(endpoint: string, mockFilePath: string, mockResponsesDir: string): void {
        const filePath = path.join(mockResponsesDir, mockFilePath);
        if (fs.existsSync(filePath)) {
            const mockResponse = fs.readFileSync(filePath, 'utf-8');
            try {
                this.mockResponses[endpoint] = JSON.parse(mockResponse);
            } catch (error) {
                console.error(`Error parsing mock response for endpoint: ${endpoint}`, error);
            }
        } else {
            console.warn(`Mock response file${filePath} not found for endpoint: ${endpoint}`);
        }
    }

    /**
     * Gets the mocked API response for the given endpoint.
     * @param endpoint - The endpoint to get the mocked response for.
     * @param scenario - The scenario to load mock responses for.
     * @returns The mocked response or null if not found.
     */
    public getMockedApiResponse(endpoint: string, scenario: string): MockResponse | null {
        if (!this.mockResponses[endpoint]) {
            this.loadMockResponses(scenario);
        }
        return this.mockResponses[endpoint] || null;
    }

    /**
     * Mocks the API response for the given endpoint.
     * @param page - The Playwright page object.
     * @param endpoint - The endpoint to mock.
     * @param scenario - The scenario to load mock responses for.
     * @param mockTestingFlag - Flag to enable or disable mocking.
     */
    public async mockApiResponse(page: Page, scenario: string, mockTestingFlag: boolean): Promise<void> {
        if (!mockTestingFlag) {
            console.info("mockTestingFlag is false/undefined. so proceeding with original requests.")
            return;
        }

        if (!scenario) {
            console.error('Invalid scenario provided for mocking.');
            return;
        }

        this.loadMockResponses(scenario);

        for (const endpoint in this.mockResponses) {
            await page.route(`**/${endpoint}`, async (route) => {
                const response = this.mockResponses[endpoint];

                if (response) {
                    route.fulfill({
                        status: response.status || 200,
                        body: JSON.stringify(response.body || response),
                        headers: response.headers || { 'Content-Type': 'application/json' },
                    });
                }
                console.info("Mocking Endpoint: " + endpoint)
            });
        }
    }

    public async updateReferralDetails(endpoint: string, response: MockResponse): Promise<MockResponse> {
        try {
            response.body.referralCorrelationId = MockApiManager.referralCorrelationId;
            response.body.referralNumber = MockApiManager.referralNumber;
        } catch (error) {

        }

        return response;
    }
    public async getReferralDetails(endpoint: string, response: MockResponse) {
        if (endpoint.includes('order/save-session/iss')) {
            MockApiManager.referralNumber = response.body.referralNumber;
            MockApiManager.referralSequenceNumber = response.body.referralSequenceNumber;
            MockApiManager.referralCorrelationId = response.body.referralCorrelationId;
            MockApiManager.savedSessionId = response.body.sessionId;
        }

    }
}


// Export the instance 
export const mockApiManager = MockApiManager.getInstance();

export async function httpGet<T>(client: AxiosInstance, url: string): Promise<T> {
    const [isSuccess, response] = await handleHttp(client.get<T>(url));
    if (isSuccess) {
        return response;
    }

    console.error(`An error occurred calling GET ${url}\nError:${response}`);
    throw response;
}

export async function httpPost<T, D>(client: AxiosInstance, url: string, data: D): Promise<T> {
    const [isSuccess, response] = await handleHttp(client.post<T>(url, data));
    if (isSuccess) {
        return response;
    }

    console.error(`An error occurred calling POST ${url}\nError:${response}`);
    throw response;
}

export function handleHttp<T>(request: Promise<AxiosResponse<T>>): Promise<[isSuccess: true, data: T] | [isSuccess: false, error: Error]> {
    return request.then(data => {
        return [true, data.data] as [true, T]
    }).catch((error: Error) => {
        return [false, error] as [false, Error]
    })
}

export function buildQueryString<T>(data: T): URLSearchParams {
    const params: Record<string, string> = {};
    for (const key in data) {
        const value = data[key];
        params[key] = `${value}`;
    }
    return new URLSearchParams(params);
}

export function forceAPIError(page: Page, endpoint: string) {
    page.route('**/*', (route) => {
        return route.request().url().includes(endpoint)
            ? route.abort()
            : route.continue()
    });
}
