/**
 * Storage abstraction for S3/GCS
 */

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

export interface StorageConfig {
  provider: 's3' | 'gcs';
  bucket: string;
  region?: string;
  accessKeyId?: string;
  secretAccessKey?: string;
}

export interface StorageObject {
  key: string;
  content: Buffer | string;
  contentType?: string;
  metadata?: Record<string, string>;
}

export class StorageClient {
  protected s3Client: S3Client;
  protected bucket: string;

  constructor(protected config: StorageConfig) {
    this.bucket = config.bucket;
    this.s3Client = new S3Client({
      region: config.region || 'us-east-1',
      credentials:
        config.accessKeyId && config.secretAccessKey
          ? {
              accessKeyId: config.accessKeyId,
              secretAccessKey: config.secretAccessKey,
            }
          : undefined,
    });
  }

  async upload(object: StorageObject): Promise<string> {
    const command = new PutObjectCommand({
      Bucket: this.bucket,
      Key: object.key,
      Body: typeof object.content === 'string' ? Buffer.from(object.content) : object.content,
      ContentType: object.contentType,
      Metadata: object.metadata,
    });

    await this.s3Client.send(command);
    return object.key;
  }

  async download(key: string): Promise<Buffer> {
    const command = new GetObjectCommand({
      Bucket: this.bucket,
      Key: key,
    });

    const response = await this.s3Client.send(command);
    if (!response.Body) {
      throw new Error(`Object ${key} not found or empty`);
    }

    const chunks: Uint8Array[] = [];
    if (response.Body) {
      const body = response.Body as AsyncIterable<Uint8Array>;
      for await (const chunk of body) {
        chunks.push(chunk);
      }
    }
    return Buffer.concat(chunks);
  }

  async delete(key: string): Promise<void> {
    const command = new DeleteObjectCommand({
      Bucket: this.bucket,
      Key: key,
    });

    await this.s3Client.send(command);
  }

  async getPresignedUrl(key: string, expiresIn: number): Promise<string> {
    const command = new GetObjectCommand({
      Bucket: this.bucket,
      Key: key,
    });

    return getSignedUrl(this.s3Client, command, { expiresIn });
  }

  async objectExists(key: string): Promise<boolean> {
    try {
      const command = new HeadObjectCommand({
        Bucket: this.bucket,
        Key: key,
      });
      await this.s3Client.send(command);
      return true;
    } catch (error: unknown) {
      if (error && typeof error === 'object' && 'name' in error && error.name === 'NotFound') {
        return false;
      }
      throw error;
    }
  }
}

