const OPENID_ENABLED    = "0";
const OPENID_CONFIG_URL = "__OPENID_CONFIG_URL__"; // https://accounts.google.com/.well-known/openid-configuration
const OPENID_CLIENT_ID  = "__OPENID_CLIENT_ID__";

let OPENID_AUTH_URL = null; // will be set after fetching config

const TOKEN_STORAGE_KEY = "id_token";
const STATE_STORAGE_KEY = "oauth_state";

// Generate a random string for state / nonce
function randomString(length = 32) {
    if (window.crypto && window.crypto.getRandomValues) {
        const bytes = new Uint8Array(length);
        window.crypto.getRandomValues(bytes);
        return Array.from(bytes)
            .map(b => b.toString(16).padStart(2, "0"))
            .join("");
    }
    let result = "";
    const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    for (let i = 0; i < length; i++) {
        result += chars[Math.floor(Math.random() * chars.length)];
    }
    return result;
}

function parseJwt(token) {
    const parts = token.split(".");
    if (parts.length !== 3) return null;

    try {
        const payload = parts[1]
            .replace(/-/g, "+")
            .replace(/_/g, "/");

        const json = atob(payload);
        return JSON.parse(json);
    } catch (e) {
        console.warn("Failed to parse JWT:", e);
        return null;
    }
}

function isTokenExpired(token) {
    const payload = parseJwt(token);
    if (!payload || !payload.exp)
        return true;

    const now = Math.floor(Date.now() / 1000);
    return payload.exp <= now;
}

// Current page (without hash) as redirect URI
function getRedirectUri() {
    return window.location.origin + window.location.pathname;
}

function getIdToken() {
    const token = window.sessionStorage.getItem(TOKEN_STORAGE_KEY);
    if (!token) return null;

    if (isTokenExpired(token)) {
        console.info("Stored id_token is expired, clearing.");
        window.sessionStorage.removeItem(TOKEN_STORAGE_KEY);
        return null;
    }

    return token;
}

function setIdToken(token) {
    window.sessionStorage.setItem(TOKEN_STORAGE_KEY, token);
}

// Parse OAuth callback (#id_token=...)
function handleOAuthCallbackIfPresent() {
    if (!window.location.hash || window.location.hash.length <= 1) {
        return;
    }

    const hash = window.location.hash.substring(1);
    const params = new URLSearchParams(hash);

    const idToken = params.get("id_token");
    const state   = params.get("state");

    if (!idToken || !state) return;

    const storedState = window.sessionStorage.getItem(STATE_STORAGE_KEY);
    if (!storedState || storedState !== state) {
        console.warn("OAuth state mismatch. Ignoring callback.");
        return;
    }

    setIdToken(idToken);
    window.sessionStorage.removeItem(STATE_STORAGE_KEY);

    const cleanUrl = window.location.origin + window.location.pathname + window.location.search;
    window.history.replaceState(null, "", cleanUrl);
}

// Redirect user to OIDC authorization endpoint
function login() {
    if (!OPENID_AUTH_URL) {
        console.error("OPENID_AUTH_URL not initialized");
        return;
    }

    const state = randomString();
    const nonce = randomString();

    window.sessionStorage.setItem(STATE_STORAGE_KEY, state);

    const params = new URLSearchParams({
        client_id:     OPENID_CLIENT_ID,
        redirect_uri:  getRedirectUri(),
        response_type: "id_token",
        scope:         "openid email profile",
        nonce:         nonce,
        state:         state,
    });

    location.href = OPENID_AUTH_URL + "?" + params.toString();
}

function showBody() {
    if (document.body) {
        document.body.style.display = "block";
    } else {
        document.addEventListener("DOMContentLoaded", () => {
            document.body.style.display = "block";
        });
    }
}

