import { createHash, generateKeyPairSync, randomBytes, sign, verify, X509Certificate, } from "node:crypto";
import { chmodSync, existsSync, mkdirSync, readFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import selfsigned from "selfsigned";
import { readJsonFileWithBackup, writeJsonFileAtomic, writeTextFileAtomic } from "../state/persistence.js";
const DEFAULT_HOME = path.join(os.homedir(), ".nordrelay");
export function loadOrCreatePeerIdentity(home = process.env.NORDRELAY_HOME || DEFAULT_HOME, name) {
    const filePath = path.join(home, "identity.json");
    const existing = readJsonFileWithBackup(filePath).value;
    if (existing?.nodeId && existing.publicKey && existing.privateKey && existing.fingerprint) {
        return {
            public: {
                nodeId: existing.nodeId,
                name: existing.name || defaultNodeName(),
                publicKey: existing.publicKey,
                fingerprint: existing.fingerprint,
                createdAt: existing.createdAt,
            },
            privateKey: existing.privateKey,
        };
    }
    const pair = generateKeyPairSync("ed25519", {
        publicKeyEncoding: { type: "spki", format: "pem" },
        privateKeyEncoding: { type: "pkcs8", format: "pem" },
    });
    const publicKey = pair.publicKey.toString();
    const createdAt = new Date().toISOString();
    const identity = {
        nodeId: createNodeId(publicKey),
        name: name?.trim() || defaultNodeName(),
        publicKey,
        privateKey: pair.privateKey.toString(),
        fingerprint: fingerprintForPublicKey(publicKey),
        createdAt,
    };
    mkdirSync(path.dirname(filePath), { recursive: true });
    writeJsonFileAtomic(filePath, identity);
    chmodSync(filePath, 0o600);
    return {
        public: {
            nodeId: identity.nodeId,
            name: identity.name,
            publicKey: identity.publicKey,
            fingerprint: identity.fingerprint,
            createdAt: identity.createdAt,
        },
        privateKey: identity.privateKey,
    };
}
export function exportPeerIdentityBackup(home = process.env.NORDRELAY_HOME || DEFAULT_HOME, name) {
    const identity = loadOrCreatePeerIdentity(home, name);
    const tls = existsSync(path.join(home, "tls", "peer.crt"))
        ? ensurePeerTlsFiles(home, identity.public)
        : undefined;
    return {
        version: 1,
        exportedAt: new Date().toISOString(),
        identity: identity.public,
        privateKey: identity.privateKey,
        tlsFingerprint: tls?.fingerprint,
    };
}
export function restorePeerIdentityBackup(backup, home = process.env.NORDRELAY_HOME || DEFAULT_HOME) {
    if (!backup || backup.version !== 1 || !backup.identity?.publicKey || !backup.privateKey) {
        throw new Error("Invalid peer identity backup.");
    }
    const fingerprint = fingerprintForPublicKey(backup.identity.publicKey);
    if (fingerprint !== backup.identity.fingerprint) {
        throw new Error("Peer identity backup fingerprint does not match the public key.");
    }
    const probe = `nordrelay-identity-restore:${Date.now()}`;
    const signature = signPeerPayload(backup.privateKey, probe);
    if (!verifyPeerPayload(backup.identity.publicKey, probe, signature)) {
        throw new Error("Peer identity backup private key does not match the public key.");
    }
    const payload = {
        nodeId: backup.identity.nodeId,
        name: backup.identity.name || defaultNodeName(),
        publicKey: backup.identity.publicKey,
        privateKey: backup.privateKey,
        fingerprint,
        createdAt: backup.identity.createdAt || new Date().toISOString(),
    };
    const filePath = path.join(home, "identity.json");
    mkdirSync(path.dirname(filePath), { recursive: true });
    writeJsonFileAtomic(filePath, payload);
    chmodSync(filePath, 0o600);
    return {
        public: {
            nodeId: payload.nodeId,
            name: payload.name,
            publicKey: payload.publicKey,
            fingerprint: payload.fingerprint,
            createdAt: payload.createdAt,
        },
        privateKey: payload.privateKey,
    };
}
export function ensurePeerTlsFiles(home = process.env.NORDRELAY_HOME || DEFAULT_HOME, identity) {
    const certDir = path.join(home, "tls");
    const certPath = path.join(certDir, "peer.crt");
    const keyPath = path.join(certDir, "peer.key");
    if (existsSync(certPath) && existsSync(keyPath)) {
        const cert = readFileSync(certPath, "utf8");
        const key = readFileSync(keyPath, "utf8");
        return { certPath, keyPath, cert, key, fingerprint: fingerprintForCertificate(cert), expiresAt: certificateExpiresAt(cert) };
    }
    mkdirSync(certDir, { recursive: true });
    const attrs = [{ name: "commonName", value: identity?.nodeId ?? "nordrelay-peer" }];
    const generated = selfsigned.generate(attrs, {
        algorithm: "sha256",
        days: 3650,
        keySize: 2048,
        extensions: [
            { name: "basicConstraints", cA: false },
            { name: "keyUsage", digitalSignature: true, keyEncipherment: true },
            { name: "extKeyUsage", serverAuth: true },
            {
                name: "subjectAltName",
                altNames: [
                    { type: 2, value: "localhost" },
                    { type: 7, ip: "127.0.0.1" },
                    { type: 7, ip: "::1" },
                ],
            },
        ],
    });
    writeTextFileAtomic(certPath, generated.cert);
    writeTextFileAtomic(keyPath, generated.private);
    chmodSync(certPath, 0o600);
    chmodSync(keyPath, 0o600);
    return {
        certPath,
        keyPath,
        cert: generated.cert,
        key: generated.private,
        fingerprint: fingerprintForCertificate(generated.cert),
        expiresAt: certificateExpiresAt(generated.cert),
    };
}
export function signPeerPayload(privateKey, payload) {
    return sign(null, Buffer.from(payload, "utf8"), privateKey).toString("base64url");
}
export function verifyPeerPayload(publicKey, payload, signature) {
    try {
        return verify(null, Buffer.from(payload, "utf8"), publicKey, Buffer.from(signature, "base64url"));
    }
    catch {
        return false;
    }
}
export function createPairingSignaturePayload(nodeId, timestamp, code) {
    return `${nodeId}\n${timestamp}\n${code}`;
}
export function createSharedSecret() {
    return randomBytes(32).toString("base64url");
}
export function fingerprintForPublicKey(publicKey) {
    return formatFingerprint(createHash("sha256").update(publicKey).digest("hex"));
}
export function fingerprintForCertificate(certPem) {
    const body = certPem
        .replace(/-----BEGIN CERTIFICATE-----/g, "")
        .replace(/-----END CERTIFICATE-----/g, "")
        .replace(/\s+/g, "");
    return formatFingerprint(createHash("sha256").update(Buffer.from(body, "base64")).digest("hex"));
}
function certificateExpiresAt(certPem) {
    try {
        return new Date(new X509Certificate(certPem).validTo).toISOString();
    }
    catch {
        return undefined;
    }
}
function createNodeId(publicKey) {
    return createHash("sha256").update(publicKey).digest("hex").slice(0, 16);
}
function defaultNodeName() {
    return `${os.hostname()} (${process.platform})`;
}
function formatFingerprint(hex) {
    return hex.match(/.{1,2}/g)?.join(":") ?? hex;
}
