/* eslint-env browser */
/* eslint-disable no-alert, no-console */

/**
 * @file apxauth.js (previously authnew.js)
 * @description Modern, class-based implementation for handling authentication (apxauth) components.
 * @version 2.1
 * @author support@ndda.fr
 */

// Establish the global namespace
window.apx = window.apx || {};

/**
 * @class ApxAuth
 * Manages authentication flows, including sign-in, sign-up, logout, and key management.
 */
class ApxAuth {
    constructor() {
        if (typeof apx.main === 'undefined') {
            throw new Error("ApxAuth requires a global 'apx.main' (ApxManager) instance.");
        }
    }

    async loadwco(id, ctx) {
        console.log(`[apxauth] loadwco triggered for id: ${id} with context:`, ctx);
        const componentRoot = document.getElementById(id);
        if (!componentRoot) return;

        const data = this._getData(id, ctx);

        if (componentRoot.innerHTML.trim() === "") {
            componentRoot.innerHTML = Mustache.render(apx.main.data.tpl.apxauthmain, data);
        }

        const screenContainer = componentRoot.querySelector('.screenaction');
        if (screenContainer) {
            const screenTemplate = apx.main.data.tpl[`apxauthscreen${ctx.link}`];
            if (screenTemplate) {
                screenContainer.innerHTML = Mustache.render(screenTemplate, data);
            }
        }
        
        const msgInfo = componentRoot.querySelector('.msginfo');
        if (msgInfo) msgInfo.innerHTML = "";
    }

    _getData(id, ctx) {
        const tpldataname = `${apx.main.data.pagename}_${id}_apxauth`;
        const data = JSON.parse(JSON.stringify(apx.main.data.tpldata[tpldataname] || {}));
        
        data.id = id;
        data.xalias = apx.main.data.headers.xalias;
        data.xtribe = apx.main.data.headers.xtribe;
        data.emailsupport = apx.main.data.appdata?.emailsupport;

        if (ctx.link === 'logout') {
            data.profils = apx.main.data.headers.xprofils
                .filter(p => !['anonymous', 'pagans', 'persons'].includes(p))
                .map(p => apx.main.data.options.profil.itms[p]?.title);
            data.noprofils = data.profils.length === 0;
            data.member = apx.main.data.headers.xprofils.includes('persons');
            data.websites = apx.main.data.appdata?.websites;
        }
        
        return data;
    }

    redirectWithAuth(url, tribe, webapp, newWindow = false, windowName = '_blank') {
        const { xlang, xalias, xdays, xhash, xprofils, xtrkversion, xuuid } = apx.main.data.headers;
        let authUrl = url.replace(/_[a-z]{2}\.html/, `_${xlang}.html`);
        
        const params = new URLSearchParams({
            xtribe: tribe,
            xapp: webapp,
            xalias,
            xdays,
            xhash,
            xprofils: xprofils.join(','),
            xtrkversion,
            xuuid
        });
        
        authUrl += `?${params.toString()}`;

        if (newWindow) {
            try {
                const newWin = window.open(authUrl, windowName);
                if (!newWin) {
                    alert("Popup blocked. Please allow popups for this site.");
                    return null;
                }
                newWin.focus();
                return newWin;
            } catch (error) {
                console.error("Error opening new window:", error);
                return null;
            }
        } else {
            window.location.href = authUrl;
            return null;
        }
    }

    async logout() {
        try {
            await axios.get('/api/apxtri/pagans/logout', { headers: apx.main.data.headers });
        } catch (err) {
            console.error("Logout API call failed:", err);
        }
        
        apx.main.data = window.apxtri;
        apx.main.saveState();

        if (apx.main.pageContext.hash.url) {
            window.location.href = apx.main.pageContext.hash.url;
        } else {
            location.reload();
        }
    }

    async _setAuthHeaders(alias, passphrase, publickey, privatekey, rememberme) {
        if (alias.length < 3 || publickey.length < 200) {
            return { status: 406, ref: "Pagans", msg: "aliasorprivkeytooshort" };
        }

        if (rememberme) {
            apx.main.data.auth = { alias, publickey, privatekey, passphrase: passphrase || "" };
        } else {
            delete apx.main.data.auth;
        }

        apx.main.data.headers.xalias = alias;
        apx.main.data.headers.xdays = dayjs().valueOf();
        const message = `${alias}_${apx.main.data.headers.xdays}`;

        try {
            apx.main.data.headers.xhash = await this._clearMsgSignature(publickey, privatekey, passphrase, message);
            apx.main.saveState();
            return { status: 200 };
        } catch (err) {
            return { status: 500, ref: "Middlewares", msg: "unconsistentpgp", data: { err: err.message } };
        }
    }

