import { SafeDictionary } from "@insite/client-framework/Common/Types";
import logger from "@insite/client-framework/Logger";
import { fetch, getUrl } from "@insite/client-framework/ServerSideRendering";
import { request } from "@insite/client-framework/Services/ApiService";
import { BasicLanguageModel } from "@insite/client-framework/Store/Data/Pages/PagesActionCreators";
import { ShareEntityModel } from "@insite/client-framework/Types/ApiModels";
import { PageModel } from "@insite/client-framework/Types/PageProps";
import FormData from "form-data";
import { ReadStream } from "fs";
import fsPromises from "fs/promises";
import { getAppDataPath } from "@insite/server-framework/FileHelper";
import { WidgetDefinition } from "@insite/client-framework/Types/ContentItemDefinitions";
import FieldDefinition from "@insite/client-framework/Types/FieldDefinition";

const internalContentUrl = "/api/internal/content/";

export interface SiteGenerationData {
    defaultLanguage: BasicLanguageModel;
    defaultPersonaId: string;
    websiteId: string;
    cmsType: "Classic" | "Spire";
    pageTypeToNodeId: SafeDictionary<string>;
}

export const getSiteGenerationData = () =>
    request<SiteGenerationData>(`${internalContentUrl}siteGenerationData`, "GET");

export const getAutoUpdateData = (nodeId: string) =>
    request<{
        pageId: string;
        templateHash: string;
    } | null>(`${internalContentUrl}autoUpdateData?nodeId=${nodeId}`, "GET");

export async function saveInitialPages(pages: PageModel[]) {
    const tokenData = await getSpireClientAccessToken();
    const headers = {
        "Content-Type": "application/json",
        Authorization: `Bearer ${tokenData.access_token}`,
    };

    return request<PageModel[]>(`${internalContentUrl}saveInitialPages`, "POST", headers, JSON.stringify(pages));
}

export async function restoreInitialPages(pages: PageModel[]) {
    const tokenData = await getSpireClientAccessToken();
    const headers = {
        "Content-Type": "application/json",
        Authorization: `Bearer ${tokenData.access_token}`,
    };

    return request<PageModel[]>(`${internalContentUrl}restoreInitialPages`, "POST", headers, JSON.stringify(pages));
}

export async function saveTranslations(stream: ReadStream, authorization: string) {
    const userHeaders = {
        Authorization: authorization,
    };

    const formData = new FormData();

    formData.append("file", stream);

    return fetch(`${internalContentUrl}ImportTranslations`, {
        headers: formData.getHeaders(userHeaders),
        method: "POST",
        // RequestInit and FormData are not the same shape,
        // so TypeScript complains. Using `any` to more or less
        // ignore TypeScript and just let the JavaScript work.
        body: formData as any,
    });
}

type ShareEntityGenerateFromWebpageModel = {
    html: string;
    username: string;
} & Omit<ShareEntityModel, "uri" | "properties">;

export async function shareEntityGenerateFromWebpage(model: ShareEntityGenerateFromWebpageModel) {
    const tokenData = await getSpireClientAccessToken();
    const headers = {
        Authorization: `Bearer ${tokenData.access_token}`,
    };
    return fetch(`${internalContentUrl}ShareEntityGenerateFromWebpage`, {
        method: "POST",
        headers,
        body: JSON.stringify(model),
    });
}

async function getSpireClientAccessToken() {
    const data = new URLSearchParams();
    data.append("grant_type", "client_credentials");
    data.append("scope", "isc_admin_api");

    const defaultClientSecret = "E445C079-3A08-455B-A155-AEEB5078FB92";
    const spireClientSecret = (process.env.SPIRE_CLIENT_SECRET ?? defaultClientSecret).trim();
    logger.debug(spireClientSecret);
    if (spireClientSecret.toUpperCase() === defaultClientSecret && IS_PRODUCTION) {
        if (process.env.ACCEPT_RISKS_OF_USING_DEFAULT_SPIRE_CLIENT_SECRET) {
            logger.warn(
                "Spire is running in production but did not have an environment variable for SPIRE_CLIENT_SECRET defined. It is using the default insecure client secret. Consult documentation for the procedure to configure this securely.",
            );
        } else {
            throw new Error(
                "Spire is running in production but did not have an environment variable for SPIRE_CLIENT_SECRET defined. Consult documentation for the procedure to configure this securely or set the environment variable ACCEPT_RISKS_OF_USING_DEFAULT_SPIRE_CLIENT_SECRET to true.",
            );
        }
    }

    const encodedAuthorization = Buffer.from(`spire:${spireClientSecret}`).toString("base64");

    let OIDC_AUTHORITY = process.env.OIDC_AUTHORITY ?? process.env.ISC_API_URL;
    const url = getUrl()!;
    // this handles the situation where we are listening on two ports and makes sure we call netcore when needed
    if (url.port === process.env.SECONDARY_INGRESS_PORT) {
        OIDC_AUTHORITY = url?.origin;
    }

    const response = await fetch(`${OIDC_AUTHORITY}/identity/connect/token`, {
        method: "POST",
        body: data.toString(),
        headers: new Headers({
            "content-type": "application/x-www-form-urlencoded",
            Authorization: `Basic ${encodedAuthorization}`,
        }),
    });

    if (response.status !== 200) {
        throw new Error(
            JSON.stringify(
                `Spire was not able to authenticate itself with Configured Commerce. This is could indicate a problem with mismatched SpireClientSecrets. \n Response: ${JSON.stringify(
                    response,
                )}`,
            ),
        );
    }

    return response.json() as Promise<{
        readonly access_token: string;
    }>;
}

export async function getTranslationDictionary(languageCode: string) {
    try {
        const response = await fetch(`${internalContentUrl}TranslationDictionary?languageCode=${languageCode}`);

        const index = response.headers.get("content-type")?.indexOf("application/json");
        if (index === -1) {
            throw new Error(
                `Received unexpected content-type when loading translation dictionary. Response received: ${await response.text()}`,
            );
        }

        return (await response.json()) as SafeDictionary<string>;
    } catch (ex) {
        logger.error(`Unable to load translation dictionary. Error: ${ex}`);
        return {};
    }
}

export async function getSiteMessages(languageCode: string) {
    try {
        const response = await fetch(`${internalContentUrl}SiteMessages?languageCode=${languageCode}`);
        const index = response.headers.get("content-type")?.indexOf("application/json");
        if (index === -1) {
            throw new Error(
                `Received unexpected content-type when loading site messages. Response received: ${await response.text()}`,
            );
        }

        return (await response.json()) as SafeDictionary<string>;
    } catch (ex) {
        logger.error(`Unable to load site messages. Error: ${ex}`);
        return {};
    }
}

export async function readAsyncWidgetFieldDefinitions() {
    return fsPromises
        .readFile(`${getAppDataPath()}/asyncWidgetFieldDefinitions.json`, "utf8")
        .then((data: string) => {
            return JSON.parse(data) as SafeDictionary<WidgetDefinition<FieldDefinition>>;
        })
        .catch(reason => {
            logger.warn(
                `Unable to read file for async widget field definitions. Defaulting to an empty object.`,
                reason,
            );
            return {} as SafeDictionary<WidgetDefinition<FieldDefinition>>;
        });
}
