/**
 * KB R2 storage utilities — sign & upload helpers for kb-module.
 *
 * - kbR2Key: deterministic R2 key for a KB attachment
 * - putKbObject: write bytes to R2 binding STORAGE
 * - signKbUrl: generate a 60-min presigned GET URL (S3-compat via aws4fetch)
 * - assertAllowedFileType: guard — rejects non-allowlisted MIME types incl. SVG
 *
 * R2 keys are NEVER returned to clients. Clients receive signed URLs only.
 */
import { AwsClient } from 'aws4fetch'
import { KB_ALLOWED_FILE_TYPES } from '@zync/types'
import type { Env } from '@zync/types'

// ── Constants ────────────────────────────────────────────────────────────────

const MAX_TTL_SECONDS = 3600 // 60 minutes

// ── Key helper ───────────────────────────────────────────────────────────────

/**
 * Deterministic R2 object key.
 * Format: kb/{tenantId}/{articleId}/{attachmentId}/{safeFilename}
 */
export function kbR2Key(
  tenantId: string,
  articleId: string,
  attachmentId: string,
  filename: string,
): string {
  // Sanitize filename: strip path separators and limit length
  const safeFilename = filename.replace(/[/\\]/g, '_').slice(0, 200)
  return `kb/${tenantId}/${articleId}/${attachmentId}/${safeFilename}`
}

// ── Put object ───────────────────────────────────────────────────────────────

/**
 * Write an object to R2 binding STORAGE.
 */
export async function putKbObject(
  env: Env,
  key: string,
  body: ReadableStream | ArrayBuffer,
  contentType: string,
): Promise<void> {
  await env.STORAGE.put(key, body, {
    httpMetadata: { contentType },
  })
}

// ── Presigned URL ─────────────────────────────────────────────────────────────

/**
 * Generate a presigned GET URL for an R2 object.
 *
 * Uses the R2 S3-compatible API via aws4fetch. Requires CF_ACCOUNT_ID and
 * R2 access key/secret stored in env. Since Cloudflare Workers R2 bindings
 * don't natively expose createPresignedUrl in production Workers runtime (as
 * of the spec date), we use the S3-compat API presign pattern.
 *
 * For environments where env.STORAGE.createPresignedUrl IS available
 * (future CF runtime / testing), this can be simplified.
 */
export async function signKbUrl(
  env: Env,
  key: string,
  ttlSeconds: number = MAX_TTL_SECONDS,
): Promise<string> {
  // Use R2 binding's createPresignedUrl if available (Cloudflare Workers runtime)
  // Fallback: construct via aws4fetch S3 compat
  const storage = env.STORAGE as R2Bucket & {
    createPresignedUrl?: (
      method: string,
      key: string,
      options: { expiresIn: number },
    ) => Promise<string>
  }

  if (typeof storage.createPresignedUrl === 'function') {
    return storage.createPresignedUrl('GET', key, { expiresIn: ttlSeconds })
  }

  // S3-compat presign via aws4fetch
  // R2 S3 endpoint: https://<accountId>.r2.cloudflarestorage.com/<bucket>/<key>
  // The CF_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY are expected in env
  // Cast env to access optional S3 compat secrets
  const envAny = env as unknown as Record<string, string>
  const accountId = envAny['CF_ACCOUNT_ID'] ?? ''
  const accessKeyId = envAny['R2_ACCESS_KEY_ID'] ?? ''
  const secretAccessKey = envAny['R2_SECRET_ACCESS_KEY'] ?? ''
  const bucketName = envAny['R2_BUCKET_NAME'] ?? 'zync-storage'

  const endpoint = `https://${accountId}.r2.cloudflarestorage.com`

  const aws = new AwsClient({
    accessKeyId,
    secretAccessKey,
    region: 'auto',
    service: 's3',
  })

  const url = new URL(`${endpoint}/${bucketName}/${key}`)
  url.searchParams.set('X-Amz-Expires', String(ttlSeconds))

  const presigned = await aws.sign(
    new Request(url.toString(), { method: 'GET' }),
    { aws: { signQuery: true } },
  )

  return presigned.url
}

// ── File-type guard ───────────────────────────────────────────────────────────

/**
 * Asserts that a MIME type is in the KB allowlist.
 * Throws a plain Error with code 415 on rejection.
 * SVG is explicitly rejected (XSS risk same-origin).
 */
export function assertAllowedFileType(mime: string): void {
  if (mime === 'image/svg+xml') {
    const err = new Error('SVG uploads are not permitted (XSS risk)')
    ;(err as unknown as Record<string, unknown>)['status'] = 415
    throw err
  }
  if (!(KB_ALLOWED_FILE_TYPES as readonly string[]).includes(mime)) {
    const err = new Error(`File type not permitted: ${mime}`)
    ;(err as unknown as Record<string, unknown>)['status'] = 415
    throw err
  }
}
