import { realpathSync } from 'node:fs';
import {createHash, createPrivateKey, sign} from 'node:crypto';
import {readFile} from 'node:fs/promises';
import {pathToFileURL} from 'node:url';

export function signedHeaders(apiKey, privateKeyPem, method, path, body = Buffer.alloc(0)) {
  const key = createPrivateKey(privateKeyPem);
  if (key.asymmetricKeyType !== 'ed25519') throw new Error('An Ed25519 private key is required');
  const timestamp = Date.now();
  const payload = Buffer.from(JSON.stringify({
    api_key: apiKey, idempotency_key: '', timestamp, sign_version: 1,
    method, path, body_hash: createHash('sha256').update(body).digest('base64url'),
  }));
  return {
    'EXN-API-KEY': apiKey, 'EXN-TIMESTAMP': String(timestamp), 'EXN-SIGN-VERSION': '1',
    'EXN-DATA': payload.toString('base64url'),
    'EXN-SIGN': sign(null, payload, key).toString('base64url'),
  };
}

export function parseError(status, body, retryAfter) {
  if (body && typeof body.code === 'string' && typeof body.message === 'string') {
    return {status, envelope: 'partnership', code: body.code,
      diagnostic: body.message, details: body.details, retryAfter};
  }
  if (body && typeof body.code === 'number' && typeof body.error_message === 'string') {
    return {status, envelope: 'request-processing', code: body.code,
      name: body.error_message, diagnostic: body.description,
      retryAfter, retryAfterTimestamp: body.retry_after};
  }
  return {status, envelope: 'unrecognized', retryAfter};
}

export async function getDefaultLink({baseUrl, apiKey, privateKeyPem, signal, transport = fetch}) {
  let base;
  try { base = new URL(baseUrl); } catch {
    throw new Error('Set EXN_API_BASE_URL to your Partnership API HTTPS origin');
  }
  if (base.protocol !== 'https:' || base.username || base.password ||
      base.pathname !== '/' || base.search || base.hash || base.hostname.endsWith('.invalid')) {
    throw new Error('EXN_API_BASE_URL must be your Partnership API HTTPS origin without credentials, a path, query or fragment');
  }
  const path = '/v1/partnership/partner/default-link';
  let response;
  try {
    response = await transport(base.origin + path, {
      method: 'GET', headers: signedHeaders(apiKey, privateKeyPem, 'GET', path),
      signal, redirect: 'error',
    });
  } catch (cause) {
    throw new Error('No response received; no automatic retry was sent.', {cause});
  }
  const text = await response.text();
  let body;
  try { body = JSON.parse(text); } catch {
    throw new Error(`HTTP ${response.status}: response was not JSON; no automatic retry was sent.`);
  }
  if (!response.ok) {
    const error = new Error(`Request failed with HTTP ${response.status}`);
    error.apiError = parseError(response.status, body, response.headers.get('Retry-After'));
    throw error;
  }
  if (response.status !== 200 || !body ||
      !['full_default_link', 'domain', 'code', 'path', 'url'].every(k => typeof body[k] === 'string')) {
    throw new Error('Unexpected success response shape');
  }
  return body;
}

async function main() {
  const baseUrl = process.env.EXN_API_BASE_URL;
  const apiKey = process.env.EXN_API_KEY;
  const keyFile = process.env.EXN_PRIVATE_KEY_FILE;
  const timeoutMs = Number(process.env.EXN_REQUEST_TIMEOUT_MS);
  if (!baseUrl || !apiKey || !keyFile || !Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
    throw new Error('Set EXN_API_BASE_URL, EXN_API_KEY, EXN_PRIVATE_KEY_FILE and a positive EXN_REQUEST_TIMEOUT_MS');
  }
  const privateKeyPem = await readFile(keyFile, 'utf8');
  const result = await getDefaultLink({baseUrl, apiKey, privateKeyPem, signal: AbortSignal.timeout(timeoutMs)});
  console.log(JSON.stringify(result, null, 2));
}

if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
  main().catch(error => {
    console.error(error.apiError ? JSON.stringify(error.apiError, null, 2) : error.message);
    process.exitCode = 1;
  });
}
