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

// S3 storage for images (organization logos, profile photos); project documents stay on
// local disk. The database keeps the object *key* (e.g. "orgs/3/logo/<uuid>.png");
// clients get a URL for it at read time: a signed URL on the private bucket, or a plain
// URL when AWS_S3_PUBLIC_BASE_URL (CloudFront / public bucket) is set.

export class StorageNotConfiguredError extends Error {
  constructor() {
    super('File storage is not configured');
  }
}

export const isStorageConfigured = () =>
  Boolean(env.AWS_REGION && env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY && env.AWS_S3_BUCKET);

let client: S3Client | undefined;
function s3() {
  if (!isStorageConfigured()) throw new StorageNotConfiguredError();
  client ??= new S3Client({
    region: env.AWS_REGION,
    credentials: { accessKeyId: env.AWS_ACCESS_KEY_ID, secretAccessKey: env.AWS_SECRET_ACCESS_KEY },
  });
  return client;
}

/** Exposed so tests can stub `send` without reaching AWS. */
export const storageClient = () => s3();

export async function putFile(key: string, body: Buffer, contentType: string, cacheControl?: string) {
  await s3().send(new PutObjectCommand({
    Bucket: env.AWS_S3_BUCKET,
    Key: key,
    Body: body,
    ContentType: contentType,
    ...(cacheControl ? { CacheControl: cacheControl } : {}),
  }));
  return key;
}

export async function deleteFile(key: string) {
  await s3().send(new DeleteObjectCommand({ Bucket: env.AWS_S3_BUCKET, Key: key }));
}

/** A URL a browser can load. Full URLs stored before S3 (e.g. a pasted logo link) pass through. */
export async function fileUrl(keyOrUrl: string | null | undefined): Promise<string | undefined> {
  if (!keyOrUrl) return undefined;
  if (/^https?:\/\//i.test(keyOrUrl)) return keyOrUrl;
  if (env.AWS_S3_PUBLIC_BASE_URL) return `${env.AWS_S3_PUBLIC_BASE_URL.replace(/\/$/, '')}/${keyOrUrl}`;
  if (!isStorageConfigured()) return undefined;
  // Sign with a time rounded down to half the TTL: the URL stays the same for a while (so
  // browsers cache the image and the apps' session refresh doesn't re-download it) and is
  // still valid for at least half the TTL.
  const window = (env.AWS_S3_SIGNED_URL_TTL * 1000) / 2;
  return getSignedUrl(s3(), new GetObjectCommand({ Bucket: env.AWS_S3_BUCKET, Key: keyOrUrl }), {
    expiresIn: env.AWS_S3_SIGNED_URL_TTL,
    signingDate: new Date(Math.floor(Date.now() / window) * window),
  });
}

/** Whether a stored value is an S3 key we own (and may delete), not an older pasted URL. */
export const isStorageKey = (value: string | null | undefined): value is string =>
  Boolean(value && !/^https?:\/\//i.test(value) && value.includes('/'));