// Fetch OpenID configuration (discovery)
async function fetchOpenIdConfig() {
    try {
        const res = await fetch(OPENID_CONFIG_URL);
        if (!res.ok) throw new Error("Failed config fetch");
        return await res.json();
    } catch (e) {
        console.error("Failed to load OpenID config", e);
        return null;
    }
}

var sso = {
    enabled: function() {
        return OPENID_ENABLED === "1";
    },

    payload: function() {
        const token = getIdToken();
        if (!token) return {};

        const payload = parseJwt(token);
        if (!payload) return {};

        return payload;
    },

    init: async function() {
        // 1. Load OpenID config
        const cfg = await fetchOpenIdConfig();
        if (!cfg || !cfg.authorization_endpoint) {
            console.error("Invalid OpenID config");
            return;
        }

        OPENID_AUTH_URL = cfg.authorization_endpoint;

        // 2. Handle callback if present (#id_token=...)
        handleOAuthCallbackIfPresent();

        // 3. Start login if no token
        if (!getIdToken()) {
            login();
            return;
        }

        // 4. If we have token → show UI
        showBody();
    }
};

function $(e) { return document.getElementById(e); }

Function.prototype.defer = function(ms) {
    const f = this;
    return function() {
        const args = arguments, ctx = this;
        setTimeout(() => f.apply(ctx, args), ms);
    };
};

function formatDetailedDate(str) {
    const date = new Date(str);
    const options = {
        year: "numeric", month: "numeric", day: "numeric",
        hour: "2-digit", minute: "2-digit",
        timeZone: "UTC"
    };
    return date.toLocaleTimeString("ru-RU", options);
}

