/**
 * Photo blob storage — provider-agnostic `ObjectStore` port + factory.
 *
 * The app depends on this interface, never on a vendor. Two adapters implement
 * it:
 *   - LocalAdapter : ./data/blobs/<key>                     (dev default)
 *   - S3Adapter    : SigV4 against any S3-compatible store  (MinIO/Spaces/AWS/R2)
 *
 * Selection is config (STORAGE_BACKEND). Cross-store transfers (the imajin ETL)
 * construct a second store from explicit creds via `createS3Store` — same
 * interface, no bespoke override type.
 */

import { loadConfig, type Config } from '@/app/config';

import { S3Adapter } from './s3';
import { LocalAdapter } from './local';

export type Body = ReadableStream | Buffer | Uint8Array;

export interface ObjectStore {
  /** Label persisted alongside a stored object (DB `storage_bucket`). */
  readonly bucket: string;
  put(key: string, contentType: string, body: Body): Promise<void>;
  get(key: string): Promise<Buffer>;
  head(key: string): Promise<boolean>;
  /**
   * Short-lived signed GET URL for consumers that fetch originals out-of-band
   * (face-worker, imajin). Always signed — never a bare object URL. The local
   * adapter returns a `file://` URL.
   */
  presignGet(key: string, ttlSeconds?: number): Promise<string>;
}

export interface S3StoreOptions {
  readonly endpoint: string;
  readonly accessKey: string;
  readonly secretKey: string;
  readonly bucket: string;
  readonly region: string;
  /** `false` → virtual-hosted (Spaces/AWS/R2); `true` → path-style (MinIO). */
  readonly forcePathStyle: boolean;
}

export class StorageError extends Error {
  readonly key: string;
  constructor(message: string, key: string, cause?: unknown) {
    super(message);
    this.name = 'StorageError';
    this.key = key;
    if (cause !== undefined) this.cause = cause;
  }
}

/** Build the configured default store (S3 or local) from app config. */
export function createObjectStore(config: Config = loadConfig()): ObjectStore {
  if (config.STORAGE_BACKEND === 's3') {
    if (!config.S3_ENDPOINT || !config.S3_ACCESS_KEY || !config.S3_SECRET_KEY) {
      throw new Error('S3 backend selected but S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY are not all set');
    }
    return new S3Adapter(
      {
        endpoint: config.S3_ENDPOINT,
        accessKey: config.S3_ACCESS_KEY,
        secretKey: config.S3_SECRET_KEY,
        bucket: config.S3_BUCKET,
        region: config.S3_REGION,
        forcePathStyle: config.S3_FORCE_PATH_STYLE,
      },
      config.S3_PRESIGN_TTL_SECONDS,
    );
  }
  return new LocalAdapter(config.STORAGE_LOCAL_PATH);
}

/** Build an S3 store from explicit credentials (e.g. the imajin ETL source). */
export function createS3Store(opts: S3StoreOptions, presignTtlSeconds = 900): ObjectStore {
  return new S3Adapter(opts, presignTtlSeconds);
}

let cached: ObjectStore | undefined;
/** Process-wide default store from config (memoized — clients are reusable). */
export function defaultObjectStore(): ObjectStore {
  return (cached ??= createObjectStore());
}
