import http from "http";
import https from "https";
import fs from "fs";
import path from "path";
import encrypt from "../package-shared/functions/dsql/encrypt";

import userAuth from "./user-auth";
import grabHostNames from "../package-shared/utils/grab-host-names";
import apiReauthUser from "../package-shared/functions/api/users/api-reauth-user";
import {
    writeAuthFile,
    deleteAuthFile,
} from "../package-shared/functions/backend/auth/write-auth-files";
import getAuthCookieNames from "../package-shared/functions/backend/cookies/get-auth-cookie-names";
import { APILoginFunctionReturn } from "../package-shared/types";

type Param = {
    key?: string;
    database?: string;
    response?: http.ServerResponse;
    request?: http.IncomingMessage;
    level?: "deep" | "normal";
    encryptionKey?: string;
    encryptionSalt?: string;
    additionalFields?: string[];
    encryptedUserString?: string;
    user_id?: string | number;
    useLocal?: boolean;
};

/**
 * # Reauthorize User
 */
export default async function reauthUser({
    key,
    database,
    response,
    request,
    level,
    encryptionKey,
    encryptionSalt,
    additionalFields,
    encryptedUserString,
    user_id,
    useLocal,
}: Param): Promise<APILoginFunctionReturn> {
    /**
     * Check Encryption Keys
     *
     * @description Check Encryption Keys
     */
    const grabedHostNames = grabHostNames();
    const { host, port, scheme } = grabedHostNames;

    const finalEncryptionKey =
        encryptionKey || process.env.DSQL_ENCRYPTION_PASSWORD;
    const finalEncryptionSalt =
        encryptionSalt || process.env.DSQL_ENCRYPTION_SALT;

    const existingUser = userAuth({
        database,
        encryptionKey: finalEncryptionKey,
        encryptionSalt: finalEncryptionSalt,
        level,
        request,
        encryptedUserString,
    });

    if (!existingUser?.payload?.id) {
        return {
            success: false,
            payload: null,
            msg: "Cookie Credentials Invalid",
        };
    }

    /**
     * Initialize HTTP response variable
     */
    let httpResponse;

    /**
     * Check for local DB settings
     *
     * @description Look for local db settings in `.env` file and by pass the http request if available
     */
    const { DSQL_DB_HOST, DSQL_DB_USERNAME, DSQL_DB_PASSWORD, DSQL_DB_NAME } =
        process.env;

    if (
        DSQL_DB_HOST?.match(/./) &&
        DSQL_DB_USERNAME?.match(/./) &&
        DSQL_DB_PASSWORD?.match(/./) &&
        DSQL_DB_NAME?.match(/./) &&
        useLocal
    ) {
        /** @type {import("../package-shared/types").DSQL_DatabaseSchemaType | undefined} */
        let dbSchema:
            | import("../package-shared/types").DSQL_DatabaseSchemaType
            | undefined;

        try {
            const localDbSchemaPath = path.resolve(
                process.cwd(),
                "dsql.schema.json"
            );
            dbSchema = JSON.parse(fs.readFileSync(localDbSchemaPath, "utf8"));
        } catch (error) {}

        httpResponse = await apiReauthUser({
            existingUser: existingUser.payload,
            additionalFields,
            useLocal,
        });
    } else {
        /**
         * Make https request
         *
         * @description make a request to datasquirel.com
         */
        httpResponse = (await new Promise((resolve, reject) => {
            const reqPayload = JSON.stringify({
                existingUser: existingUser.payload,
                database,
                additionalFields,
            });

            const httpsRequest = scheme.request(
                {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json",
                        "Content-Length": Buffer.from(reqPayload).length,
                        Authorization:
                            key ||
                            process.env.DSQL_FULL_ACCESS_API_KEY ||
                            process.env.DSQL_API_KEY,
                    },
                    port,
                    hostname: host,
                    path: `/api/user/${
                        user_id || grabedHostNames.user_id
                    }/reauth-user`,
                },

                /**
                 * Callback Function
                 *
                 * @description https request callback
                 */
                (response) => {
                    var str = "";

                    response.on("data", function (chunk) {
                        str += chunk;
                    });

                    response.on("end", function () {
                        resolve(JSON.parse(str));
                    });

                    response.on("error", (err) => {
                        reject(err);
                    });
                }
            );

            httpsRequest.write(reqPayload);
            httpsRequest.end();
        })) as APILoginFunctionReturn;
    }

    /**
     * Make https request
     *
     * @description make a request to datasquirel.com
     */
    if (httpResponse?.success) {
        let encryptedPayload = encrypt({
            data: JSON.stringify(httpResponse.payload),
            encryptionKey: finalEncryptionKey,
            encryptionSalt: finalEncryptionSalt,
        });

        const cookieNames = getAuthCookieNames({
            database,
            userId: user_id || grabedHostNames.user_id,
        });

        httpResponse["cookieNames"] = cookieNames;
        httpResponse["key"] = String(encryptedPayload);

        const authKeyName = cookieNames.keyCookieName;
        const csrfName = cookieNames.csrfCookieName;

        response?.setHeader("Set-Cookie", [
            `${authKeyName}=${encryptedPayload};samesite=strict;path=/;HttpOnly=true;Secure=true`,
            `${csrfName}=${httpResponse.payload?.csrf_k};samesite=strict;path=/;HttpOnly=true`,
        ]);

        if (httpResponse.csrf) {
            deleteAuthFile(String(existingUser.payload.csrf_k));
            writeAuthFile(
                httpResponse.csrf,
                JSON.stringify(httpResponse.payload)
            );
        }
    }

    return httpResponse;
}