import { sha256 } from "@oslojs/crypto/sha2";
import { encodeHexLowerCase } from "@oslojs/encoding";
import { currentFingerprint, db, fingerprintSnapshots, Olm } from "@server/db";
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
import { desc, eq, lt } from "drizzle-orm";

function fingerprintSnapshotHash(fingerprint: any, postures: any): string {
    const canonical = {
        username: fingerprint.username ?? null,
        hostname: fingerprint.hostname ?? null,
        platform: fingerprint.platform ?? null,
        osVersion: fingerprint.osVersion ?? null,
        kernelVersion: fingerprint.kernelVersion ?? null,
        arch: fingerprint.arch ?? null,
        deviceModel: fingerprint.deviceModel ?? null,
        serialNumber: fingerprint.serialNumber ?? null,
        platformFingerprint: fingerprint.platformFingerprint ?? null,

        biometricsEnabled: postures.biometricsEnabled ?? false,
        diskEncrypted: postures.diskEncrypted ?? false,
        firewallEnabled: postures.firewallEnabled ?? false,
        autoUpdatesEnabled: postures.autoUpdatesEnabled ?? false,
        tpmAvailable: postures.tpmAvailable ?? false,

        windowsAntivirusEnabled: postures.windowsAntivirusEnabled ?? false,

        macosSipEnabled: postures.macosSipEnabled ?? false,
        macosGatekeeperEnabled: postures.macosGatekeeperEnabled ?? false,
        macosFirewallStealthMode: postures.macosFirewallStealthMode ?? false,

        linuxAppArmorEnabled: postures.linuxAppArmorEnabled ?? false,
        linuxSELinuxEnabled: postures.linuxSELinuxEnabled ?? false
    };

    return encodeHexLowerCase(
        sha256(new TextEncoder().encode(JSON.stringify(canonical)))
    );
}

