import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { Readable } from 'node:stream';
import type {
  DeleteObjectInput,
  GetObjectInput,
  ObjectExistsInput,
  PutObjectInput,
  StorageProvider
} from '../types';

function resolveLocalRoot() {
  return path.resolve(process.cwd(), process.env.LOCAL_STORAGE_ROOT?.trim() || 'storage');
}

function resolveAbsolutePath(key: string) {
  return path.resolve(resolveLocalRoot(), key);
}

function assertRelativeKey(key: string) {
  const normalized = key.replace(/\\/g, '/').replace(/^\/+/, '');

  if (!normalized || normalized.includes('..')) {
    throw new Error('Storage key must be a safe relative path');
  }

  return normalized;
}

export function createLocalStorageProvider(): StorageProvider {
  return {
    async putObject(input: PutObjectInput) {
      const key = assertRelativeKey(input.key);
      const absolutePath = resolveAbsolutePath(key);

      await mkdir(path.dirname(absolutePath), { recursive: true });
      await writeFile(absolutePath, input.body);

      return {
        provider: 'local',
        key,
        contentType: input.contentType,
        fileName: input.fileName,
        size: input.body.byteLength
      };
    },
    async getObject(input: GetObjectInput) {
      const key = assertRelativeKey(input.key);
      const absolutePath = resolveAbsolutePath(key);
      const [buffer, fileStats] = await Promise.all([readFile(absolutePath), stat(absolutePath)]);

      return {
        provider: 'local',
        key,
        contentType: 'application/octet-stream',
        size: fileStats.size,
        body: Readable.toWeb(Readable.from(buffer)) as ReadableStream<Uint8Array>
      };
    },
    async deleteObject(input: DeleteObjectInput) {
      const key = assertRelativeKey(input.key);
      const absolutePath = resolveAbsolutePath(key);
      await rm(absolutePath, { force: true });
    },
    async objectExists(input: ObjectExistsInput) {
      const key = assertRelativeKey(input.key);
      const absolutePath = resolveAbsolutePath(key);

      try {
        await stat(absolutePath);
        return true;
      } catch {
        return false;
      }
    }
  };
}
