import http from "http";
import fs from "fs";
import path from "path";
import encrypt from "../../functions/dsql/encrypt";
import grabHostNames from "../../utils/grab-host-names";
import apiLoginUser from "../../functions/api/users/api-login";
import getAuthCookieNames from "../../functions/backend/cookies/get-auth-cookie-names";
import { writeAuthFile } from "../../functions/backend/auth/write-auth-files";
import {
    APILoginFunctionReturn,
    DSQL_DatabaseSchemaType,
    PackageUserLoginRequestBody,
} from "../../types";
import debugLog from "../../utils/logging/debug-log";
import grabCookieExpiryDate from "../../utils/grab-cookie-expirt-date";
import emailRegexCheck from "../../functions/email/verification/email-regex-test";
import emailMxLookup from "../../functions/email/verification/email-mx-lookup";
import validateEmail from "../../functions/email/fns/validate-email";

type Param = {
    key?: string;
    database: string;
    payload: {
        email?: string;
        username?: string;
        password?: string;
    };
    additionalFields?: string[];
    request?: http.IncomingMessage & { [s: string]: any };
    response?: http.ServerResponse & { [s: string]: any };
    encryptionKey?: string;
    encryptionSalt?: string;
    email_login?: boolean;
    email_login_code?: string;
    temp_code_field?: string;
    token?: boolean;
    user_id?: string | number;
    skipPassword?: boolean;
    debug?: boolean;
    skipWriteAuthFile?: boolean;
    apiUserID?: string | number;
    dbUserId?: string | number;
    cleanupTokens?: boolean;
    secureCookie?: boolean;
};

/**
 * # Login A user
 */
export default async function loginUser({
    key,
    payload,
    database,
    additionalFields,
    response,
    encryptionKey,
    encryptionSalt,
    email_login,
    email_login_code,
    temp_code_field,
    token,
    user_id,
    skipPassword,
    apiUserID,
    skipWriteAuthFile,
    dbUserId,
    debug,
    cleanupTokens,
    secureCookie,
    request,
}: Param): Promise<APILoginFunctionReturn> {
    const grabedHostNames = grabHostNames({ userId: user_id || apiUserID });
    const { host, port, scheme } = grabedHostNames;
    const COOKIE_EXPIRY_DATE = grabCookieExpiryDate();

    const defaultTempLoginFieldName = "temp_login_code";
    const emailLoginTempCodeFieldName = email_login
        ? temp_code_field
            ? temp_code_field
            : defaultTempLoginFieldName
        : undefined;

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

    function debugFn(log: any, label?: string) {
        debugLog({ log, addTime: true, title: "loginUser", label });
    }

    if (!finalEncryptionKey?.match(/.{8,}/)) {
        console.log("Encryption key is invalid");
        return {
            success: false,
            payload: null,
            msg: "Encryption key is invalid",
        };
    }

    if (!finalEncryptionSalt?.match(/.{8,}/)) {
        console.log("Encryption salt is invalid");
        return {
            success: false,
            payload: null,
            msg: "Encryption salt is invalid",
        };
    }

    /**
     * Check required fields
     *
     * @description Check required fields
     */
    const isEmailValid = await validateEmail({ email: payload.email });

    if (!payload.email || !isEmailValid.isValid) {
        return {
            success: false,
            payload: null,
            msg: isEmailValid.message,
        };
    }

    /**
     * Initialize HTTP response variable
     */

    let httpResponse: import("../../types").APILoginFunctionReturn = {
        success: false,
    };

    /**
     * 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(/./) &&
        global.DSQL_USE_LOCAL
    ) {
        let dbSchema: DSQL_DatabaseSchemaType | undefined;

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

        httpResponse = await apiLoginUser({
            database: process.env.DSQL_DB_NAME || "",
            email: payload.email,
            username: payload.username,
            password: payload.password,
            skipPassword,
            encryptionKey: finalEncryptionKey,
            additionalFields,
            email_login,
            email_login_code,
            email_login_field: emailLoginTempCodeFieldName,
            token,
            dbUserId,
            debug,
        });
    } else {
        httpResponse = await new Promise((resolve, reject) => {
            const reqPayload: PackageUserLoginRequestBody = {
                encryptionKey: finalEncryptionKey,
                payload,
                database,
                additionalFields,
                email_login,
                email_login_code,
                email_login_field: emailLoginTempCodeFieldName,
                token,
                skipPassword: skipPassword,
                dbUserId: dbUserId || 0,
            };

            const reqPayloadJSON = JSON.stringify(reqPayload);

            const httpsRequest = scheme.request(
                {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json",
                        "Content-Length": Buffer.from(reqPayloadJSON).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
                    }/login-user`,
                },

                (res) => {
                    var str = "";

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

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

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

            httpsRequest.write(reqPayloadJSON);
            httpsRequest.end();
        });
    }

    if (debug) {
        debugFn(httpResponse, "httpResponse");
    }

    if (httpResponse?.success) {
        let encryptedPayload = encrypt({
            data: JSON.stringify(httpResponse.payload),
            encryptionKey: finalEncryptionKey,
            encryptionSalt: finalEncryptionSalt,
        });

        try {
            if (token && encryptedPayload)
                httpResponse["token"] = encryptedPayload;
        } catch (error) {
            global.ERROR_CALLBACK?.(
                `Login User HTTP Response Error`,
                error as Error
            );
        }

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

        if (httpResponse.csrf && !skipWriteAuthFile) {
            writeAuthFile(
                httpResponse.csrf,
                JSON.stringify(httpResponse.payload),
                cleanupTokens && httpResponse.payload?.id
                    ? { userId: httpResponse.payload.id }
                    : undefined
            );
        }

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

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

        if (debug) {
            debugFn(authKeyName, "authKeyName");
            debugFn(csrfName, "csrfName");
            debugFn(encryptedPayload, "encryptedPayload");
        }

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

        if (debug) {
            debugFn("Response Sent!");
        }
    }

    return httpResponse;
}