<script setup lang="ts">
import { Buffer } from 'node:buffer';
import {
  parseCertificate, parseFingerprint,
  parseKey,
  parsePrivateKey,
  parseSignature,
} from 'sshpk';
import type {
  AlgorithmType,
  Certificate,
  CertificateFormat,
  Fingerprint,
  Key,
  PrivateKey, Signature, SignatureFormatType,
} from 'sshpk';
import { Base64 } from 'js-base64';
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';

function buf2Hex(buffer: ArrayBuffer) { // buffer is an ArrayBuffer
  return [...new Uint8Array(buffer)]
    .map(x => x.toString(16).padStart(2, '0'))
    .join('');
}

const inputKeyOrCertificate = ref('');
const passphrase = ref('');
const fileInput = ref() as Ref<Buffer>;

async function onUpload(file: File) {
  if (file) {
    fileInput.value = Buffer.from(await file.arrayBuffer());
    inputKeyOrCertificate.value = '';
  }
}

const certificateX509DER = ref('');
const { download: downloadX509DER } = useDownloadFileFromBase64(
  {
    source: certificateX509DER,
    extension: 'der',
  });

function downloadX509DERFile() {
  if (certificateX509DER.value === '') {
    return;
  }

  try {
    downloadX509DER();
  }
  catch (_) {
    //
  }
}

interface LabelValue {
  label: string
  value: string
  multiline?: boolean
}
const parsedSections = computed<LabelValue[]>(() => {
  try {
    certificateX509DER.value = '';
    const onErrorReturnErrorMessage = (func: () => any) => {
      try {
        return func();
      }
      catch (e: any) {
        return e.toString();
      }
    };
    const canParse = (value: string | Buffer, parseFunction: (value: string | Buffer) => any) => {
      try {
        return parseFunction(value);
      }
      catch {
        return null;
      }
    };
    const inputKeyOrCertificateValue
      = inputKeyOrCertificate.value !== ''
        ? inputKeyOrCertificate.value
        : fileInput.value;
    const publicKey = canParse(inputKeyOrCertificateValue, parseKey) as Key;
    if (publicKey) {
      return [
        {
          label: 'Type: ',
          value: 'Public Key',
        },
        {
          label: 'Key Type: ',
          value: publicKey.type,
        },
        {
          label: 'Size: ',
          value: publicKey.size,
        },
        {
          label: 'Comment: ',
          value: publicKey.comment,
          multiline: true,
        },
        {
          label: 'Curve: ',
          value: publicKey.curve ?? 'none',
        },
        {
          label: 'Fingerprint (sha256): ',
          value: onErrorReturnErrorMessage(() => publicKey.fingerprint('sha256')),
          multiline: true,
        },
        {
          label: 'Fingerprint (sha512): ',
          value: onErrorReturnErrorMessage(() => publicKey.fingerprint('sha512')),
          multiline: true,
        },
      ] as LabelValue[];
    }

    const privateKey = canParse(inputKeyOrCertificateValue,
      value => parsePrivateKey(value, 'auto', { passphrase: passphrase.value })) as PrivateKey;
    if (privateKey) {
      return [
        {
          label: 'Type: ',
          value: 'Private Key',
        },
        {
          label: 'Key Type: ',
          value: privateKey.type,
        },
        {
          label: 'Size: ',
          value: privateKey.size,
        },
        {
          label: 'Comment: ',
          value: privateKey.comment,
          multiline: true,
        },
        {
          label: 'Curve: ',
          value: privateKey.curve,
        },
        {
          label: 'Fingerprint (sha256): ',
          value: onErrorReturnErrorMessage(() => privateKey.fingerprint('sha256')),
          multiline: true,
        },
        {
          label: 'Fingerprint (sha512): ',
          value: onErrorReturnErrorMessage(() => privateKey.fingerprint('sha512')),
          multiline: true,
        },
      ] as LabelValue[];
    }

    const cert = canParse(inputKeyOrCertificateValue, (value) => {
      for (const format of ['openssh', 'pem', 'x509']) {
        try {
          return parseCertificate(value, format as CertificateFormat);
        }
        catch {}
      }
      return null;
    }) as Certificate;
    if (cert) {
      try {
        certificateX509DER.value = Base64.fromUint8Array(cert.toBuffer('x509'));
      }
      catch {}

      return [
        {
          label: 'Type: ',
          value: 'Certificate',
        },
        {
          label: 'Subjects: ',
          value: cert.subjects.map(s => s.toString()).join('\n'),
          multiline: true,
        },
        {
          label: 'Issuer: ',
          value: cert.issuer.toString(),
          multiline: true,
        },
        {
          label: 'Subject Key: ',
          value: onErrorReturnErrorMessage(() => cert.subjectKey?.toString('ssh')),
          multiline: true,
        },
        {
          label: 'Subject Key Type: ',
          value: cert.subjectKey?.type,
        },
        {
          label: 'Subject Size: ',
          value: cert.subjectKey?.size,
        },
        {
          label: 'Subject Comment: ',
          value: cert.subjectKey?.comment,
          multiline: true,
        },
        {
          label: 'Subject Curve: ',
          value: cert.subjectKey?.curve ?? 'none',
        },
        {
          label: 'Issuer Key: ',
          value: onErrorReturnErrorMessage(() => cert.issuerKey?.toString('ssh')),
          multiline: true,
        },
        {
          label: 'Serial: ',
          value: buf2Hex(cert.serial),
        },
        {
          label: 'Purposes: ',
          value: cert.purposes?.join(', '),
        },
        {
          label: 'Extensions: ',
          value: JSON.stringify(cert.getExtensions(), null, 2),
          multiline: true,
        },
        {
          label: 'Fingerprint (sha256): ',
          value: onErrorReturnErrorMessage(() => cert.fingerprint('sha256')),
          multiline: true,
        },
        {
          label: 'Fingerprint (sha512): ',
          value: onErrorReturnErrorMessage(() => cert.fingerprint('sha512')),
          multiline: true,
        },
        {
          label: 'Certificate (pem): ',
          value: onErrorReturnErrorMessage(() => cert.toString('pem')),
          multiline: true,
        },
      ] as LabelValue[];
    }

    const fingerprint = canParse(inputKeyOrCertificateValue, value => parseFingerprint(value.toString())) as Fingerprint;
    if (fingerprint) {
      return [
        {
          label: 'Type: ',
          value: 'Fingerprint',
        },
        {
          label: 'Fingerprint (hex): ',
          value: fingerprint.toString('hex'),
        },
        {
          label: 'Fingerprint (base64): ',
          value: fingerprint.toString('base64'),
        },
      ] as LabelValue[];
    }

    const signature = canParse(inputKeyOrCertificateValue, (value) => {
      //
      for (const algo of ['dsa', 'rsa', 'ecdsa', 'ed25519']) {
        for (const format of ['asn1', 'ssh', 'raw']) {
          try {
            return parseSignature(value, algo as AlgorithmType, format as SignatureFormatType);
          }
          catch {}
        }
      }
      return null;
    }) as Signature;
    if (signature) {
      return [
        {
          label: 'Type: ',
          value: 'Signature',
        },
        {
          label: 'Fingerprint (asn1): ',
          value: signature.toString('asn1'),
        },
        {
          label: 'Fingerprint (ssh): ',
          value: signature.toString('ssh'),
        },
      ] as LabelValue[];
    }

    return [
      {
        label: 'Type: ',
        value: 'Unknown format or invalid passphrase',
      }];
  }
  catch (e: any) {
    return [
      {
        label: 'Error: ',
        value: e.toString(),
      }] as LabelValue[];
  }
});
</script>

