/* eslint-disable unicorn/no-null */

const sha256 = async (str) => {
    const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));
    return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, '0')).join('');
};

const getCanvasFingerprint = async () => {
    try {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        if (!ctx) return null;
        canvas.width = 220;
        canvas.height = 30;
        ctx.textBaseline = 'top';
        ctx.font = '14px Arial';
        ctx.fillStyle = '#f60';
        ctx.fillRect(0, 0, 50, 30);
        ctx.fillStyle = '#069';
        ctx.fillText('fingerprint123', 2, 2);
        return await sha256(canvas.toDataURL());
    } catch {
        return null;
    }
};

const getWebGLFingerprint = async () => {
    try {
        const canvas = document.createElement('canvas');
        const gl = canvas.getContext('webgl');
        if (!gl) return null;
        const dbg = gl.getExtension('WEBGL_debug_renderer_info');
        const props = {
            vendor: dbg ? gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR),
            renderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER),
            version: gl.getParameter(gl.VERSION),
            shadingLanguage: gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
            maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
            aliasedLineWidthRange: gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE),
            aliasedPointSizeRange: gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE),
            redBits: gl.getParameter(gl.RED_BITS),
            greenBits: gl.getParameter(gl.GREEN_BITS),
            blueBits: gl.getParameter(gl.BLUE_BITS),
            alphaBits: gl.getParameter(gl.ALPHA_BITS),
            depthBits: gl.getParameter(gl.DEPTH_BITS),
            stencilBits: gl.getParameter(gl.STENCIL_BITS),
            extensions: gl.getSupportedExtensions(),
        };
        return { hash: await sha256(JSON.stringify(props)), props };
    } catch {
        return null;
    }
};

const getAudioFingerprint = async () => {
    try {
        const Offline = window.OfflineAudioContext || window.webkitOfflineAudioContext;
        if (!Offline) return null;
        const ctx = new Offline(1, 44_100, 44_100);
        const osc = ctx.createOscillator();
        const comp = ctx.createDynamicsCompressor();
        osc.type = 'triangle';
        osc.frequency.value = 10_000;
        osc.connect(comp);
        comp.connect(ctx.destination);
        osc.start(0);
        const buf = await ctx.startRendering();
        let sum = 0;
        const data = buf.getChannelData(0);
        for (let i = 0; i < data.length; i += 100) sum += Math.abs(data[i]);
        return await sha256(String(sum));
    } catch {
        return null;
    }
};

const getMathFingerprint = async () => {
    try {
        const values = {
            acos: Math.acos(0.123_456_789),
            asin: Math.asin(0.123_456_789),
            atan: Math.atan(0.123_456_789),
            sin: Math.sin(1e37),
            cos: Math.cos(1e-37),
            tan: Math.tan(10),
            exp: Math.exp(1),
            log: Math.log(10),
            sqrt: Math.sqrt(2),
            pow: Math.pow(123_456.789, -0.123),
            round: (0.1 + 0.2).toFixed(20),
            toExp: (123_456_789).toExponential(20),
            oneDivNegZero: 1 / -0,
        };
        return await sha256(JSON.stringify(values));
    } catch {
        return null;
    }
};

const getBattery = async () => {
    try {
        if (!navigator.getBattery) return null;
        const b = await navigator.getBattery();
        return {
            charging: b.charging,
            level: b.level,
            chargingTime: b.chargingTime,
            dischargingTime: b.dischargingTime,
        };
    } catch {
        return null;
    }
};

const getWebGPU = async () => {
    try {
        if (!navigator.gpu?.requestAdapter) return null;
        const adapter = await navigator.gpu.requestAdapter();
        if (!adapter) return null;
        return {
            name: adapter.name,
            features: [...(adapter.features?.values?.() ?? [])],
            limits: adapter.limits ? Object.fromEntries(Object.entries(adapter.limits)) : {},
        };
    } catch {
        return null;
    }
};

const getStorage = async () => {
    try {
        const est = await navigator.storage.estimate();
        return { quota: est.quota, usage: est.usage };
    } catch {
        return null;
    }
};

const getVoices = () => {
    try {
        if (!window.speechSynthesis) return null;
        return window.speechSynthesis.getVoices().map((v) => ({
            name: v.name,
            lang: v.lang,
            local: v.localService,
            default: v.default,
        }));
    } catch {
        return null;
    }
};

const getNetwork = () => {
    try {
        const c = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
        if (!c) return null;
        return {
            downlink: c.downlink,
            effectiveType: c.effectiveType,
            rtt: c.rtt,
            saveData: c.saveData,
        };
    } catch {
        return null;
    }
};

