/**
 * Shared OID extraction helpers for Mosaic federation certificates.
 *
 * Custom OID registry (PRD §6, docs/federation/SETUP.md):
 *   1.3.6.1.4.1.99999.1  — mosaic_grant_id
 *   1.3.6.1.4.1.99999.2  — mosaic_subject_user_id
 *
 * The encoding convention: each extension value is an OCTET STRING wrapping
 * an ASN.1 UTF8String TLV:
 *   0x0C (tag) + 1-byte length + UTF-8 bytes
 *
 * CaService encodes values this way via encodeUtf8String(), and this module
 * decodes them with the corresponding `.slice(2)` to skip tag + length byte.
 *
 * This module is intentionally pure — no NestJS, no DB, no network I/O.
 */

import { X509Certificate } from '@peculiar/x509';

// ---------------------------------------------------------------------------
// OID constants
// ---------------------------------------------------------------------------

export const OID_MOSAIC_GRANT_ID = '1.3.6.1.4.1.99999.1';
export const OID_MOSAIC_SUBJECT_USER_ID = '1.3.6.1.4.1.99999.2';

// ---------------------------------------------------------------------------
// Extraction result types
// ---------------------------------------------------------------------------

export interface MosaicOids {
  grantId: string;
  subjectUserId: string;
}

export type OidExtractionResult =
  | { ok: true; value: MosaicOids }
  | {
      ok: false;
      error: 'MISSING_GRANT_ID' | 'MISSING_SUBJECT_USER_ID' | 'PARSE_ERROR';
      detail?: string;
    };

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const decoder = new TextDecoder();

/**
 * Decode an extension value encoded as ASN.1 UTF8String TLV
 * (tag 0x0C + 1-byte length + UTF-8 bytes).
 * Validates tag, length byte, and buffer bounds before decoding.
 * Throws a descriptive Error on malformed input; caller wraps in try/catch.
 */
function decodeUtf8StringTlv(value: ArrayBuffer): string {
  const bytes = new Uint8Array(value);

  // Need at least tag + length bytes
  if (bytes.length < 2) {
    throw new Error(`UTF8String TLV too short: expected at least 2 bytes, got ${bytes.length}`);
  }

  // Tag byte must be 0x0C (ASN.1 UTF8String)
  if (bytes[0] !== 0x0c) {
    throw new Error(
      `UTF8String TLV tag mismatch: expected 0x0C, got 0x${bytes[0]!.toString(16).toUpperCase()}`,
    );
  }

  // Only single-byte length form is supported (values 0–127); long form not needed
  // for OID strings of this length.
  const declaredLength = bytes[1]!;
  if (declaredLength > 127) {
    throw new Error(
      `UTF8String TLV uses long-form length (0x${declaredLength.toString(16).toUpperCase()}), which is not supported`,
    );
  }

  // Declared length must match actual remaining bytes
  if (declaredLength !== bytes.length - 2) {
    throw new Error(
      `UTF8String TLV length mismatch: declared ${declaredLength}, actual ${bytes.length - 2}`,
    );
  }

  // Skip: tag (1 byte) + length (1 byte)
  return decoder.decode(bytes.slice(2));
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

/**
 * Extract Mosaic custom OIDs (grantId, subjectUserId) from an X.509 certificate
 * already parsed via @peculiar/x509.
 *
 * Returns `{ ok: true, value: MosaicOids }` on success, or
 * `{ ok: false, error: <code>, detail? }` on any failure — never throws.
 */
export function extractMosaicOids(cert: X509Certificate): OidExtractionResult {
  try {
    const grantIdExt = cert.getExtension(OID_MOSAIC_GRANT_ID);
    if (!grantIdExt) {
      return { ok: false, error: 'MISSING_GRANT_ID' };
    }

    const subjectUserIdExt = cert.getExtension(OID_MOSAIC_SUBJECT_USER_ID);
    if (!subjectUserIdExt) {
      return { ok: false, error: 'MISSING_SUBJECT_USER_ID' };
    }

    const grantId = decodeUtf8StringTlv(grantIdExt.value);
    const subjectUserId = decodeUtf8StringTlv(subjectUserIdExt.value);

    return {
      ok: true,
      value: { grantId, subjectUserId },
    };
  } catch (err) {
    return {
      ok: false,
      error: 'PARSE_ERROR',
      detail: err instanceof Error ? err.message : String(err),
    };
  }
}

/**
 * Parse a PEM-encoded certificate and extract Mosaic OIDs.
 * Returns an OidExtractionResult — never throws.
 */
export function extractMosaicOidsFromPem(certPem: string): OidExtractionResult {
  let cert: X509Certificate;
  try {
    cert = new X509Certificate(certPem);
  } catch (err) {
    return {
      ok: false,
      error: 'PARSE_ERROR',
      detail: err instanceof Error ? err.message : String(err),
    };
  }
  return extractMosaicOids(cert);
}
