// Media storage abstraction with two implementations:
// - local: writes to the ./uploads directory and serves via /uploads/* (the
//   original behavior, and the zero-config default)
// - s3: stores objects in an S3-compatible bucket (e.g. Garage), so uploads are
//   shared across instances instead of living on one container's local disk
//
// S3 is enabled only when S3_ENDPOINT and S3_BUCKET are set. With it unset the
// app behaves exactly as before. A "key" is the object name (e.g. "abc123.jpg").

import { writeFile, mkdir, unlink } from 'fs/promises';
import { existsSync } from 'fs';
import { join } from 'path';

const UPLOAD_DIR = './uploads';

export interface Storage {
  readonly backend: 'local' | 's3';
  put(key: string, buffer: Buffer, contentType: string): Promise<void>;
  delete(key: string): Promise<void>;
  // Public URL to persist as the media record's fileUrl.
  publicUrl(key: string): string;
}

/** Whether S3-compatible storage is configured. */
export function isS3Enabled(): boolean {
  return !!(process.env.S3_ENDPOINT && process.env.S3_BUCKET);
}

/** Extract the storage key (object name) from a stored fileUrl. */
export function keyFromUrl(fileUrl: string): string {
  return fileUrl.split('/').pop() || fileUrl;
}

// ==================== Local implementation ====================

class LocalStorage implements Storage {
  readonly backend = 'local' as const;

  private async ensureDir(): Promise<void> {
    if (!existsSync(UPLOAD_DIR)) {
      await mkdir(UPLOAD_DIR, { recursive: true });
    }
  }

  async put(key: string, buffer: Buffer): Promise<void> {
    await this.ensureDir();
    await writeFile(join(UPLOAD_DIR, key), buffer);
  }

  async delete(key: string): Promise<void> {
    const filepath = join(UPLOAD_DIR, key);
    if (existsSync(filepath)) {
      await unlink(filepath);
    }
  }

  publicUrl(key: string): string {
    return `/uploads/${key}`;
  }
}

// ==================== S3 implementation ====================

// Imported lazily so the AWS SDK is only loaded when S3 is actually configured.
type S3ClientType = import('@aws-sdk/client-s3').S3Client;

class S3Storage implements Storage {
  readonly backend = 's3' as const;
  private client: S3ClientType | null = null;
  private bucket = process.env.S3_BUCKET as string;

  private async getClient(): Promise<S3ClientType> {
    if (this.client) return this.client;
    const { S3Client } = await import('@aws-sdk/client-s3');
    const forcePathStyle = (process.env.S3_FORCE_PATH_STYLE || 'true') !== 'false';
    this.client = new S3Client({
      endpoint: process.env.S3_ENDPOINT,
      region: process.env.S3_REGION || 'us-east-1',
      forcePathStyle,
      credentials:
        process.env.S3_ACCESS_KEY_ID && process.env.S3_SECRET_ACCESS_KEY
          ? {
              accessKeyId: process.env.S3_ACCESS_KEY_ID,
              secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
            }
          : undefined,
    });
    return this.client;
  }

  async put(key: string, buffer: Buffer, contentType: string): Promise<void> {
    const client = await this.getClient();
    const { PutObjectCommand } = await import('@aws-sdk/client-s3');
    await client.send(
      new PutObjectCommand({
        Bucket: this.bucket,
        Key: key,
        Body: buffer,
        ContentType: contentType,
      })
    );
  }

  async delete(key: string): Promise<void> {
    const client = await this.getClient();
    const { DeleteObjectCommand } = await import('@aws-sdk/client-s3');
    await client.send(
      new DeleteObjectCommand({
        Bucket: this.bucket,
        Key: key,
      })
    );
  }

  publicUrl(key: string): string {
    // Prefer an explicit public base URL (e.g. a CDN or Garage web endpoint).
    const base = process.env.S3_PUBLIC_URL;
    if (base) {
      return `${base.replace(/\/$/, '')}/${key}`;
    }
    // Fall back to a path-style URL against the configured endpoint.
    const endpoint = (process.env.S3_ENDPOINT || '').replace(/\/$/, '');
    return `${endpoint}/${this.bucket}/${key}`;
  }
}

// ==================== Selection ====================

let instance: Storage | null = null;

export function getStorage(): Storage {
  if (!instance) {
    instance = isS3Enabled() ? new S3Storage() : new LocalStorage();
  }
  return instance;
}