const getIntlInfo = () => {
    try {
        const locales = navigator.languages ?? [navigator.language];
        const dateSample = new Date(2020, 11, 31, 19, 5, 7);
        const numSample = 1_234_567.89;
        return {
            locales,
            date: new Intl.DateTimeFormat(locales).format(dateSample),
            time: new Intl.DateTimeFormat(locales, { hour: 'numeric', minute: 'numeric' }).format(dateSample),
            num: new Intl.NumberFormat(locales).format(numSample),
            currency: new Intl.NumberFormat(locales, { style: 'currency', currency: 'USD' }).format(numSample),
            resolved: {
                date: new Intl.DateTimeFormat(locales).resolvedOptions(),
                num: new Intl.NumberFormat(locales).resolvedOptions(),
            },
        };
    } catch {
        return null;
    }
};

const getFeatures = () => ({
    webgl: !!window.WebGLRenderingContext,
    workers: !!window.Worker,
    wasm: typeof WebAssembly === 'object',
    hid: !!navigator.hid,
    bluetooth: !!navigator.bluetooth,
    gpu: !!navigator.gpu,
    share: !!navigator.share,
    clipboard: !!navigator.clipboard,
    idle: !!navigator.requestIdleCallback,
});

const mq = (q) => window.matchMedia(q).matches;

const getCSSFeatures = () => ({
    prefersColorSchemeDark: mq('(prefers-color-scheme: dark)'),
    prefersColorSchemeLight: mq('(prefers-color-scheme: light)'),
    prefersReducedMotion: mq('(prefers-reduced-motion: reduce)'),
    prefersReducedTransparency: mq('(prefers-reduced-transparency: reduce)'),
    prefersReducedData: mq('(prefers-reduced-data: reduce)'),
    prefersContrastMore: mq('(prefers-contrast: more)'),
    prefersContrastLess: mq('(prefers-contrast: less)'),
    forcedColorsActive: mq('(forced-colors: active)'),
    invertedColorsInverted: mq('(inverted-colors: inverted)'),
    hoverHover: mq('(hover: hover)'),
    hoverNone: mq('(hover: none)'),
    anyHoverHover: mq('(any-hover: hover)'),
    anyHoverNone: mq('(any-hover: none)'),
    pointerFine: mq('(pointer: fine)'),
    pointerCoarse: mq('(pointer: coarse)'),
    pointerNone: mq('(pointer: none)'),
    anyPointerFine: mq('(any-pointer: fine)'),
    anyPointerCoarse: mq('(any-pointer: coarse)'),
    anyPointerNone: mq('(any-pointer: none)'),
    lightLevelDim: mq('(light-level: dim)'),
    lightLevelNormal: mq('(light-level: normal)'),
    lightLevelWashed: mq('(light-level: washed)'),
});

export const get = async () => {
    return {
        date: Date.now(),

        // Базовые данные
        ua: navigator.userAgent,
        platform: navigator.platform ?? null,
        lang: navigator.language,
        languages: navigator.languages,
        timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
        dnt: navigator.doNotTrack === '1',

        // Hardware
        width: window.innerWidth,
        height: window.innerHeight,
        devicePixelRatio: window.devicePixelRatio,
        hardwareConcurrency: navigator.hardwareConcurrency,
        deviceMemory: navigator.deviceMemory ?? null,
        maxTouchPoints: navigator.maxTouchPoints ?? 0,

        // Storage / Cookies
        cookies: navigator.cookieEnabled,
        localStorage: !!window.localStorage,
        sessionStorage: !!window.sessionStorage,
        storage: await getStorage(),

        // Fingerprints
        canvas: await getCanvasFingerprint(),
        webgl: await getWebGLFingerprint(),
        audio: await getAudioFingerprint(),
        math: await getMathFingerprint(),

        // Power
        battery: await getBattery(),

        // Graphics
        webgpu: await getWebGPU(),

        // Lists
        plugins: [...(navigator.plugins ?? [])].map(({ name, filename, description }) => ({
            name,
            filename,
            description,
        })),
        mimes: [...(navigator.mimeTypes ?? [])].map(({ type, suffixes }) => ({ type, suffixes })),

        // Voices
        voices: getVoices(),

        // Network
        network: getNetwork(),

        // Intl
        intl: getIntlInfo(),

        // Возможности
        features: getFeatures(),
        css: getCSSFeatures(),
    };
};