<template>
  <div>
    <c-card>
      <c-file-upload title="Drag and drop a Certificate file here, or click to select a Certificate file" @file-upload="onUpload" />
      <!-- separator -->
      <div my-4 w-full flex items-center justify-center op-70>
        <div class="h-1px max-w-100px flex-1 bg-gray-300 op-50" />
        <div class="mx-2 text-gray-400">
          or
        </div>
        <div class="h-1px max-w-100px flex-1 bg-gray-300 op-50" />
      </div>
      <c-input-text
        v-model:value="inputKeyOrCertificate"
        label="Paste your Public Key / Private Key / Signature / Fingerprint / Certificate:"
        placeholder="Your Public Key / Private Key / Signature / Fingerprint / Certificate..."
        multiline
        rows="8"
      />
    </c-card>

    <c-input-text
      v-model:value="passphrase"
      label="Passphrase (for encrypted keys):"
      placeholder="Passphrase (for encrypted keys)..."
      type="password"
    />

    <n-divider />

    <input-copyable
      v-for="{ label, value, multiline } of parsedSections"
      :key="label"
      :label="label"
      label-position="left"
      label-width="100px"
      label-align="right"

      autosize mb-2
      :multiline="multiline"
      :value="value"
      placeholder="Not Set"
    />

    <div v-if="certificateX509DER !== ''" flex justify-center>
      <c-button @click="downloadX509DERFile()">
        Download X509 DER certificate
      </c-button>
    </div>
  </div>
</template>
