import {
    networkInterfaces as os_networkInterfaces,
    platform as os_platform,
    release as os_release
} from 'os'

import { v4 as uuidv4 } from 'uuid';
import {EventContext } from '../core/typings';

import isBrowser from "is-in-browser";

const VISIBILITY_HIDDEN = 'hidden'
const VISIBILITY_VISIBLE = 'visible'
const UserIdStorageKey = 'CFUserIdStorageKey'
let visibilityInitialized = false

export interface NetworkInformation {
    downlink: number,
    uplink: number
}

export type DeviceInfoObject = {
    brand: string,
    model: string,
    os: string,
    os_ver: string,
}

export type AppInfoObject = {
    version_code: number,
    version_name: string,
    version: string,
}

export function getDeviceId(): string {

    const deviceIdStorageKey = 'deviceIdStorageKey'

    let deviceId = localStorage.getItem(deviceIdStorageKey)
    if (!deviceId) {
        deviceId = uuidv4()
        localStorage.setItem(deviceIdStorageKey, deviceId)
    }
    return deviceId
}

export function setLocalStorageUserId(userId : string) {
    if (userId) {
        localStorage.setItem(UserIdStorageKey, userId)
    }
}

export function getLocalStorageUserId(): string {
    const userId = localStorage.getItem(UserIdStorageKey)
    return userId? userId : ''
}

export function clearLocalStorageUserId() {
    localStorage.setItem(UserIdStorageKey, '')
}

export function getDeviceDetails(): DeviceInfoObject {

    if (!isBrowser) {
        return {
            brand: `NO_BROWSER`,
            model: `NO_BROWSER`,
            os: `${os_platform()}`,
            os_ver: `${os_release()}`
        }
    }

    // Current web specification defines `window.navigator.userAgentData.platform
    // as the official way to retrieve the operating system, but it is not
    // widely adopted by browsers. To avoid that problem this function
    // also takes into account the deprecated mechanism `window.navigator.platform`
    if (!window?.navigator) {
        return {
            brand: '',
            model: '',
            os: '',
            os_ver: ''
        }
    }

    const platform = window.navigator["userAgentData"]?.platform
    const platformLegacy = window.navigator.platform
    const userAgentValue = window.navigator.userAgent

    return {
        brand: platform || platformLegacy,
        model: userAgentValue.match(/\(([^)]+)\)/)[1].split(';')[0],
        os: platform || platformLegacy,
        os_ver: userAgentValue.match(/\(([^)]+)\)/)[1].split(';')[1].trim()
    }
}

export function getAppDetails(): AppInfoObject {


    // Current web specification defines `window.navigator.userAgentData.platform
    // as the official way to retrieve the operating system, but it is not
    // widely adopted by browsers. To avoid that problem this function
    // also takes into account the deprecated mechanism `window.navigator.platform`
    // Also for non browser env
    if (!isBrowser || !window?.navigator || window.navigator.userAgent === "") {
        return {
            version_code: 0,
            version_name: 'NO_BROWSER',
            version: 'NO_BROWSER',
        }
    }
    const appVersion = getBrowserVersion()

    return {
        version_code: parseInt(appVersion.split(" ")[1] || "0"),
        version_name: appVersion.split(" ")[0] || "0",
        version: appVersion  || "0",
    }
}

function getBrowserVersion() : string{
    const ua= navigator.userAgent;
    let tem;
    let M= ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
    if(/trident/i.test(M[1])){
        tem=  /\brv[ :]+(\d+)/g.exec(ua) || [];
        return 'IE '+(tem[1] || '');
    }
    if(M[1]=== 'Chrome'){
        tem= ua.match(/\b(OPR|Edge)\/(\d+)/);
        if(tem!= null) return tem.slice(1).join(' ').replace('OPR', 'Opera');
    }
    M= M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
    if((tem= ua.match(/version\/(\d+)/i))!= null) M.splice(1, 1, tem[1]);
    return M.join(' ');
}

