import 'server-only'

import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'

const APPROVED_BUCKET = 'heavy-haul-agent'
const APPROVED_REGION = 'us-east-1'

export const S3_PREFIX = {
  rateConfirmation: 'permits/rate_confirmations',
  permit: 'permits/orderSteptwo',
  tripSupporting: 'documents/trip_supporting',
  companyLogo: 'images/company_logos',
  pilotUnitPhoto: 'images/pilot_unit_photos',
} as const

export type S3Prefix = (typeof S3_PREFIX)[keyof typeof S3_PREFIX]

export interface S3UploadResult {
  bucket: string
  region: string
  key: string
  url: string
  contentType: string
}

let client: S3Client | null = null

function storageConfig() {
  const bucket = process.env.S3_BUCKET_NAME?.trim()
  const region = process.env.AWS_DEFAULT_REGION?.trim()
  const accessKeyId = process.env.AWS_ACCESS_KEY_ID?.trim()
  const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY?.trim()

  if (!bucket || !region || !accessKeyId || !secretAccessKey) {
    throw new Error(
      'AWS S3 is not configured. Set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION and S3_BUCKET_NAME.',
    )
  }
  // This application is intentionally locked to one bucket and region. A bad
  // deployment value must fail closed rather than writing to another account.
  if (bucket !== APPROVED_BUCKET || region !== APPROVED_REGION) {
    throw new Error(`S3 storage must use ${APPROVED_BUCKET} in ${APPROVED_REGION}.`)
  }
  return { bucket, region, accessKeyId, secretAccessKey }
}

function s3Client() {
  if (client) return client
  const config = storageConfig()
  client = new S3Client({
    region: config.region,
    credentials: {
      accessKeyId: config.accessKeyId,
      secretAccessKey: config.secretAccessKey,
      ...(process.env.AWS_SESSION_TOKEN?.trim()
        ? { sessionToken: process.env.AWS_SESSION_TOKEN.trim() }
        : {}),
    },
    maxAttempts: 3,
  })
  return client
}

const MIME_BY_EXTENSION: Record<string, string> = {
  pdf: 'application/pdf',
  png: 'image/png',
  jpg: 'image/jpeg',
  jpeg: 'image/jpeg',
  webp: 'image/webp',
  heic: 'image/heic',
  heif: 'image/heif',
  svg: 'image/svg+xml',
  docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
}

/** Keep the uploaded extension and use the browser MIME, with an extension fallback. */
export function contentTypeForUpload(fileName: string, suppliedType?: string | null): string {
  const cleanType = suppliedType?.split(';', 1)[0]?.trim().toLowerCase()
  if (cleanType && cleanType !== 'application/octet-stream') return cleanType
  const extension = fileName.split('.').pop()?.toLowerCase() ?? ''
  return MIME_BY_EXTENSION[extension] ?? 'application/octet-stream'
}

/** Detect the formats that this application accepts from their file signatures. */
export function detectedContentType(bytes: Uint8Array): string | null {
  if (bytes.length >= 5 && String.fromCharCode(...bytes.slice(0, 5)) === '%PDF-') {
    return 'application/pdf'
  }
  if (
    bytes.length >= 8 &&
    bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47 &&
    bytes[4] === 0x0d && bytes[5] === 0x0a && bytes[6] === 0x1a && bytes[7] === 0x0a
  ) return 'image/png'
  if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
    return 'image/jpeg'
  }
  if (
    bytes.length >= 12 &&
    String.fromCharCode(...bytes.slice(0, 4)) === 'RIFF' &&
    String.fromCharCode(...bytes.slice(8, 12)) === 'WEBP'
  ) return 'image/webp'
  if (bytes.length >= 12 && String.fromCharCode(...bytes.slice(4, 8)) === 'ftyp') {
    const brand = String.fromCharCode(...bytes.slice(8, 12)).toLowerCase()
    if (['heic', 'heix', 'hevc', 'hevx', 'mif1', 'msf1'].includes(brand)) return 'image/heic'
  }
  return null
}

function safeFileName(fileName: string): string {
  const base = fileName.split(/[\\/]/).pop()?.trim() || 'upload'
  return base.replace(/[^a-zA-Z0-9._-]/g, '_').replace(/^\.+/, '') || 'upload'
}

export function buildS3ObjectKey(prefix: S3Prefix, fileName: string): string {
  return `${prefix}/${crypto.randomUUID()}-${safeFileName(fileName)}`
}

export function canonicalS3Url(key: string): string {
  const { bucket, region } = storageConfig()
  const encodedKey = key.split('/').map(encodeURIComponent).join('/')
  return `https://${bucket}.s3.${region}.amazonaws.com/${encodedKey}`
}

export function isWorkspaceS3Key(value: string | null | undefined): boolean {
  if (!value) return false
  return Object.values(S3_PREFIX).some((prefix) => value.startsWith(`${prefix}/`))
}

export function s3KeyFromCanonicalUrl(value: string | null | undefined): string | null {
  if (!value) return null
  try {
    const url = new URL(value)
    if (url.protocol !== 'https:' || url.hostname !== `${APPROVED_BUCKET}.s3.${APPROVED_REGION}.amazonaws.com`) {
      return null
    }
    const key = url.pathname
      .split('/')
      .filter(Boolean)
      .map(decodeURIComponent)
      .join('/')
    return isWorkspaceS3Key(key) ? key : null
  } catch {
    return null
  }
}

/** The single upload implementation used by every persisted Workspace upload. */
export async function uploadFileToS3(file: Blob, fileName: string, prefix: S3Prefix) {
  const config = storageConfig()
  const key = buildS3ObjectKey(prefix, fileName)
  const bytes = new Uint8Array(await file.arrayBuffer())
  // Prefer a verified PDF/image signature. This prevents a PNG/JPG upload from
  // being stored as application/pdf when a browser supplies a bad MIME value.
  const contentType = detectedContentType(bytes) ?? contentTypeForUpload(fileName, file.type)
  await s3Client().send(
    new PutObjectCommand({
      Bucket: config.bucket,
      Key: key,
      Body: bytes,
      ContentType: contentType,
    }),
  )
  return {
    bucket: config.bucket,
    region: config.region,
    key,
    url: canonicalS3Url(key),
    contentType,
  } satisfies S3UploadResult
}

export async function getSignedS3Url(key: string, expiresIn = 60 * 30): Promise<string> {
  if (!isWorkspaceS3Key(key)) throw new Error('Refusing to sign an unknown S3 key.')
  const { bucket } = storageConfig()
  return getSignedUrl(
    s3Client(),
    new GetObjectCommand({ Bucket: bucket, Key: key }),
    { expiresIn },
  )
}

export async function getSignedS3UrlFromCanonical(
  url: string | null | undefined,
  expiresIn = 60 * 30,
): Promise<string | null> {
  const key = s3KeyFromCanonicalUrl(url)
  if (!key) return null
  try {
    return await getSignedS3Url(key, expiresIn)
  } catch {
    return null
  }
}

export async function deleteS3Object(key: string): Promise<void> {
  if (!isWorkspaceS3Key(key)) throw new Error('Refusing to delete an unknown S3 key.')
  const { bucket } = storageConfig()
  await s3Client().send(new DeleteObjectCommand({ Bucket: bucket, Key: key }))
}
