import {
  Injectable,
  BadRequestException,
  NotFoundException,
  ForbiddenException,
} from '@nestjs/common';
import { writeFile } from 'fs/promises';
import * as fs from 'fs';
import * as path from 'path';
import sizeOf from 'image-size';
import { promises } from 'fs';
import axios from 'axios';
import { SystemLang } from '@/system/lang/system.lang';
import { Response } from 'express';
import AppResponse from '@/system/response/ktq-response';
import { EventEmitter2 } from '@nestjs/event-emitter';
@Injectable()
export class MediasService {
  constructor(private eventEmitter: EventEmitter2) {}

  private ROOT_MEDIA_FOLDER = './public';
  private readonly allowedTypes = ['avatars', 'products'];

  async saveBase64Image(
    base64: string,
    filenamePrefix = 'image',
    folder: string,
    maxSizeInMB = 2,
  ): Promise<{
    filename: string;
    filepath: string;
    url: string;
    mimetype: string;
    width?: number;
    height?: number;
    sizeInKB: number;
  }> {
    let mimeType = '';
    let base64Data = '';

    const matches = base64.match(/^data:(.+);base64,(.*)$/);
    if (matches) {
      mimeType = matches[1];
      base64Data = matches[2];
    } else {
      base64Data = base64;
      mimeType = 'image/jpeg';
    }

    const buffer = Buffer.from(base64Data, 'base64');

    const sizeInBytes = buffer.length;
    const sizeInKB = Math.round(sizeInBytes / 1024);
    const sizeInMB = sizeInBytes / (1024 * 1024);
    if (sizeInMB > maxSizeInMB) {
      throw new BadRequestException(`Ảnh vượt quá giới hạn ${maxSizeInMB}MB`);
    }

    const extension = this._getExtensionFromMime(mimeType);
    if (!extension) {
      throw new BadRequestException('Không hỗ trợ loại ảnh này');
    }

    const fullPath = path.join(this.ROOT_MEDIA_FOLDER, folder);
    if (!fs.existsSync(fullPath)) {
      fs.mkdirSync(fullPath, { recursive: true });
    }

    const fileNameOnly = `${filenamePrefix}-${Date.now()}.${extension}`;
    const filepath = path.join(fullPath, fileNameOnly);
    const relativeUrl = path.join(folder, fileNameOnly).replace(/\\/g, '/');

    await writeFile(filepath, buffer);

    let dimensions: { width?: number; height?: number } = {};
    try {
      dimensions = sizeOf(buffer);
    } catch (_) {}

    return {
      filename: fileNameOnly,
      filepath,
      url: `/uploads/${relativeUrl}`, // nếu bạn serve thư mục public tại /uploads
      mimetype: mimeType,
      sizeInKB,
      width: dimensions.width,
      height: dimensions.height,
    };
  }

  async saveBufferImage(
    buffer: Buffer,
    filenamePrefix = 'image',
    folder: string,
    maxSizeInMB = 2,
    mimeType: string = 'png',
  ): Promise<{
    filename: string;
    filepath: string;
    url: string;
    mimetype: string;
    width?: number;
    height?: number;
    sizeInKB: number;
  }> {
    const sizeInBytes = buffer.length;
    const sizeInMB = sizeInBytes / (1024 * 1024);
    if (sizeInMB > maxSizeInMB) {
      throw new BadRequestException(`Ảnh vượt quá giới hạn ${maxSizeInMB}MB`);
    }
    const sizeInKB = Math.round(sizeInBytes / 1024);

    // Tạo thư mục nếu chưa có
    const fullPath = path.join(this.ROOT_MEDIA_FOLDER, folder);
    if (!fs.existsSync(fullPath)) {
      await promises.mkdir(fullPath, { recursive: true });
    }

    // Tạo tên file
    const fileNameOnly = `${filenamePrefix}-${Date.now()}.${mimeType}`;
    const filepath = path.join(fullPath, fileNameOnly);

    // Ghi file
    await writeFile(filepath, buffer);

    // Lấy kích thước ảnh
    let dimensions: { width?: number; height?: number } = {};
    try {
      dimensions = sizeOf(buffer);
    } catch (_) {
      // Không lấy được kích thước thì thôi
    }

    // Tạo url tương đối (giả sử serve public tại /uploads)
    const relativeUrl = path.join(folder, fileNameOnly).replace(/\\/g, '/');

    return {
      filename: fileNameOnly,
      filepath,
      url: `/uploads/${relativeUrl}`,
      mimetype: mimeType,
      sizeInKB,
      width: dimensions.width,
      height: dimensions.height,
    };
  }

  /**
   * Extract ID from filename
   * Example: "product-51-1755224843970.jpg" => 51
   * Example: "avatar-123-1755224843970.png" => 123
   */
  extractIdFromFilename(filename: string): number | null {
    const match = filename.match(/^[^-]+-(\d+)-/);
    if (match) {
      return Number(match[1]);
    }
    return null;
  }

