import boxen, { type Options as BoxenOptions } from 'boxen';
import chalk from 'chalk';
import open from 'open';
import { shuffle } from 'd3-array';
import { z } from 'zod';

import got from '@lib/got';
import getOsDistro from '@lib/host/getOsDistro.js';
import { txDevEnv, txEnv, txHostConfig } from '@core/globalData';
import consoleFactory from '@lib/console';
import { addLocalIpAddress } from '@lib/host/isIpAddressLocal';
import { chalkInversePad } from '@lib/misc';
const console = consoleFactory();


const getPublicIp = async () => {
    const zIpValidator = z.string().ip();
    const reqOptions = {
        timeout: { request: 2000 },
    };
    const httpGetter = async (url: string, jsonPath: string) => {
        const res = await got(url, reqOptions).json();
        return zIpValidator.parse((res as any)[jsonPath]);
    };

    const allApis = shuffle([
        ['https://api.ipify.org?format=json', 'ip'],
        ['https://api.myip.com', 'ip'],
        ['https://ipv4.jsonip.com/', 'ip'],
        ['https://api.my-ip.io/v2/ip.json', 'ip'],
        ['https://www.l2.io/ip.json', 'ip'],
    ]);
    for await (const [url, jsonPath] of allApis) {
        try {
            return await httpGetter(url, jsonPath);
        } catch (error) { }
    }
    return false;
};

const getOSMessage = async () => {
    const serverMessage = [
        `To be able to access txAdmin from the internet open port ${txHostConfig.txaPort}`,
        'on your OS Firewall as well as in the hosting company.',
    ];
    const winWorkstationMessage = [
        '[!] Home-hosting fxserver is not recommended [!]',
        'You need to open the fxserver port (usually 30120) on Windows Firewall',
        'and set up port forwarding on your router so other players can access it.',
    ];
    if (txEnv.displayAds) {
        winWorkstationMessage.push('We recommend renting a server from ' + chalk.inverse(' https://zap-hosting.com/txAdmin ') + '.');
    }

    //FIXME: use si.osInfo() instead
    const distro = await getOsDistro();
    return (distro && distro.includes('Linux') || distro.includes('Server'))
        ? serverMessage
        : winWorkstationMessage;
};

const awaitHttp = new Promise((resolve, reject) => {
    const tickLimit = 100; //if over 15 seconds
    let counter = 0;
    let interval: NodeJS.Timeout;
    const check = () => {
        counter++;
        if (txCore.webServer && txCore.webServer.isListening && txCore.webServer.isServing) {
            clearInterval(interval);
            resolve(true);
        } else if (counter == tickLimit) {
            clearInterval(interval);
            interval = setInterval(check, 2500);
        } else if (counter > tickLimit) {
            console.warn('The WebServer is taking too long to start:', {
                module: !!txCore.webServer,
                listening: txCore?.webServer?.isListening,
                serving: txCore?.webServer?.isServing,
            });
        }
    };
    interval = setInterval(check, 150);
});

const awaitMasterPin = new Promise((resolve, reject) => {
    const tickLimit = 100; //if over 15 seconds
    let counter = 0;
    let interval: NodeJS.Timeout;
    const check = () => {
        counter++;
        if (txCore.adminStore && txCore.adminStore.admins !== null) {
            clearInterval(interval);
            const pin = (txCore.adminStore.admins === false) ? txCore.adminStore.addMasterPin : false;
            resolve(pin);
        } else if (counter == tickLimit) {
            clearInterval(interval);
            interval = setInterval(check, 2500);
        } else if (counter > tickLimit) {
            console.warn('The AdminStore is taking too long to start:', {
                module: !!txCore.adminStore,
                admins: txCore?.adminStore?.admins === null ? 'null' : 'not null',
            });
        }
    };
    interval = setInterval(check, 150);
});

const awaitDatabase = new Promise((resolve, reject) => {
    const tickLimit = 100; //if over 15 seconds
    let counter = 0;
    let interval: NodeJS.Timeout;
    const check = () => {
        counter++;
        if (txCore.database && txCore.database.isReady) {
            clearInterval(interval);
            resolve(true);
        } else if (counter == tickLimit) {
            clearInterval(interval);
            interval = setInterval(check, 2500);
        } else if (counter > tickLimit) {
            console.warn('The Database is taking too long to start:', {
                module: !!txCore.database,
                ready: !!txCore?.database?.isReady,
            });
        }
    };
    interval = setInterval(check, 150);
});


export const startReadyWatcher = async (cb: () => void) => {
    const [publicIpResp, msgRes, adminPinRes] = await Promise.allSettled([
        getPublicIp(),
        getOSMessage(),
        awaitMasterPin as Promise<undefined | string | false>,
        awaitHttp,
        awaitDatabase,
    ]);

    //Addresses
    let detectedUrls;
    if (txHostConfig.netInterface && txHostConfig.netInterface !== '0.0.0.0') {
        detectedUrls = [txHostConfig.netInterface];
    } else {
        detectedUrls = [
            (txEnv.isWindows) ? 'localhost' : 'your-public-ip',
        ];
        if ('value' in publicIpResp && publicIpResp.value) {
            detectedUrls.push(publicIpResp.value);
            addLocalIpAddress(publicIpResp.value);
        }
    }
    const bannerUrls = txHostConfig.txaUrl
        ? [txHostConfig.txaUrl]
        : detectedUrls.map((addr) => `http://${addr}:${txHostConfig.txaPort}/`);

    //Admin PIN
    const adminMasterPin = 'value' in adminPinRes && adminPinRes.value ? adminPinRes.value : false;
    const adminPinLines = !adminMasterPin ? [] : [
        '',
        'Use the PIN below to register:',
        chalk.inverse(` ${adminMasterPin} `),
    ];

    //Printing stuff
    const boxOptions = {
        padding: 1,
        margin: 1,
        align: 'center',
        borderStyle: 'bold',
        borderColor: 'cyan',
    } satisfies BoxenOptions;
    const boxLines = [
        'All ready! Please access:',
        ...bannerUrls.map(chalkInversePad),
        ...adminPinLines,
    ];
    console.multiline(boxen(boxLines.join('\n'), boxOptions), chalk.bgGreen);
    if (!txDevEnv.ENABLED && !txHostConfig.netInterface && 'value' in msgRes && msgRes.value) {
        console.multiline(msgRes.value, chalk.bgBlue);
    }

    //Opening page
    if (txEnv.isWindows && adminMasterPin && bannerUrls[0]) {
        const linkUrl = new URL(bannerUrls[0]);
        linkUrl.pathname = '/addMaster/pin';
        linkUrl.hash = adminMasterPin;
        open(linkUrl.href);
    }

    //Callback
    cb();
};
