/**
 * S3Adapter — signed (SigV4) object access via Bun's native S3 client.
 *
 * Works against any S3-compatible endpoint — MinIO (path-style), DO Spaces,
 * AWS S3, Cloudflare R2 — selected by endpoint + path-style flag. Every request
 * is signed; there is no anonymous code path.
 */

import { S3Client } from 'bun';

import { StorageError, type Body, type ObjectStore, type S3StoreOptions } from './index';

export class S3Adapter implements ObjectStore {
  readonly bucket: string;
  private readonly client: S3Client;
  private readonly presignTtlSeconds: number;

  constructor(opts: S3StoreOptions, presignTtlSeconds: number) {
    this.bucket = opts.bucket;
    this.presignTtlSeconds = presignTtlSeconds;
    this.client = new S3Client({
      accessKeyId: opts.accessKey,
      secretAccessKey: opts.secretKey,
      region: opts.region,
      endpoint: opts.endpoint,
      bucket: opts.bucket,
      virtualHostedStyle: !opts.forcePathStyle,
    });
  }

  async put(key: string, contentType: string, body: Body): Promise<void> {
    try {
      const data = body instanceof ReadableStream ? await streamToBuffer(body) : body;
      await this.client.write(key, data, { type: contentType });
    } catch (err) {
      throw new StorageError(`S3 put failed for key ${key}`, key, err);
    }
  }

  async get(key: string): Promise<Buffer> {
    try {
      const arr = await this.client.file(key).arrayBuffer();
      return Buffer.from(arr);
    } catch (err) {
      throw new StorageError(`S3 get failed for key ${key}`, key, err);
    }
  }

  async head(key: string): Promise<boolean> {
    try {
      return await this.client.exists(key);
    } catch (err) {
      throw new StorageError(`S3 head failed for key ${key}`, key, err);
    }
  }

  async presignGet(key: string, ttlSeconds: number = this.presignTtlSeconds): Promise<string> {
    return this.client.presign(key, { method: 'GET', expiresIn: ttlSeconds });
  }
}

async function streamToBuffer(stream: ReadableStream): Promise<Buffer> {
  const arr = new Uint8Array(await new Response(stream).arrayBuffer());
  return Buffer.from(arr);
}
