import { createHash } from 'node:crypto';
import { promises as fs } from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { Injectable } from '@nestjs/common';
import type { StoragePort } from './storage.port';

/**
 * Dev storage: bytes on local disk under MEDIA_DIR, with a sidecar `.meta.json`
 * holding the mime/size/checksum. Object keys may contain a scope subdirectory.
 * A drop-in for an S3/Supabase adapter in prod.
 */
@Injectable()
export class LocalDiskStorage implements StoragePort {
  private readonly root = process.env.MEDIA_DIR?.trim() || path.join(os.tmpdir(), 'iios-media');

  private full(objectKey: string): string {
    // Prevent path traversal: keep everything under root.
    const p = path.normalize(path.join(this.root, objectKey));
    if (!p.startsWith(path.normalize(this.root))) throw new Error('invalid object key');
    return p;
  }

  async put(objectKey: string, data: Buffer, mime: string): Promise<{ sizeBytes: number; checksumSha256: string }> {
    const file = this.full(objectKey);
    await fs.mkdir(path.dirname(file), { recursive: true });
    const checksumSha256 = createHash('sha256').update(data).digest('hex');
    await fs.writeFile(file, data);
    await fs.writeFile(`${file}.meta.json`, JSON.stringify({ mime, sizeBytes: data.length, checksumSha256 }));
    return { sizeBytes: data.length, checksumSha256 };
  }

  async get(objectKey: string): Promise<{ data: Buffer; mime: string; sizeBytes: number } | null> {
    const file = this.full(objectKey);
    try {
      const data = await fs.readFile(file);
      const meta = JSON.parse(await fs.readFile(`${file}.meta.json`, 'utf8')) as { mime: string };
      return { data, mime: meta.mime || 'application/octet-stream', sizeBytes: data.length };
    } catch {
      return null;
    }
  }

  async remove(objectKey: string): Promise<void> {
    const file = this.full(objectKey);
    await fs.rm(file, { force: true });
    await fs.rm(`${file}.meta.json`, { force: true });
  }
}