export async function handleFingerprintInsertion(
    olm: Olm,
    fingerprint: any,
    postures: any
) {
    if (
        !olm?.olmId ||
        !fingerprint ||
        !postures ||
        Object.keys(fingerprint).length === 0 ||
        Object.keys(postures).length === 0
    ) {
        return;
    }

    const now = Math.floor(Date.now() / 1000);
    const hash = fingerprintSnapshotHash(fingerprint, postures);

    const [current] = await db
        .select()
        .from(currentFingerprint)
        .where(eq(currentFingerprint.olmId, olm.olmId))
        .limit(1);

    if (!current) {
        const [inserted] = await db
            .insert(currentFingerprint)
            .values({
                olmId: olm.olmId,
                firstSeen: now,
                lastSeen: now,
                lastCollectedAt: now,

                // fingerprint
                username: fingerprint.username,
                hostname: fingerprint.hostname,
                platform: fingerprint.platform,
                osVersion: fingerprint.osVersion,
                kernelVersion: fingerprint.kernelVersion,
                arch: fingerprint.arch,
                deviceModel: fingerprint.deviceModel,
                serialNumber: fingerprint.serialNumber,
                platformFingerprint: fingerprint.platformFingerprint,

                biometricsEnabled: postures.biometricsEnabled,
                diskEncrypted: postures.diskEncrypted,
                firewallEnabled: postures.firewallEnabled,
                autoUpdatesEnabled: postures.autoUpdatesEnabled,
                tpmAvailable: postures.tpmAvailable,

                windowsAntivirusEnabled: postures.windowsAntivirusEnabled,

                macosSipEnabled: postures.macosSipEnabled,
                macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
                macosFirewallStealthMode: postures.macosFirewallStealthMode,

                linuxAppArmorEnabled: postures.linuxAppArmorEnabled,
                linuxSELinuxEnabled: postures.linuxSELinuxEnabled
            })
            .returning();

        await db.insert(fingerprintSnapshots).values({
            fingerprintId: inserted.fingerprintId,

            username: fingerprint.username,
            hostname: fingerprint.hostname,
            platform: fingerprint.platform,
            osVersion: fingerprint.osVersion,
            kernelVersion: fingerprint.kernelVersion,
            arch: fingerprint.arch,
            deviceModel: fingerprint.deviceModel,
            serialNumber: fingerprint.serialNumber,
            platformFingerprint: fingerprint.platformFingerprint,

            biometricsEnabled: postures.biometricsEnabled,
            diskEncrypted: postures.diskEncrypted,
            firewallEnabled: postures.firewallEnabled,
            autoUpdatesEnabled: postures.autoUpdatesEnabled,
            tpmAvailable: postures.tpmAvailable,

            windowsAntivirusEnabled: postures.windowsAntivirusEnabled,

            macosSipEnabled: postures.macosSipEnabled,
            macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
            macosFirewallStealthMode: postures.macosFirewallStealthMode,

            linuxAppArmorEnabled: postures.linuxAppArmorEnabled,
            linuxSELinuxEnabled: postures.linuxSELinuxEnabled,

            hash,
            collectedAt: now
        });

        return;
    }

    const [latestSnapshot] = await db
        .select({ hash: fingerprintSnapshots.hash })
        .from(fingerprintSnapshots)
        .where(eq(fingerprintSnapshots.fingerprintId, current.fingerprintId))
        .orderBy(desc(fingerprintSnapshots.collectedAt))
        .limit(1);

    const changed = !latestSnapshot || latestSnapshot.hash !== hash;

    if (changed) {
        await db.insert(fingerprintSnapshots).values({
            fingerprintId: current.fingerprintId,

            username: fingerprint.username,
            hostname: fingerprint.hostname,
            platform: fingerprint.platform,
            osVersion: fingerprint.osVersion,
            kernelVersion: fingerprint.kernelVersion,
            arch: fingerprint.arch,
            deviceModel: fingerprint.deviceModel,
            serialNumber: fingerprint.serialNumber,
            platformFingerprint: fingerprint.platformFingerprint,

            biometricsEnabled: postures.biometricsEnabled,
            diskEncrypted: postures.diskEncrypted,
            firewallEnabled: postures.firewallEnabled,
            autoUpdatesEnabled: postures.autoUpdatesEnabled,
            tpmAvailable: postures.tpmAvailable,

            windowsAntivirusEnabled: postures.windowsAntivirusEnabled,

            macosSipEnabled: postures.macosSipEnabled,
            macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
            macosFirewallStealthMode: postures.macosFirewallStealthMode,

            linuxAppArmorEnabled: postures.linuxAppArmorEnabled,
            linuxSELinuxEnabled: postures.linuxSELinuxEnabled,

            hash,
            collectedAt: now
        });

        await db
            .update(currentFingerprint)
            .set({
                lastSeen: now,
                lastCollectedAt: now,

                username: fingerprint.username,
                hostname: fingerprint.hostname,
                platform: fingerprint.platform,
                osVersion: fingerprint.osVersion,
                kernelVersion: fingerprint.kernelVersion,
                arch: fingerprint.arch,
                deviceModel: fingerprint.deviceModel,
                serialNumber: fingerprint.serialNumber,
                platformFingerprint: fingerprint.platformFingerprint,

                biometricsEnabled: postures.biometricsEnabled,
                diskEncrypted: postures.diskEncrypted,
                firewallEnabled: postures.firewallEnabled,
                autoUpdatesEnabled: postures.autoUpdatesEnabled,
                tpmAvailable: postures.tpmAvailable,

                windowsAntivirusEnabled: postures.windowsAntivirusEnabled,

                macosSipEnabled: postures.macosSipEnabled,
                macosGatekeeperEnabled: postures.macosGatekeeperEnabled,
                macosFirewallStealthMode: postures.macosFirewallStealthMode,

                linuxAppArmorEnabled: postures.linuxAppArmorEnabled,
                linuxSELinuxEnabled: postures.linuxSELinuxEnabled
            })
            .where(eq(currentFingerprint.fingerprintId, current.fingerprintId));
    } else {
        await db
            .update(currentFingerprint)
            .set({ lastSeen: now })
            .where(eq(currentFingerprint.fingerprintId, current.fingerprintId));
    }
}

export async function cleanUpOldFingerprintSnapshots(retentionDays: number) {
    const cutoff = calculateCutoffTimestamp(retentionDays);

    await db
        .delete(fingerprintSnapshots)
        .where(lt(fingerprintSnapshots.collectedAt, cutoff));
}
