import { BUILD_TIME, GENERATOR, PUBLIC_KEY, VERSION } from "./engine_constants.js";
import { ContextEvent, ContextRegistry } from "./engine_context_registry.js";
import { Context } from "./engine_setup.js";
import type { IContext } from "./engine_types.js";
import { getParam } from "./engine_utils.js";

const debug = getParam("debuglicense");

const _licenseCheckResultChangedCallbacks: ((result: boolean) => void)[] = [];

// This is modified by a bundler (e.g. vite)
// Do not edit manually
let NEEDLE_ENGINE_LICENSE_TYPE: string = "basic";
if (debug)
    console.log("License Type: " + NEEDLE_ENGINE_LICENSE_TYPE)

/** @internal */
export function hasProLicense() {
    switch (NEEDLE_ENGINE_LICENSE_TYPE) {
        case "pro":
        case "enterprise":
            return true;
    };
    return false;
}

/** @internal */
export function hasIndieLicense() {
    switch (NEEDLE_ENGINE_LICENSE_TYPE) {
        case "indie":
            return true;
    }
    return false;
}

/** @internal */
export function hasEduLicense() {
    switch (NEEDLE_ENGINE_LICENSE_TYPE) {
        case "edu":
            return true;
    }
    return false;
}

/** @internal */
export function hasCommercialLicense() {
    return hasProLicense() || hasIndieLicense() || hasEduLicense();
}


/** @internal */
export function onLicenseCheckResultChanged(cb: (result: boolean) => void) {
    if (hasProLicense() || hasIndieLicense() || hasEduLicense())
        return cb(true);
    _licenseCheckResultChangedCallbacks.push(cb);
}

function invokeLicenseCheckResultChanged(result: boolean) {
    for (const cb of _licenseCheckResultChangedCallbacks) {
        try {
            cb(result);
        }
        catch {
            // ignore
        }
    }
}

ContextRegistry.registerCallback(ContextEvent.ContextRegistered, evt => {
    showLicenseInfo(evt.context);
    sendUsageMessageToAnalyticsBackend();
    handleForbidden(evt.context);
});

export let runtimeLicenseCheckPromise: Promise<void> | undefined = undefined;
let applicationIsForbidden = false;
let applicationForbiddenText = "";
async function checkLicense() {
    // Only perform the runtime license check once
    if (runtimeLicenseCheckPromise) return runtimeLicenseCheckPromise;
    if (NEEDLE_ENGINE_LICENSE_TYPE === "basic") {
        try {
            const licenseUrl = "https://engine.needle.tools/licensing/check?location=" + encodeURIComponent(window.location.href) + "&version=" + VERSION + "&generator=" + encodeURIComponent(GENERATOR);
            const res = await fetch(licenseUrl, {
                method: "GET",
            }).catch(_err => {
                if (debug) console.error("License check failed", _err);
                return undefined;
            });
            if (res?.status === 200) {
                applicationIsForbidden = false;
                if (debug) console.log("License check succeeded");
                NEEDLE_ENGINE_LICENSE_TYPE = "pro";
                invokeLicenseCheckResultChanged(true);
            }
            else if (res?.status === 403) {
                invokeLicenseCheckResultChanged(false);
                applicationIsForbidden = true;
                applicationForbiddenText = await res.text();
            }
            else {
                invokeLicenseCheckResultChanged(false);
                if (debug) console.log("License check failed with status " + res?.status);
            }
        }
        catch (err) {
            invokeLicenseCheckResultChanged(false);
            if (debug) console.error("License check failed", err);
        }
    }
    else if (debug) console.log("Runtime license check is skipped because license is already applied as \"" + NEEDLE_ENGINE_LICENSE_TYPE + "\"");
}
runtimeLicenseCheckPromise = checkLicense();