    async authenticate(id, alias, passphrase, privatekey, rememberme) {
        const msgContainer = `#${id} .msginfo`;
        apx.main.notify(msgContainer, {}, true);

        if (alias.length < 3 || privatekey.length < 200) {
            apx.main.notify(msgContainer, { status: 406, ref: "Pagans", msg: "aliasorprivkeytooshort" });
            return;
        }

        try {
            const { data: { data: paganData } } = await axios.get(`/api/apxtri/pagans/alias/${alias}`, { headers: apx.main.data.headers });
            
            const headersResult = await this._setAuthHeaders(alias, passphrase, paganData.publickey, privatekey, rememberme);
            if (headersResult.status !== 200) {
                apx.main.notify(msgContainer, headersResult);
                return;
            }

            const { data: { data: authData } } = await axios.get('/api/apxtri/pagans/isauth', { headers: apx.main.data.headers, withCredentials: true });
            
            apx.main.data.headers.xprofils = authData.xprofils;
            apx.main.saveState();

            if (apx.main.pageContext.hash.url) {
                window.location.href = apx.main.pageContext.hash.url;
            } else {
                const parentWco = document.getElementById(id).closest('[wco-name]');
                if (parentWco) parentWco.setAttribute('wco-link', 'mytribes');
            }

        } catch (err) {
            console.error("Authentication failed:", err);
            delete apx.main.data.auth;
            apx.main.saveState();
            const parentWco = document.getElementById(id).closest('[wco-name]');
            if (parentWco) parentWco.setAttribute('wco-link', 'signin');
            apx.main.notify(msgContainer, err.response?.data || { status: 500, ref: "Middlewares", msg: "errrequest" });
        }
    }

    async recoverKey(id, aliasOrEmail) {
        const msgContainer = `#${id} .msginfo`;
        apx.main.notify(msgContainer, {}, true);

        if (aliasOrEmail.length < 3) {
            apx.main.notify(msgContainer, { status: 406, ref: "Pagans", msg: "recoveryemailnotfound", data: { search: aliasOrEmail } });
            return;
        }

        const recoveryData = {
            tribe: apx.main.data.headers.xtribe,
            search: aliasOrEmail,
            emailalias: Checkjson.testformat(aliasOrEmail, "email") ? "email" : "alias",
        };

        try {
            const { data: response } = await axios.post('/api/apxtri/pagans/keyrecovery', recoveryData, { headers: apx.main.data.headers });
            response.data.search = aliasOrEmail;
            apx.main.notify(msgContainer, response, true);
        } catch (err) {
            const errorData = err.response?.data || { status: 500, ref: "Pagans", msg: "checkconsole" };
            errorData.data = { ...errorData.data, search: aliasOrEmail };
            apx.main.notify(msgContainer, errorData, true);
        }
    }

    async _generateKey(alias, passphrase) {
        const { privateKey, publicKey } = await openpgp.generateKey({
            type: "ecc",
            curve: "curve25519",
            userIDs: [{ alias }],
            passphrase,
            format: "armored",
        });
        return { alias, privatekey: privateKey, publickey: publicKey };
    }

    async createIdentity(id, alias, recoveryEmail, passphrase = "") {
        const msgContainer = `#${id} .msginfo`;
        apx.main.notify(msgContainer, {}, true);

        const aliasRegex = /^[a-z0-9]{4,}$/;
        if (!aliasRegex.test(alias)) {
            apx.main.notify(msgContainer, { status: 406, ref: "Pagans", msg: "invalidalias" }, true);
            return;
        }
        if (recoveryEmail && !Checkjson.testformat(recoveryEmail, "email")) {
            apx.main.notify(msgContainer, { status: 406, ref: "Pagans", msg: "invalidemail" }, true);
            return;
        }

        try {
            await axios.get(`/api/apxtri/pagans/alias/${alias}`, { headers: apx.main.data.headers });
            apx.main.notify(msgContainer, { ref: "Pagans", msg: "aliasexist", data: { alias } }, true);
        } catch (err) {
            if (err.response?.status === 404) {
                const keys = await this._generateKey(alias, passphrase);
                apx.main.data.tmpauth = { keys, recoveryEmail, passphrase };
                this._showKeyDownloadUI(id, keys);
            } else {
                apx.main.notify(msgContainer, { ref: "Middlewares", msg: "errrequest" }, true);
            }
        }
    }
    
