
import {
    default as forge
} from "node-forge";

import {
    loadCertificate,
    loadPrivateKey,
    saveCertificate,
    savePrivateKey
} from "./pki.utils.mjs";

import {
    saveCertificate as saveCertificateToDatabase
} from "./db.certificates.mjs";

import {
    default as ValidateSchema
} from 'validate'

import {
    default as parseDuration
} from "parse-duration";

import {
    randomBytes
} from "crypto";
import { error } from "console";


const CertificateSchema = new ValidateSchema({
    certType: {
        type: String,
        required: true,
        enum: [
            'rootCA',
            'intermediateCA',
            'identity'
        ],
        message: {
            type: '[certType] must be a string.',
            required: '[certType] is required.',
            enum: '[certType] must be one of [authority, identity]'
        }
    },
    parent: {
        type: String,
        required: false,
        message: {
            type: '[parent] must be a string.'
        }
    },
    duration: {
        type: String,
        required: true,
        message: {
            type: '[duration] must be a string.',
            required: '[duration] is required.'
        }
    },
    commonName: {
        type: String,
        required: true,
        match: /^[a-zA-Z0-9\ \-\_\.]+$/,
        length: { min: 3, max: 32 },
        message: {
            type: '[commonName] must be a string.',
            required: '[commonName] is required.',
            match: '[commonName] is invalid.',
            length: '[commonName] is invalid.'
        }
    },
    emailAddress: {
        type: String,
        required: true,
        match: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/,
        message: {
            type: '[emailAddress] must be a string.',
            required: '[emailAddress] is required.',
            match: '[emailAddress] is invalid (e.g. admin@example.com).'
        }
    },
    countryName: {
        type: String,
        required: true,
        match: /^[A-Z]{2}$/,
        message: {
            type: '[countryName] must be a string.',
            required: '[countryName] is required.',
            match: '[countryName] needs to be 2 letter code (e.g. US).'
        }
    },
    stateOrProvinceName: {
        type: String,
        required: true,
        match: /^[A-Za-z ]{3,64}$/,
        message: {
            type: '[stateOrProvinceName] must be a string.',
            required: '[stateOrProvinceName] is required.',
            match: '[stateOrProvinceName] is invalid: (A-Z|a-z| ), 3-64 characters'
        }
    },
    localityName: {
        type: String,
        required: true,
        match: /^[A-Za-z ]{3,64}$/,
        message: {
            type: '[localityName] must be a string.',
            required: '[localityName] is required.',
            match: '[localityName] is invalid: (A-Z,a-z, ), 3-64 characters'
        }
    },
    organizationName: {
        type: String,
        required: true,
        match: /^[A-Za-z\ \-\_\.]{3,64}$/,
        message: {
            type: '[organizationName] must be a string.',
            required: '[organizationName] is required.',
            match: '[organizationName] is invalid: (A-Z,a-z, ), 3-64 characters'
        }
    },
    organizationalUnitName: {
        type: String,
        required: true,
        match: /^[A-Za-z\ \-\_\.]{3,64}$/,
        message: {
            type: '[organizationalUnitName] must be a string.',
            required: '[organizationalUnitName] is required.',
            match: '[organizationalUnitName] is invalid: (A-Z,a-z, ,.,-,_), 3-64 characters'
        }
    }
})