  async downloadImageAndSave(
    url: string,
    folder: string,
    path: string,
  ): Promise<{
    filename: string;
    filepath: string;
    url: string;
    mimetype: string;
    width?: number;
    height?: number;
    sizeInKB: number;
  }> {
    try {
      const response = await axios.get<ArrayBuffer>(url, {
        responseType: 'arraybuffer',
      });

      const buffer = Buffer.from(response.data);

      const filename = await this.saveBufferImage(buffer, folder, path);

      return filename;
    } catch (error) {
      console.warn('Failed to download image:', url, error);
      throw error;
    }
  }

  async removeMediasFolder(filepath: string, folder: string): Promise<void> {
    if (!filepath) return;

    // Luôn xử lý path bắt đầu từ 'medias/filepaths'
    const relativePath = path.join('medias/' + folder, filepath); // ✅ chính xác từ input: khangpn/avatar-xxx.jpg
    const fullPath = path.join(this.ROOT_MEDIA_FOLDER, relativePath);

    const rootPath = path.resolve(this.ROOT_MEDIA_FOLDER);
    const resolvedFullPath = path.resolve(fullPath);

    if (!resolvedFullPath.startsWith(rootPath)) {
      throw new Error('Invalid: Cannot delete outside the specified directory');
    }

    try {
      // Xóa file avatar
      if (fs.existsSync(fullPath)) {
        await fs.promises.unlink(fullPath);
      }

      // Xóa folder username nếu rỗng (khangpn)
      const usernameFolder = path.dirname(fullPath); // => /public/medias/avatars/khangpn
      const files = await fs.promises.readdir(usernameFolder);
      if (files.length === 0) {
        await fs.promises.rmdir(usernameFolder);
      }
    } catch (error) {
      console.error(`Error when deleting avatar or folder: ${error.message}`);
    }
  }

  async renameMediasFolder(
    oldFilepath: string,
    folder: string,
    newFilename: string,
  ): Promise<void> {
    if (!oldFilepath) return;

    // Đường dẫn cũ
    const oldRelativePath = path.join('medias/' + folder, oldFilepath);
    const oldFullPath = path.join(this.ROOT_MEDIA_FOLDER, oldRelativePath);

    // Đường dẫn mới
    const newRelativePath = path.join('medias/' + folder, newFilename);
    const newFullPath = path.join(this.ROOT_MEDIA_FOLDER, newRelativePath);

    const rootPath = path.resolve(this.ROOT_MEDIA_FOLDER);
    const resolvedOldFullPath = path.resolve(oldFullPath);
    const resolvedNewFullPath = path.resolve(newFullPath);

    // Ngăn đổi tên ra ngoài thư mục ROOT_MEDIA_FOLDER
    if (
      !resolvedOldFullPath.startsWith(rootPath) ||
      !resolvedNewFullPath.startsWith(rootPath)
    ) {
      throw new Error('Invalid: Cannot rename outside the specified directory');
    }

    try {
      if (fs.existsSync(resolvedOldFullPath)) {
        await fs.promises.rename(resolvedOldFullPath, resolvedNewFullPath);
      } else {
        console.warn(`File not found: ${oldFullPath}`);
      }
    } catch (error) {
      console.error(`Error when renaming file: ${error.message}`);
    }
  }

  private _getExtensionFromMime(mime: string): string | null {
    const map = {
      'image/jpeg': 'jpg',
      'image/png': 'png',
      'image/gif': 'gif',
      'image/webp': 'webp',
    };
    return map[mime] || null;
  }

  private _getDatePath(): string[] {
    const now = new Date();
    const yyyy = now.getFullYear().toString();
    const mm = String(now.getMonth() + 1).padStart(2, '0');
    const dd = String(now.getDate()).padStart(2, '0');
    return [yyyy, mm, dd];
  }

  private buildFilePath(
    type: string,
    subdir: string,
    filename: string,
  ): string {
    return path.join(process.cwd(), 'public', 'medias', type, subdir, filename);
  }

  private validateType(type: string) {
    if (!this.allowedTypes.includes(type)) {
      throw new NotFoundException(
        SystemLang.getText('messages', 'file_not_found'),
      );
    }
  }

  async serveMedia(
    type: 'avatars' | 'products',
    subdir: string,
    filename: string,
    res: Response,
  ) {
    this.validateType(type);

    const filePath = this.buildFilePath(type, subdir, filename);

    if (!fs.existsSync(filePath)) {
      throw new NotFoundException(
        SystemLang.getText('messages', 'file_not_found'),
      );
    }

    return res.sendFile(filePath);
  }

  async deleteMedia(
    type: 'avatars' | 'products',
    subdir: string,
    filename: string,
  ) {
    if (!['avatars', 'products'].includes(type)) {
      throw new ForbiddenException(
        SystemLang.getText('messages', 'file_not_found'),
      );
    }

    const filePath = path.join(
      process.cwd(),
      'public',
      'medias',
      type,
      subdir,
      filename,
    );

    if (!fs.existsSync(filePath)) {
      throw new NotFoundException(
        SystemLang.getText('messages', 'file_not_found'),
      );
    }

    try {
      fs.unlinkSync(filePath);

      // bắn event sau khi xóa
      this.eventEmitter.emit('media.deleted', {
        type,
        subdir,
        filename,
        filePath,
      });

      return AppResponse.toResponse(true);
    } catch (err) {
      throw new ForbiddenException(
        SystemLang.getText('messages', 'file_delete_failed'),
      );
    }
  }
}
