import { decodeProto, TYPES } from "../../lib/protobuf-decoder/protobufDecoder";
import { TOTP } from "../../lib/totp-quickjs";
import { base64decode, encode } from "../../lib/totp-quickjs/base32decoder";

const otpauthScheme = "otpauth://";
const googleMigrationScheme = "otpauth-migration://";

function _parseSingleMigrationEntry(part) {
    const totpProto = decodeProto(part.value);
    const otpData = {};

    const protoPartHandlers = {
        1: (p) => {
            otpData.secret = encode(p.value);
        },
        2: (p) => {
            otpData.name = bytesToString(p.value);
        },
        3: (p) => {
            otpData.issuer = bytesToString(p.value);
        },
        4: (p) => {
            otpData.algorithm = p.value;
        },
        5: (p) => {
            otpData.digits = p.value;
        },
        6: (p) => {
            otpData.type = p.value;
        },
    };

    totpProto.parts.forEach((p) => {
        const handler = protoPartHandlers[p.index];
        if (handler) {
            handler(p);
        }
    });

    if (otpData.type !== "2") {
        return null;
    }

    const digitsMap = { 1: 6, 2: 8 };
    const algoMap = { 1: "SHA-1", 2: "SHA-256", 3: "SHA-512" };

    const finalDigits = digitsMap[otpData.digits] || 6;
    const finalAlgo = algoMap[otpData.algorithm] || "SHA-1";
    const finalIssuer = otpData.issuer || otpData.name;
    const finalName = otpData.name;

    if (!otpData.secret || !finalName) {
        throw new Error("Skipping record with missing secret or name.");
    }

    return new TOTP(
        otpData.secret,
        finalIssuer,
        finalName,
        finalDigits,
        30,
        0,
        finalAlgo,
    );
}

function getByGoogleMigrationScheme(link) {
    try {
        const data = link.split("data=")[1];
        if (!data) return null;

        const decodedData = decodeURIComponent(data);
        const buffer = base64decode(decodedData);
        const proto = decodeProto(buffer);

        const totps = [];
        const otpParameters = proto.parts.filter(
            (p) => p.index === 1 && p.type === TYPES.LENDELIM,
        );

        otpParameters.forEach((part) => {
            const totp = _parseSingleMigrationEntry(part);
            if (totp) {
                totps.push(totp);
            }
        });

        return totps.length > 0 ? totps : null;
    } catch (err) {
        console.log("Failed to parse Google Migration scheme:", err);
        throw new Error(
            "Invalid otpauth-migration:// link. Failed to parse migration data.",
        );
    }
}

export function getTOTPByLink(link) {
    if (link.startsWith(googleMigrationScheme)) {
        return getByGoogleMigrationScheme(link);
    }
    if (link.startsWith(otpauthScheme)) {
        return getByOtpauthScheme(link);
    }

    return null;
}

function getHashType(algorithm) {
    if (algorithm == "SHA1") return "SHA-1";
    if (algorithm == "SHA256") return "SHA-256";
    if (algorithm == "SHA512") return "SHA-512";
    else return "SHA-1";
}

function getByOtpauthScheme(link) {
    try {
        let args = link.split("?");
        let path = args[0];
        let params = args[1];

        let pathParts = path.split("/");
        let type = pathParts[2]; //hotp or totp
        let label = decodeURIComponent(pathParts[3]);

        let issuerFromLabel = label.includes(":") ? label.split(":")[0] : null;
        let client = label.includes(":") ? label.split(":")[1].trim() : label;

        let secret = params.match(/secret=([^&]*)/)?.[1];
        let issuerFromParams = params.match(/issuer=([^&]*)/)?.[1];

        let issuer = issuerFromParams
            ? decodeURIComponent(issuerFromParams)
            : issuerFromLabel;
        if (!issuer) issuer = client;

        let period = params.match(/period=([^&]*)/)?.[1];
        let digits = params.match(/digits=([^&]*)/)?.[1];
        let algorithm = params.match(/algorithm=([^&]*)/)?.[1];
        let offset = params.match(/offset=([^&]*)/)?.[1] ?? 0;

        if (type.toLowerCase() != "totp")
            throw new Error("Type is not valid, requires 'TOTP'");

        if (!secret) throw new Error("Secret not defined");

        return new TOTP(
            secret,
            issuer,
            client,
            Number(digits) || 6,
            Number(period) || 30,
            Number(offset),
            getHashType(algorithm),
        );
    } catch (err) {
        console.log("Failed to parse otpauth scheme:", err);
        throw new Error(
            `Invalid otpauth:// link. Please check the link and try again. ERR: ${err}`,
        );
    }
}

function bytesToString(bytes) {
    let str = "";
    for (let i = 0; i < bytes.length; i++) {
        str += String.fromCharCode(bytes[i]);
    }
    return str;
}