function generateCertificate(Params) {
    let configErrorList = CertificateSchema.validate(Params);

    configErrorList = configErrorList.map((configError) => {
        return configError.message;
    })

    if (configErrorList.length > 0) {
        return {
            status: "error",
            errors: configErrorList
        }
    }

    // generate certificate id
    const certificateId = randomBytes(4).toString("hex");

    // generate new keypair
    const {
        privateKey,
        publicKey
    } = forge.pki.rsa.generateKeyPair(4096);

    // initialize new certificate
    const certificate = forge.pki.createCertificate();
    certificate.publicKey = publicKey;
    // cert.serialNumber = '01';
    certificate.validity.notBefore = new Date(Date.now());
    certificate.validity.notAfter = new Date(Date.now() + parseDuration(Params.duration));

    const attrs = [{
        name: 'commonName',
        value: Params.commonName
    }, {
        name: 'countryName',
        value: Params.countryName
    }, {
        name: 'stateOrProvinceName',
        value: Params.stateOrProvinceName
    }, {
        name: 'localityName',
        value: Params.localityName
    }, {
        name: 'organizationName',
        value: Params.organizationName
    }, {
        name: 'organizationalUnitName',
        value: Params.organizationalUnitName
    }];

    certificate.setSubject(attrs);
    certificate.setIssuer(attrs);

    // Erweiterungen hinzufügen
    certificate.setExtensions([{
        name: 'basicConstraints',
        cA: true
    }, {
        name: 'keyUsage',
        keyCertSign: true,
        cRLSign: true
    }, {
        name: 'subjectKeyIdentifier'
    }]);

    console.log(`new certificate with uid ${certificateId} generated`);

    return {
        status: "ok",
        certificateId,
        certificate,
        privateKey,
        publicKey
    }
}

export async function createRootCertificate(certificateParameters) {
    let oGeneratedData = generateCertificate({
        certType: 'rootCA',
        parent: null,
        duration: certificateParameters.duration,
        emailAddress: certificateParameters.emailAddress,
        commonName: certificateParameters.commonName,
        countryName: certificateParameters.countryName,
        stateOrProvinceName: certificateParameters.stateOrProvinceName,
        localityName: certificateParameters.localityName,
        organizationName: certificateParameters.organizationName,
        organizationalUnitName: certificateParameters.organizationalUnitName
    });

    if (oGeneratedData.status != "ok") {
        return {
            status: "error",
            errors: oGeneratedData.errors
        }
    }

    // sign the new certificate
    oGeneratedData.certificate.sign(oGeneratedData.privateKey, forge.md.sha256.create());

    // save certificate and keys
    saveCertificate(oGeneratedData.certificate, oGeneratedData.certificateId, 'rootCA');
    savePrivateKey(oGeneratedData.privateKey, oGeneratedData.certificateId, 'rootCA');
    saveCertificateToDatabase(oGeneratedData.certificateId, {
        displayName: certificateParameters.commonName,
        certificateType: 'rootCA'
    })

    console.log(`new rootCA certificate with uid ${oGeneratedData.certificateId} saved`);

    return {
        status: "ok",
        certificateId: oGeneratedData.certificateId
    }
}

export async function createIntermediateCertificate(oCertificateParameters, sRootCertificateId) {
    let {
        certificateId: newCertificateId,
        certificate: newCertificate,
        privateKey: newPrivateKey
    } = generateCertificate(oCertificateParameters);

    // load certificate authority certificate and privateKey
    const rootCACertificate = loadCertificate(sRootCertificateId);
    const rootCAPrivateKey = loadPrivateKey(sRootCertificateId);

    // set rootCA as issuer
    newCertificate.setIssuer(rootCACertificate.subject.attributes);
    // sign certificate with root certificate
    newCertificate.sign(rootCAPrivateKey, forge.md.sha256.create());

    // save certificate and keys
    saveCertificate(newCertificate, newCertificateId, 'intermediateCA');
    savePrivateKey(newPrivateKey, newCertificateId, 'intermediateCA');
    saveCertificateToDatabase(newCertificateId, {
        displayName: Params.commonName,
        certificateType: 'intermediateCA'
    })

    console.log(`new intermediateCA certificate with uid ${newCertificateId} saved`);
}

export async function createIdentityCertificate(Params) {
    let {
        certificateId,
        certificate,
        privateKey,
        publicKey
    } = generateCertificate(Params)
}

// Funktion aufrufen, um das Root-CA-Zertifikat zu generieren
console.log((await createRootCertificate({
    duration: '10y',
    emailAddress: 'kai@waggeling.net',
    commonName: 'waggeling.net rootCA',
    countryName: 'DE',
    stateOrProvinceName: 'Sachsen',
    localityName: 'Leipzig',
    organizationName: 'WCloud',
    organizationalUnitName: 'TEst'
})));