    _showKeyDownloadUI(id, keys) {
        const componentRoot = document.getElementById(id);
        ['publickey', 'privatekey'].forEach(keyType => {
            const btn = componentRoot.querySelector(`button.signup${keyType}`);
            if(btn) {
                btn.onclick = () => {
                    const blob = new Blob([keys[keyType]], { type: "text/plain" });
                    const url = URL.createObjectURL(blob);
                    const a = document.createElement("a");
                    a.href = url;
                    a.download = `${keys.alias}_${keyType}.txt`;
                    a.click();
                    URL.revokeObjectURL(url);
                };
            }
        });

        componentRoot.querySelectorAll('.signupalias, .signupemailrecovery, .signuppassphrase').forEach(el => el.disabled = true);
        componentRoot.querySelector('.getmykeys')?.classList.remove('hidden');
        componentRoot.querySelector('.btncreatekey')?.classList.add('hidden');
    }

    async registerIdentity(id, isTrustedTribe) {
        const msgContainer = `#${id} .msginfo`;
        const { keys, recoveryEmail, passphrase } = apx.main.data.tmpauth;

        const headersResult = await this._setAuthHeaders(keys.alias, passphrase, keys.publickey, keys.privatekey, false);
        if (headersResult.status !== 200) {
            apx.main.notify(msgContainer, headersResult);
            return;
        }

        const registrationData = {
            alias: keys.alias,
            publickey: keys.publickey,
            trustedtribe: isTrustedTribe,
        };

        if (recoveryEmail && Checkjson.testformat(recoveryEmail, "email")) {
            registrationData.email = recoveryEmail;
            registrationData.passphrase = passphrase;
            registrationData.privatekey = keys.privatekey;
        }

        try {
            const { data: response } = await axios.post('/api/apxtri/pagans', registrationData, { headers: apx.main.data.headers });
            apx.main.notify(msgContainer, response);
            document.querySelector(`#${id} .btncreateidentity`)?.classList.add('hidden');
            document.querySelector(`#${id} .signupbtnreload`)?.classList.remove('hidden');
        } catch (err) {
            apx.main.notify(msgContainer, err.response?.data || { status: 500, ref: "Pagans", msg: "errcreate" });
        }
    }
    
    async joinTribe(id) {
        const msgContainer = `#${id} .msginfo`;
        const personData = {
            alias: apx.main.data.headers.xalias,
            profils: [...new Set([...apx.main.data.headers.xprofils, 'persons'])],
        };

        try {
            const { data: response } = await axios.put(`/api/apxtri/pagans/person/${apx.main.data.headers.xtribe}`, personData, { headers: apx.main.data.headers });
            apx.main.notify(msgContainer, response);
            await this.logout();
            
        } catch (err) {
            apx.main.notify(msgContainer, err.response?.data || { status: 500, ref: "Pagans", msg: "errcreate" });
        }
    }

    async _clearMsgSignature(pubK, privK, passphrase, message) {
        const publickey = await openpgp.readKey({ armoredKey: pubK });
        const privatekey = await openpgp.decryptKey({
            privateKey: await openpgp.readPrivateKey({ armoredKey: privK }),
            passphrase,
        });

        const cleartextMessage = await openpgp.sign({
            message: await openpgp.createCleartextMessage({ text: message }),
            signingKeys: privatekey,
        });

        const { signatures: [{ verified }] } = await openpgp.verify({
            message: await openpgp.readCleartextMessage({ cleartextMessage }),
            verificationKeys: publickey,
        });

        if (!(await verified)) {
            throw new Error("Signature verification failed.");
        }
        return btoa(cleartextMessage);
    }
}

// Attach an instance to the global namespace
apx.apxauth = new ApxAuth();