export function getOperatingSystem(): string {

    if (!isBrowser) {
        return `${os_platform()}-${os_release()}`
    }

    // Current web specification defines `window.navigator.userAgentData.platform
    // as the official way to retrieve the operating system, but it is not
    // widely adopted by browsers. To avoid that problem this function
    // also takes into account the deprecated mechanism `window.navigator.platform`
    if (!window?.navigator) {
        return ''
    }

    const platform = window.navigator["userAgentData"]?.platform
    const platformLegacy = window.navigator.platform

    return platform || platformLegacy
}

export function getNetworkInformation(): NetworkInformation {

    if (!isBrowser) {
        return {
            downlink: 0,
            uplink: 0
        }
    }

    // in case the speed is not available, we have agreed to send 0
    // otherwise, return the network speed in kbps
    const connection = (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection;

    if (!connection) {
        return {
            downlink: 0,
            uplink: 0
        }
    }

    return {
        downlink: parseInt(((connection.downlink || 0) * 1000).toString()),
        uplink: 0
    }
}

export function listenLocationChangedEvent(clb) {
    let originalPushState = window.history.pushState;
    let originalReplaceState = window.history.replaceState;

    window.history.pushState = function (data, title, url) {
        let currentPath = url || ''  // first time is undefined
        if(currentPath[0] != '/'){
            currentPath = `/${currentPath}`
        }
        clb(`${window.location.origin}${currentPath}`)
        return originalPushState.call(window.history, data, title, url);
    }

    window.history.replaceState = function (data, title, url) {
        let currentPath = url || ''  // first time is undefined
        if(currentPath[0] != '/'){
            currentPath = `/${currentPath}`
        }
        clb(`${window.location.origin}${currentPath}`)

        return originalReplaceState.call(window.history, data, title, url);
    }

    window.addEventListener('popstate', function (evt) {
        clb((evt.target as Window).location.href)
    });
}


export function listenApplicationClose(clb) {
    window.addEventListener('unload', () => {
        clb()
    })
}

export function listenApplicationVisibilityChange(clb_visible, clb_hide) {
    if (document.visibilityState === VISIBILITY_VISIBLE) {
        visibilityInitialized = true
    }

    document.addEventListener('visibilitychange', e => {
        console.warn('[event] [visibilitychange] ', document.visibilityState)
        if (document.visibilityState === VISIBILITY_VISIBLE) {
            visibilityInitialized = true

            clb_visible()
        } else if (document.visibilityState === VISIBILITY_HIDDEN) {
            if (!visibilityInitialized) {
                return
            }

            clb_hide()
        }
    })
}

export function listenPageChanged(clb) {
    listenLocationChangedEvent(newUrl => {
        // the reason of this delay is that the new title
        // is not available yet when the location-changed
        // event is fired
        setTimeout(() => {
            clb(newUrl, document.title)
        }, 10)
    })
}

export function listenPageScrolled(threshold, clb) {
    // const currentScrollTop = (): number => (window.pageYOffset || document.documentElement.scrollTop)  - (document.documentElement.clientTop || 0)

    const getScrollPercent = () => {
        const SCROLL_TOP = 'scrollTop'
        const SCROLL_HEIGHT = 'scrollHeight'

        const currentScrollTop = document.documentElement[SCROLL_TOP] || document.body[SCROLL_TOP]
        const totalScrollHeight = document.documentElement[SCROLL_HEIGHT] || document.body[SCROLL_HEIGHT]
        const visibleHeight = document.documentElement.clientHeight

        return currentScrollTop / (totalScrollHeight - visibleHeight) * 100;
    }

    window.addEventListener('scroll', (e: Event) => {
        const scroll = getScrollPercent()

        if (scroll < threshold) {
            return
        }

        clb(scroll)
    })
}

// legacy function. Leave it here for reusing it when needed
async function getContext(): Promise<EventContext> {
    const context: EventContext = {}

    if (!!navigator) {
        const battery = (await (navigator as any).getBattery()) || null
        context.browser = {
            user_agent: window.navigator.userAgent,
            languages: [...window.navigator.languages],
            online: navigator.onLine,
            battery: battery ? {
                charging: battery.charging,
                chargingTime: battery.chargingTime,
                dischargingTime: battery.dischargingTime,
                level: battery.level
            } : null
        }
    }

    return context
}

export function isDevEnvironment() {
    return document.location.search.includes('cfenv=dev')
}

export function init(): void {
    window.addEventListener('beforeunload', () => {
    })
}
