import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';

export interface LocalUploadUrlResult {
  uploadUrl: string;
  bucket: string;
  region: string;
  objectKey: string;
  expireSeconds: number;
}

export interface LocalObjectInfo {
  size: number;
  etag: string;
  contentType: string;
}

@Injectable()
export class LocalStorageProvider {
  private readonly logger = new Logger(LocalStorageProvider.name);
  private readonly storagePath: string;
  private readonly bucket = 'local';
  private readonly region = 'local';

  constructor(private readonly configService: ConfigService) {
    this.storagePath = this.configService.get<string>(
      'storage.localPath',
      path.resolve(process.cwd(), 'uploads'),
    );
    fs.mkdirSync(this.storagePath, { recursive: true });
    this.logger.log(`Local storage initialized at ${this.storagePath}`);
  }

  getBucket(): string { return this.bucket; }
  getRegion(): string { return this.region; }

  getObjectPath(objectKey: string): string {
    // Prevent path traversal
    const safe = objectKey.replace(/\.\./g, '').replace(/[^a-zA-Z0-9_\-\/\.]/g, '_');
    return path.join(this.storagePath, safe);
  }

  async generateUploadUrl(
    objectKey: string,
    expiresInSeconds = 3600,
  ): Promise<LocalUploadUrlResult> {
    // For local storage, the upload URL is an endpoint on the same API server
    const port = this.configService.get<number>('app.port', 3000);
    const baseUrl = `http://localhost:${port}`;
    const uploadUrl = `${baseUrl}/api/storage/upload/${encodeURIComponent(objectKey)}`;
    return {
      uploadUrl,
      bucket: this.bucket,
      region: this.region,
      objectKey,
      expireSeconds: expiresInSeconds,
    };
  }

  async generateDownloadUrl(
    objectKey: string,
    expiresInSeconds = 86400,
  ): Promise<string> {
    const port = this.configService.get<number>('app.port', 3000);
    return `http://localhost:${port}/api/storage/download/${encodeURIComponent(objectKey)}`;
  }

  async headObject(objectKey: string): Promise<LocalObjectInfo | null> {
    const filePath = this.getObjectPath(objectKey);
    try {
      const stat = fs.statSync(filePath);
      if (!stat.isFile()) return null;

      // Compute etag from file content (first 4KB + last 4KB)
      const fd = fs.openSync(filePath, 'r');
      const buf = Buffer.alloc(8192);
      const bytesRead = fs.readSync(fd, buf, 0, 8192, 0);
      fs.closeSync(fd);
      const etag = crypto.createHash('md5').update(buf.slice(0, bytesRead)).digest('hex');

      return {
        size: stat.size,
        etag,
        contentType: 'application/octet-stream',
      };
    } catch (err: any) {
      if (err.code === 'ENOENT') return null;
      throw err;
    }
  }

  async getObject(objectKey: string): Promise<Buffer | null> {
    try {
      const filePath = this.getObjectPath(objectKey);
      return fs.readFileSync(filePath);
    } catch (err: any) {
      if (err.code === 'ENOENT') return null;
      throw err;
    }
  }

  async deleteObject(objectKey: string): Promise<void> {
    const filePath = this.getObjectPath(objectKey);
    try {
      if (fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
        this.logger.log(`Deleted: ${objectKey}`);
      }
    } catch (err: any) {
      if (err.code === 'ENOENT') return;
      throw err;
    }
  }

  async healthCheck(): Promise<boolean> {
    try {
      fs.accessSync(this.storagePath, fs.constants.W_OK);
      return true;
    } catch {
      return false;
    }
  }

  /**
   * Save uploaded file to local storage.
   */
  saveFile(objectKey: string, buffer: Buffer): string {
    const filePath = this.getObjectPath(objectKey);
    const dir = path.dirname(filePath);
    fs.mkdirSync(dir, { recursive: true });
    fs.writeFileSync(filePath, buffer);
    this.logger.log(`Saved: ${objectKey} (${buffer.length} bytes)`);
    return filePath;
  }
}
