import { AwsClient } from 'aws4fetch'
import {
  detectMimeFromMagicBytes,
  type DetectedImageMime,
} from '@platform-modules/uploads/magic-bytes'
import {
  parseImageDimensions,
  type ImageDimensions,
} from '@platform-modules/uploads/image-dimensions'
import {
  presignR2Put,
  type PresignedR2Put,
} from '@platform-modules/uploads/presign-s3compat'

export interface UploadGuardVerdict {
  accepted: boolean
}

export interface UploadPresignEnv {
  CF_ACCOUNT_ID?: string
  R2_ACCESS_KEY_ID?: string
  R2_SECRET_ACCESS_KEY?: string
  R2_BUCKET_NAME?: string
}

export interface PresignPutOptions {
  contentType: string
  expiresIn?: number
}

export interface PresignGetOptions {
  expiresIn?: number
  contentDisposition?: string
}

const TEXT_DECODER = new TextDecoder('utf-8', { fatal: false, ignoreBOM: true })

function headAsLowerText(bytes: Uint8Array, max = 512): string {
  return TEXT_DECODER.decode(bytes.slice(0, max)).trimStart().toLowerCase()
}

function isDangerousUploadText(bytes: Uint8Array): boolean {
  const head = headAsLowerText(bytes)
  return (
    head.startsWith('<svg') ||
    head.startsWith('<?xml') ||
    head.startsWith('<!doctype html') ||
    head.startsWith('<html') ||
    head.startsWith('<script')
  )
}

function normalizeMimeType(mimeType: string): string {
  return mimeType.split(';')[0]!.trim().toLowerCase()
}

function buildEndpoint(env: UploadPresignEnv): string {
  const accountId = env.CF_ACCOUNT_ID ?? ''
  const bucketName = env.R2_BUCKET_NAME ?? 'zync-storage'
  return `https://${accountId}.r2.cloudflarestorage.com/${bucketName}`
}

function createAwsClient(env: UploadPresignEnv): AwsClient {
  return new AwsClient({
    accessKeyId: env.R2_ACCESS_KEY_ID ?? '',
    secretAccessKey: env.R2_SECRET_ACCESS_KEY ?? '',
    region: 'auto',
    service: 's3',
  })
}

function joinEndpoint(endpoint: string, key: string): string {
  const base = endpoint.endsWith('/') ? endpoint.slice(0, -1) : endpoint
  const encodedKey = key
    .split('/')
    .map((segment) => encodeURIComponent(segment))
    .join('/')
  return `${base}/${encodedKey}`
}

function isAcceptedImageMime(mimeType: string, detectedMime: DetectedImageMime | null): boolean {
  return detectedMime !== null && detectedMime === mimeType
}

export function guardMagicBytes(mimeType: string, bytes: Uint8Array): UploadGuardVerdict {
  const normalizedMimeType = normalizeMimeType(mimeType)
  if (isDangerousUploadText(bytes)) {
    return { accepted: false }
  }
  if (!normalizedMimeType.startsWith('image/')) {
    return { accepted: true }
  }

  const detectedMime = detectMimeFromMagicBytes(bytes)
  return { accepted: isAcceptedImageMime(normalizedMimeType, detectedMime) }
}

export function imageDimensions(bytes: Uint8Array): ImageDimensions | null {
  return parseImageDimensions(bytes)
}

export async function createSignedPutUrl(
  env: UploadPresignEnv,
  key: string,
  options: PresignPutOptions,
): Promise<PresignedR2Put> {
  return presignR2Put(
    {
      endpoint: buildEndpoint(env),
      accessKeyId: env.R2_ACCESS_KEY_ID ?? '',
      secretAccessKey: env.R2_SECRET_ACCESS_KEY ?? '',
      region: 'auto',
    },
    key,
    options.contentType,
    options.expiresIn ?? 300,
  )
}

export async function createSignedGetUrl(
  env: UploadPresignEnv,
  key: string,
  options: PresignGetOptions = {},
): Promise<string> {
  const client = createAwsClient(env)
  const url = new URL(joinEndpoint(buildEndpoint(env), key))
  url.searchParams.set('X-Amz-Expires', String(options.expiresIn ?? 300))
  if (options.contentDisposition) {
    url.searchParams.set('response-content-disposition', options.contentDisposition)
  }

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

  return signed.url
}
