import { Injectable, Logger } from '@nestjs/common';
import { promises as fs } from 'fs';
import * as path from 'path';
import { loadEnv } from '@/config/env';

/**
 * Object storage abstraction for generated documents and uploads.
 * local: files under STORAGE_LOCAL_PATH (suitable for single-node / on-prem with a shared volume)
 * s3:    any S3-compatible store (AWS S3, MinIO, Azure via gateway). Implemented with the
 *        AWS SDK v3 when STORAGE_DRIVER=s3; kept behind this interface so the rest of the
 *        code never knows which one is in use.
 */
export interface StoredObject {
  key: string;
  sizeBytes: number;
}

@Injectable()
export class StorageService {
  private readonly logger = new Logger(StorageService.name);
  private readonly env = loadEnv();

  /** Keys are namespaced per tenant to make per-tenant export / deletion trivial. */
  buildKey(tenantId: string, category: string, fileName: string): string {
    const safe = fileName.replace(/[^A-Za-z0-9._-]/g, '_');
    const d = new Date();
    return `${tenantId}/${category}/${d.getUTCFullYear()}/${String(d.getUTCMonth() + 1).padStart(2, '0')}/${Date.now()}-${safe}`;
  }

  async put(key: string, data: Buffer, _mimeType: string): Promise<StoredObject> {
    if (this.env.STORAGE_DRIVER === 's3') return this.putS3(key, data, _mimeType);
    const full = path.resolve(this.env.STORAGE_LOCAL_PATH, key);
    await fs.mkdir(path.dirname(full), { recursive: true });
    await fs.writeFile(full, data);
    return { key, sizeBytes: data.length };
  }

  async get(key: string): Promise<Buffer> {
    if (this.env.STORAGE_DRIVER === 's3') return this.getS3(key);
    return fs.readFile(path.resolve(this.env.STORAGE_LOCAL_PATH, key));
  }

  async delete(key: string): Promise<void> {
    if (this.env.STORAGE_DRIVER === 's3') return this.deleteS3(key);
    await fs.rm(path.resolve(this.env.STORAGE_LOCAL_PATH, key), { force: true });
  }

  // ---- S3 (lazy-loaded so the SDK is only required when configured) ----
  private s3Client: unknown;
  private async s3() {
    if (!this.s3Client) {
      // eslint-disable-next-line @typescript-eslint/no-var-requires
      const { S3Client } = require('@aws-sdk/client-s3');
      this.s3Client = new S3Client({
        region: this.env.S3_REGION,
        endpoint: this.env.S3_ENDPOINT,
        forcePathStyle: !!this.env.S3_ENDPOINT,
        credentials: this.env.S3_ACCESS_KEY_ID ? { accessKeyId: this.env.S3_ACCESS_KEY_ID, secretAccessKey: this.env.S3_SECRET_ACCESS_KEY ?? '' } : undefined,
      });
    }
    return this.s3Client as { send: (cmd: unknown) => Promise<{ Body?: { transformToByteArray: () => Promise<Uint8Array> } }> };
  }
  private async putS3(key: string, data: Buffer, mimeType: string): Promise<StoredObject> {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const { PutObjectCommand } = require('@aws-sdk/client-s3');
    await (await this.s3()).send(new PutObjectCommand({ Bucket: this.env.S3_BUCKET, Key: key, Body: data, ContentType: mimeType }));
    return { key, sizeBytes: data.length };
  }
  private async getS3(key: string): Promise<Buffer> {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const { GetObjectCommand } = require('@aws-sdk/client-s3');
    const out = await (await this.s3()).send(new GetObjectCommand({ Bucket: this.env.S3_BUCKET, Key: key }));
    return Buffer.from(await out.Body!.transformToByteArray());
  }
  private async deleteS3(key: string): Promise<void> {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const { DeleteObjectCommand } = require('@aws-sdk/client-s3');
    await (await this.s3()).send(new DeleteObjectCommand({ Bucket: this.env.S3_BUCKET, Key: key }));
  }
}