async function handleForbidden(ctx: IContext) {
    function createForbiddenElement() {
        const div = document.createElement("div");
        div.className = "needle-forbidden";
        div.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        pointer-events: all;
        zIndex: 2147483647;
        line-height: 1.5;
        backdrop-filter: blur(15px);
        -webkit-backdrop-filter: blur(15px);
        `;
        const expectedStyle = div.style.cssText;

        const text = document.createElement("div");
        div.appendChild(text);
        text.style.cssText = `
        position: absolute;
        left: 0;
        right: 0;
        top:0;
        bottom: 0;
        padding: 10%;
        color: white;
        font-size: 20px;
        font-family: sans-serif;
        text-align: center;
        pointer-events: all;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: rgba(0,0,0,.3);
        text-shadow: 0 0 2px black;
        `;
        const expectedTextStyle = text.style.cssText;
        const forbiddenText = applicationForbiddenText?.length > 1 ? applicationForbiddenText : "This web application has been paused.<br/>You might be in violation of the Needle Engine terms of use.<br/>Please contact the Needle support if you think this is a mistake.";
        text.innerHTML = forbiddenText;
        setInterval(() => {
            if (text.innerHTML !== forbiddenText) text.innerHTML = forbiddenText;
            if (text.parentNode !== div) div.appendChild(text);
            if (div.style.cssText !== expectedStyle) div.style.cssText = expectedStyle;
            if (text.style.cssText !== expectedTextStyle) text.style.cssText = expectedTextStyle;
        }, 500)
        return div;
    }
    let forbiddenElement = createForbiddenElement();
    const expectedCSS = forbiddenElement.style.cssText;
    setInterval(() => {
        if (applicationIsForbidden === true) {
            if (forbiddenElement.style.cssText !== expectedCSS) forbiddenElement = createForbiddenElement();
            if (ctx.domElement.shadowRoot) {
                if (forbiddenElement.parentNode !== ctx.domElement.shadowRoot)
                    ctx.domElement.shadowRoot?.appendChild(forbiddenElement);
            }
            else if (forbiddenElement.parentNode != document.body) {
                document.body.appendChild(forbiddenElement);
            }
        }
    }, 500)
}

async function showLicenseInfo(ctx: IContext) {
    try {
        if (!hasProLicense() && !hasIndieLicense()) {
            return onNonCommercialVersionDetected(ctx);
        }
    }
    catch (err) {
        if (debug) console.log("License check failed", err)
        return onNonCommercialVersionDetected(ctx)
    }
    if (debug) onNonCommercialVersionDetected(ctx)
}



async function onNonCommercialVersionDetected(ctx: IContext) {

    // if the engine loads faster than the license check, we need to capture the ready event here
    let isReady = false;
    ctx.domElement.addEventListener("ready", () => isReady = true);

    await runtimeLicenseCheckPromise?.catch(() => { });


    if (hasProLicense() || hasIndieLicense()) return;
    if (hasCommercialLicense() === false) logNonCommercialUse();

    // check if the engine is already ready (meaning has finished loading)
    if (isReady) {
        insertNonCommercialUseHint(ctx);
    }
    else {
        ctx.domElement.addEventListener("ready", () => {
            insertNonCommercialUseHint(ctx);
        });
    }
}

// const licenseElementIdentifier = "needle-license-element";
// const licenseDuration = 10000;
// const licenseDelay = 1200;
function insertNonCommercialUseHint(ctx: IContext) {

    const style = `
        position: relative;
        display: block;
        background-size: 20px;
        background-position: 10px 5px;
        background-repeat:no-repeat;
        background-image:url('${base64Logo}');
        background-max-size: 40px;
        padding: 10px;
        padding-left: 30px;
    `;
    if (NEEDLE_ENGINE_LICENSE_TYPE === "edu") {
        console.log("%c " + "Supported by Needle for Education – https://needle.tools", style);
    }
    else {
        // if the user has a basic license we already show the logo in the menu and log a license message
        return;
    }

    const banner = document.createElement("div");
    banner.className = "needle-non-commercial-use";
    banner.innerHTML = "Made with Needle for Education";
    ctx.domElement.shadowRoot?.appendChild(banner);
    let bannerStyle = `
        position: absolute;
        font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
        font-size: 12px;
        color: rgb(100, 100, 100);
        /*mix-blend-mode: difference;*/
        background-color: transparent;
        z-index: 10000;

        cursor: pointer;
        user-select: none;
        opacity: 0;

        bottom: 6px;
        right: 12px;
        transform: translateY(0px);
        transition: all .5s ease-in-out 1s;
    `;
    banner.style.cssText = bannerStyle;
    banner.addEventListener("click", () => { window.open("https://needle.tools", "_blank") });
    let expectedBannerStyle = banner.style.cssText;
    setTimeout(() => {
        bannerStyle = bannerStyle.replace("opacity: 0", "opacity: 1");
        bannerStyle = bannerStyle.replace("transform: translateY(10px)", "transform: translateY(0)");
        banner.style.cssText = bannerStyle;
        expectedBannerStyle = banner.style.cssText;
    }, 100);

    // ensure the banner is always visible
    const interval = setInterval(() => {
        const parent = ctx.domElement.shadowRoot || ctx.domElement;
        if (banner.parentNode !== parent) {
            parent.appendChild(banner);
        }
        if (expectedBannerStyle != banner.style.cssText) {
            banner.style.cssText = bannerStyle;
            expectedBannerStyle = banner.style.cssText;
        }
    }, 1000);

    if (hasEduLicense()) {
        const removeDelay = 20_000;
        setTimeout(() => {
            clearInterval(interval);
            banner?.remove();
            // show the logo every x minutes
            const intervalInMinutes = 5;
            setTimeout(() => {
                if (ctx.domElement.parentNode)
                    insertNonCommercialUseHint(ctx);
            }, 1000 * 60 * intervalInMinutes)
        }, removeDelay);
    }

}


const base64Logo = "data:image/webp;base64,UklGRrABAABXRUJQVlA4WAoAAAAQAAAAHwAAHwAAQUxQSKEAAAARN6CmbSM4WR7vdARON11EBDq3fLiNbVtVzpMCPlKAEzsx0Y/x+Ovuv4dn0EFE/ydAvz6YggXzgh5sVgXM/zOC/4sii7qgGvB5N7hmuQYwkvazWAu1JPW41FXSHq6pnaQWvqYH18Fc0j1hO/BFTtIeSBlJi5w6qIIO7IOrwhFsB2Yxukif0FTRLpXswHR8MxbslKe9VZsn/Ub5C7YFOpqSTABWUDgg6AAAAFAGAJ0BKiAAIAA+7VyoTqmkpCI3+qgBMB2JbACdMt69DwMIQBLhkTO6XwY00UEDK6cNIDnuNibPf0EgAP7Y1myuiQHLDsF/0h5unrGh6WAbv7aegg2ZMd3uRKfT/3SJztcaujYfTvMXspfCTmYcoO6a+vhC3ss4M8uM58t4siiu59I4aOl59e9Sr6xoxYlHf2v+NnBNpJYeJf8jABQAId/PXuBkLEFkiCucgSGEcfhvajql/j3reCGl0M5/9gQWy7ayNPs+wlvIxFnNfSlfuND4CZOCyxOHhRqOmHN4ULHo3tCSrUNvgAA=";

let lastLogTime = 0;
async function logNonCommercialUse(_logo?: string) {
    const now = Date.now();
    if (now - lastLogTime < 2000) return;
    lastLogTime = now;
    const style = `
        position: relative;
        display: block;
        font-size: 18px;
        background-size: 20px;
        background-position: 10px 5px;
        background-repeat:no-repeat;
        background-image:url('${base64Logo}');
        background-max-size: 40px;
        margin-bottom: 5px;
        margin-top: .3em;
        margin-bottom: .5em;
        padding: .2em;
        padding-left: 25px;
        border-radius: .5em;
        border: 2px solid rgba(160,160,160,.3);
    `;
    // url must contain https for firefox to make it clickable
    const version = VERSION;
    const licenseText = `Needle Engine — No license active, commercial use is not allowed. Visit https://needle.tools/pricing for more information and licensing options! v${version}`;
    if (Context.Current?.xr) {
        console.log(licenseText);
    }
    else {
        console.log("%c " + licenseText, style);
    }
}


async function sendUsageMessageToAnalyticsBackend() {
    // We can't send beacons from cross-origin isolated pages
    if (window.crossOriginIsolated) return;

    try {
        const analyticsUrl = "https://needle-engine-analytics-v2-r26roub2hq-lz.a.run.app";
        if (analyticsUrl) {
            if (debug) console.log("Analytics backend url", analyticsUrl);

            // analyticsUrl = "http://localhost:3000/";

            // current url without query parameters
            const currentUrl = window.location.href.split("?")[0];

            let endpoint = "api/v2/new/request";
            if (!analyticsUrl.endsWith("/")) endpoint = "/" + endpoint;
            const license = NEEDLE_ENGINE_LICENSE_TYPE;
            const finalUrl = `${analyticsUrl}${endpoint}`;
            if (debug) console.log("Sending non-commercial usage message to analytics backend", finalUrl);

            const beaconData = {
                license,
                url: currentUrl,
                hostname: window.location.hostname,
                pathname: window.location.pathname,
                // search: window.location.search,
                // hash: window.location.hash,
                version: VERSION,
                generator: GENERATOR,
                build_time: BUILD_TIME,
                public_key: PUBLIC_KEY,
            };
            const res = navigator.sendBeacon?.(finalUrl, JSON.stringify(beaconData));
            if (debug) console.log("Send beacon result", res);
        }
    }
    catch (err) {
        if (debug)
            console.log("Failed to send non-commercial usage message to analytics backend", err);
    }
}