import { Injectable, BadRequestException, InternalServerErrorException } from '@nestjs/common';
import 'multer';
import { Logger } from 'pino-nestjs';
import { randomUUID } from 'crypto';
import * as path from 'path';

export type UploadInput = {
  filename: string;
  path: string;
  file: Buffer;
};

@Injectable()
export class ImageService {
  private readonly CDN_UPLOAD_URL = 'https://cdn.thefurfinder.com/uploader.php';
  private readonly DEFAULT_TIMEOUT_MS = 30_000;

  constructor(private readonly logger: Logger) {}

  private sanitizeBaseName(originalName: string): string {
    const ext = path.extname(originalName);
    const base = path.basename(originalName, ext);
    const safe = base.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 48);
    return safe || 'file';
  }

  private buildUploadFilename(originalName: string): string {
    const ext = path.extname(originalName).toLowerCase();
    const safeExt = ext && ext.length <= 10 ? ext : '';
    return `${Date.now()}-${randomUUID()}-${this.sanitizeBaseName(originalName)}${safeExt}`;
  }

  private getMimeType(filename: string): string {
    const ext = path.extname(filename).toLowerCase();
    const mimeTypes: Record<string, string> = {
      '.jpg': 'image/jpeg',
      '.jpeg': 'image/jpeg',
      '.png': 'image/png',
      '.gif': 'image/gif',
      '.webp': 'image/webp',
      '.bmp': 'image/bmp',
      '.svg': 'image/svg+xml',
      '.pdf': 'application/pdf',
    };
    return mimeTypes[ext] ?? 'application/octet-stream';
  }

  async uploadToCDN(files: UploadInput[]): Promise<string[]> {
    if (!Array.isArray(files) || files.length === 0) {
      throw new BadRequestException('At least one file is required for upload.');
    }

    const form = new FormData();
    const metadata = files.map(({ filename, path: filePath }) => ({
      filename,
      path: filePath,
    }));

    form.append('data', JSON.stringify(metadata));

    for (const file of files) {
      if (!file.filename || !file.path || !file.file) {
        throw new BadRequestException('Each file must include filename, path, and content buffer.');
      }

      const mimeType = this.getMimeType(file.filename);
      const blob = new Blob([new Uint8Array(file.file)], { type: mimeType });
      form.append('files[]', blob, file.filename);
    }

    const abortController = new AbortController();
    const timeout = setTimeout(() => abortController.abort(), this.DEFAULT_TIMEOUT_MS);

    try {
      this.logger.log(`Uploading ${files.length} files to CDN...`);
      const response = await fetch(this.CDN_UPLOAD_URL, {
        method: 'POST',
        body: form,
        signal: abortController.signal,
      });

      if (!response.ok) {
        const body = await response.text().catch(() => '');
        throw new InternalServerErrorException(
          `CDN upload failed with status ${response.status}. Body: ${body}`,
        );
      }

      const payload = await response.json().catch(() => null);

      let urls: string[] | null = null;
      if (Array.isArray(payload)) {
        urls = payload;
      } else if (payload && typeof payload === 'object' && Array.isArray(payload.data)) {
        urls = payload.data;
      }

      if (!urls) {
        throw new InternalServerErrorException('Invalid response format from CDN.');
      }

      this.logger.log(`CDN upload success: ${urls.length} URLs generated.`);
      return urls;
    } catch (error: any) {
      if (error.name === 'AbortError') {
        throw new InternalServerErrorException(`CDN upload timed out after ${this.DEFAULT_TIMEOUT_MS}ms.`);
      }
      this.logger.error('Error in CDN upload', error);
      throw error;
    } finally {
      clearTimeout(timeout);
    }
  }

  async uploadMulterFiles(multerFiles: Express.Multer.File[], cdnPath: string): Promise<string[]> {
    if (!multerFiles || multerFiles.length === 0) {
      return [];
    }

    const uploadInputs = multerFiles.map((file) => ({
      filename: this.buildUploadFilename(file.originalname),
      path: cdnPath,
      file: file.buffer,
    }));

    return this.uploadToCDN(uploadInputs);
  }
}
