/**
 * AWS Signature V4 — internal helpers for S3-compatible storage providers.
 * Not exported from package.json; only imported by provider implementations.
 */

import { createHmac, createHash } from 'crypto';

// ─── Crypto primitives ───────────────────────────────────────────────────────

export function sha256hex(data) {
  return createHash('sha256').update(data).digest('hex');
}

/**
 * HMAC-SHA256. Pass encoding='hex' for a hex string; omit for a Buffer.
 */
export function hmac(key, data, encoding) {
  return createHmac('sha256', Buffer.isBuffer(key) ? key : Buffer.from(key))
    .update(data)
    .digest(encoding);
}

export function amzDate(date) {
  return date.toISOString().replace(/[:\-]|\.\d{3}/g, '');
}

export function dateStamp(date) {
  return date.toISOString().slice(0, 10).replace(/-/g, '');
}

// ─── URI encoding ────────────────────────────────────────────────────────────

/**
 * Encode a string per AWS Signature V4 spec (encodes everything except A-Z a-z 0-9 - _ . ~)
 */
export function encodeS3(str) {
  return encodeURIComponent(str)
    .replace(/!/g, '%21')
    .replace(/'/g, '%27')
    .replace(/\(/g, '%28')
    .replace(/\)/g, '%29')
    .replace(/\*/g, '%2A');
}

/**
 * Encode a URI path, encoding each segment individually (preserving slashes)
 */
export function encodePath(path) {
  return path.split('/').map(seg => (seg ? encodeS3(seg) : '')).join('/');
}

// ─── Signing key ─────────────────────────────────────────────────────────────

function signingKey(secret, ds, region, service) {
  const kDate    = hmac('AWS4' + secret, ds);
  const kRegion  = hmac(kDate, region);
  const kService = hmac(kRegion, service);
  return hmac(kService, 'aws4_request');
}

// ─── Request signing ─────────────────────────────────────────────────────────

/**
 * Sign an S3 request using AWS Signature V4.
 * Returns the full URL and the headers object to pass to fetch.
 */
export function signRequest({ method, host, path, query = {}, extraHeaders = {}, bodyBuffer, config, date }) {
  const { accessKeyId, secretAccessKey, region } = config;
  const service = 's3';

  const ts       = amzDate(date);
  const ds       = dateStamp(date);
  const bodyHash = sha256hex(bodyBuffer ?? Buffer.alloc(0));

  const headers = { host, 'x-amz-date': ts, 'x-amz-content-sha256': bodyHash, ...extraHeaders };

  const sortedKeys        = Object.keys(headers).sort();
  const canonicalHeaders  = sortedKeys.map(k => `${k}:${headers[k]}\n`).join('');
  const signedHeaders     = sortedKeys.join(';');

  const canonicalQueryString = Object.keys(query)
    .sort()
    .map(k => `${encodeS3(k)}=${encodeS3(query[k])}`)
    .join('&');

  const canonicalRequest = [method, encodePath(path), canonicalQueryString, canonicalHeaders, signedHeaders, bodyHash].join('\n');

  const scope       = `${ds}/${region}/${service}/aws4_request`;
  const stringToSign = ['AWS4-HMAC-SHA256', ts, scope, sha256hex(canonicalRequest)].join('\n');
  const sig         = hmac(signingKey(secretAccessKey, ds, region, service), stringToSign, 'hex');
  const auth        = `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${sig}`;

  const requestHeaders = { ...headers, Authorization: auth };
  delete requestHeaders.host;

  const url = canonicalQueryString
    ? `https://${host}${path}?${canonicalQueryString}`
    : `https://${host}${path}`;

  return { url, headers: requestHeaders };
}

/**
 * Build a presigned URL (signature embedded in query string, no Authorization header).
 * The payload is marked UNSIGNED-PAYLOAD so no body hash is needed at signing time.
 */
export function buildPresignedUrl({ method, host, path, expiresIn, config, date }) {
  const { accessKeyId, secretAccessKey, region } = config;
  const service = 's3';

  const ts    = amzDate(date);
  const ds    = dateStamp(date);
  const scope = `${ds}/${region}/${service}/aws4_request`;

  const query = {
    'X-Amz-Algorithm':     'AWS4-HMAC-SHA256',
    'X-Amz-Credential':    `${accessKeyId}/${scope}`,
    'X-Amz-Date':          ts,
    'X-Amz-Expires':       String(expiresIn),
    'X-Amz-SignedHeaders': 'host',
  };

  const canonicalQueryString = Object.keys(query)
    .sort()
    .map(k => `${encodeS3(k)}=${encodeS3(query[k])}`)
    .join('&');

  const canonicalRequest = [method, encodePath(path), canonicalQueryString, `host:${host}\n`, 'host', 'UNSIGNED-PAYLOAD'].join('\n');
  const stringToSign     = ['AWS4-HMAC-SHA256', ts, scope, sha256hex(canonicalRequest)].join('\n');
  const sig              = hmac(signingKey(secretAccessKey, ds, region, service), stringToSign, 'hex');

  return `https://${host}${path}?${canonicalQueryString}&X-Amz-Signature=${sig}`;
}

// ─── XML helpers ─────────────────────────────────────────────────────────────

export function xmlFirst(xml, tag) {
  const m = xml.match(new RegExp(`<${tag}[^>]*>(.*?)</${tag}>`, 's'));
  return m ? m[1] : null;
}

export function xmlAll(xml, tag) {
  const re = new RegExp(`<${tag}[^>]*>(.*?)</${tag}>`, 'gs');
  const results = [];
  let m;
  while ((m = re.exec(xml)) !== null) results.push(m[1]);
  return results;
}

// ─── Body normalizer ─────────────────────────────────────────────────────────

export async function toBuffer(body) {
  if (Buffer.isBuffer(body))     return body;
  if (body instanceof Uint8Array) return Buffer.from(body);
  if (typeof body === 'string')  return Buffer.from(body, 'utf8');
  if (body instanceof Blob)      return Buffer.from(await body.arrayBuffer());
  return Buffer.from(body);
}

// ─── Security helpers ─────────────────────────────────────────────────────────

/**
 * Strip HTTP header injection characters (\r, \n, \0) from a header value.
 */
export function sanitizeHeaderValue(value) {
  return String(value).replace(/[\r\n\0]/g, '');
}

/**
 * Escape XML special characters to prevent injection into the DeleteObjects payload.
 */
export function escapeXml(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}

// ─── Metadata helpers ─────────────────────────────────────────────────────────

export function metaToHeaders(metadata) {
  return Object.fromEntries(
    Object.entries(metadata).map(([k, v]) => [
      `x-amz-meta-${sanitizeHeaderValue(k).toLowerCase()}`,
      sanitizeHeaderValue(v),
    ])
  );
}

export function headersToMeta(headers) {
  return Object.fromEntries(
    [...headers.entries()]
      .filter(([k]) => k.startsWith('x-amz-meta-'))
      .map(([k, v]) => [k.replace('x-amz-meta-', ''), v])
  );
}