// MD5 (legacy)
function md5(d){return rstr2hex(binl2rstr(binl_md5(rstr2binl(d),8*d.length)))}function rstr2hex(d){for(var _,m="0123456789ABCDEF",f="",r=0;r<d.length;r++)_=d.charCodeAt(r),f+=m.charAt(_>>>4&15)+m.charAt(15&_);return f}function rstr2binl(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<<m%32;return _}function binl2rstr(d){for(var _="",m=0;m<32*d.length;m+=8)_+=String.fromCharCode(d[m>>5]>>>m%32&255);return _}function binl_md5(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n<d.length;n+=16){var h=m,t=f,g=r,e=i;f=md5_ii(f=md5_ii(f=md5_ii(f=md5_ii(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_ff(f=md5_ff(f=md5_ff(f=md5_ff(f,r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+0],7,-680876936),f,r,d[n+1],12,-389564586),m,f,d[n+2],17,606105819),i,m,d[n+3],22,-1044525330),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+4],7,-176418897),f,r,d[n+5],12,1200080426),m,f,d[n+6],17,-1473231341),i,m,d[n+7],22,-45705983),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+8],7,1770035416),f,r,d[n+9],12,-1958414417),m,f,d[n+10],17,-42063),i,m,d[n+11],22,-1990404162),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+12],7,1804603682),f,r,d[n+13],12,-40341101),m,f,d[n+14],17,-1502002290),i,m,d[n+15],22,1236535329),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+1],5,-165796510),f,r,d[n+6],9,-1069501632),m,f,d[n+11],14,643717713),i,m,d[n+0],20,-373897302),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+5],5,-701558691),f,r,d[n+10],9,38016083),m,f,d[n+15],14,-660478335),i,m,d[n+4],20,-405537848),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+9],5,568446438),f,r,d[n+14],9,-1019803690),m,f,d[n+3],14,-187363961),i,m,d[n+8],20,1163531501),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+13],5,-1444681467),f,r,d[n+2],9,-51403784),m,f,d[n+7],14,1735328473),i,m,d[n+12],20,-1926607734),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+5],4,-378558),f,r,d[n+8],11,-2022574463),m,f,d[n+11],16,1839030562),i,m,d[n+14],23,-35309556),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+1],4,-1530992060),f,r,d[n+4],11,1272893353),m,f,d[n+7],16,-155497632),i,m,d[n+10],23,-1094730640),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+13],4,681279174),f,r,d[n+0],11,-358537222),m,f,d[n+3],16,-722521979),i,m,d[n+6],23,76029189),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+9],4,-640364487),f,r,d[n+12],11,-421815835),m,f,d[n+15],16,530742520),i,m,d[n+2],23,-995338651),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+0],6,-198630844),f,r,d[n+7],10,1126891415),m,f,d[n+14],15,-1416354905),i,m,d[n+5],21,-57434055),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+12],6,1700485571),f,r,d[n+3],10,-1894986606),m,f,d[n+10],15,-1051523),i,m,d[n+1],21,-2054922799),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+8],6,1873313359),f,r,d[n+15],10,-30611744),m,f,d[n+6],15,-1560198380),i,m,d[n+13],21,1309151649),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+4],6,-145523070),f,r,d[n+11],10,-1120210379),m,f,d[n+2],15,718787259),i,m,d[n+9],21,-343485551),m=safe_add(m,h),f=safe_add(f,t),r=safe_add(r,g),i=safe_add(i,e)}return Array(m,f,r,i)}function md5_cmn(d,_,m,f,r,i){return safe_add(bit_rol(safe_add(safe_add(_,d),safe_add(f,i)),r),m)}function md5_ff(d,_,m,f,r,i,n){return md5_cmn(_&m|~_&f,d,_,r,i,n)}function md5_gg(d,_,m,f,r,i,n){return md5_cmn(_&f|m&~f,d,_,r,i,n)}function md5_hh(d,_,m,f,r,i,n){return md5_cmn(_^m^f,d,_,r,i,n)}function md5_ii(d,_,m,f,r,i,n){return md5_cmn(m^(_|~f),d,_,r,i,n)}function safe_add(d,_){var m=(65535&d)+(65535&_);return(d>>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}

// ==== POST request helper ====
function postRequest(uri = "/", data, callback = null) {
    const xhr = new XMLHttpRequest();
    xhr.open("POST", uri);

    if (sso.enabled()) {
        const token = getIdToken();
        if (!token) {
            login();
            return;
        }

        xhr.setRequestHeader("Authorization", "Bearer " + token);
    }

    xhr.onreadystatechange = function() {
        if (xhr.readyState !== 4) return;

        const result = xhr.responseText || "";
        if (xhr.status === 200 && result.length > 0) {
            if (result[0] !== "*" && result[0] !== ">") {
                if (callback) callback(result);
            } else {
                printResponse(result, result[0] === ">");
            }
        } else {
            if (xhr.status === 0) {
                printResponse("Network error", true);
            } else {
                printResponse(xhr.statusText || "Unknown error", true);
            }
        }
    };

    xhr.send(data);
}

function printResponse(message, is_error) {
    printResponseTo(message, is_error, document.getElementById("responseText"));
}

function printResponseTo(message, is_error, element, should_scroll = false) {
    if (element == null) {
        const logFn = is_error ? console.error : console.log;
        logFn(message);
        return;
    }

    const color = is_error ? "red" : "green";
    const payload = sso.payload() || {};
    const name = payload.name || "";
    const email = payload.email || "";

    const username = [name, email].filter(Boolean).join(" ");

    const html =
        "Response" +
        (username ? " (" + username + ")" : "") +
        ': <em style="color: ' +
        color +
        ';">' +
        message +
        "</em>";

    // Support both plain DOM element and jQuery-like object
    if (element.html != null) {
        element.html(html);
    } else {
        element.innerHTML = html;
    }

    // Auto scroll to the response element when requested
    if (should_scroll) {
        const target = element.length ? element[0] : element;
        if (target && typeof target.scrollIntoView === "function") {
            target.scrollIntoView({ behavior: "smooth", block: "center" });
        }
    }
}

// Make sure helpers are available globally
window.printResponse = printResponse;
window.printResponseTo = printResponseTo;

if (sso.enabled()) {
    sso.init();
} else {
    showBody();
}
