import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  BadRequestException,
  UseInterceptors,
  UsePipes,
  ValidationPipe,
  UploadedFiles,
  Res,
} from '@nestjs/common';
import { MultiFileUploadService } from './multi-file-upload.service';
import { CreateMultiFileUploadDto } from './dto/create-multi-file-upload.dto';
import { UpdateMultiFileUploadDto } from './dto/update-multi-file-upload.dto';
import * as multer from 'multer';
import * as path from 'path';
import * as fs from 'fs';
import { generateSlug } from 'src/common/slug/slug-url';
import { FileFieldsInterceptor } from '@nestjs/platform-express';
import {
  errorResponse,
  successResponse,
  validationErrorMessage,
} from 'src/common/errors/response-config';
import { Response as ExpressResponse } from 'express';
import { commonMessage } from 'src/common/messages';
import * as sharp from 'sharp';
import { CreatePortfolioImageDto } from './dto/create-portfolio-image.dto';
import isValidUuidV4 from 'src/common/uuid validator/uuid-validate';
import { UpdatePortfolioImageDto } from './dto/update-portfolio-image.dto';
import { saveError } from 'src/api-logs/api-error-log';
import { InjectRepository } from '@nestjs/typeorm';
import { ApiLog } from 'src/api-logs/entities/api-log.entity';
import { Repository } from 'typeorm';

const createFolderIfNotExist = (path) => {
  if (!fs.existsSync(path)) {
    fs.mkdirSync(path, { recursive: true, mode: 0o775 });
  }
};

const multerOptions = {
  storage: multer.diskStorage({
    destination: function (req, file, cb) {
      let uploadPath;
      switch (file.fieldname) {
        case 'single':
          uploadPath = './public/uploads/single-file';
          break;
        case 'multiple':
          uploadPath = './public/uploads/multiple-file';
          break;
        default:
          return cb(new Error('Unknown field'), null);
      }
      createFolderIfNotExist(uploadPath);
      cb(null, uploadPath);
    },
    filename: function (req, file, cb) {
      const timestamp = Date.now();
      const originalFilename = file.originalname;
      const fileExtension = originalFilename.split('.').pop();
      const fileNameWithoutExtension = originalFilename.replace(
        `.${fileExtension}`,
        '',
      );
      const slugifyFilename = generateSlug(fileNameWithoutExtension);
      const newFilename = `${slugifyFilename}-${timestamp}.${fileExtension}`;
      return cb(null, newFilename);
    },
  }),
  fileFilter: (req, file, cb) => {
    if (!file.originalname.match(/\.(jpg|jpeg|png|webp)$/)) {
      return cb(
        new BadRequestException({
          status: false,
          statusCode: 400,
          message:
            'Only following file formats are allowed... (jpg, jpeg, png, webp) are allowed.',
          data: [],
        }),
        false,
      );
    }
    cb(null, true);
  },
};

@Controller('multi-file-upload')
export class MultiFileUploadController {
  constructor(
    private readonly multiFileUploadService: MultiFileUploadService,
    @InjectRepository(ApiLog)
    private readonly apiLogRepository: Repository<ApiLog>,
  ) {}

  @Post()
  @UseInterceptors(
    FileFieldsInterceptor(
      [
        { name: 'single', maxCount: 1 },
        { name: 'multiple', maxCount: 10 },
      ],
      multerOptions,
    ),
  )
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
    }),
  )
  async create(
    @Body() createMultiFileUploadDto: CreateMultiFileUploadDto,
    @UploadedFiles() files,
    @Res() res: ExpressResponse,
  ) {
    try {
      const singleFile = files['single'] ? files['single'][0] : null;
      const multipleFiles = files['multiple'];

      if (!singleFile || !multipleFiles || multipleFiles.length === 0) {
        return res.send(errorResponse(404, commonMessage.noAttachmentFound));
      }

      // Function to convert an image to .webp format with metadata density of 72
      async function convertImageToWebp(inputPath: string, outputPath: string) {
        await sharp(inputPath)
          .webp({ lossless: true })
          .withMetadata({ density: 72 })
          .toFile(outputPath);
      }

      // Convert the hero image to .webp format if it's not already .webp
      if (singleFile) {
        const heroImagePath = `./public/uploads/single-file/${singleFile.filename}`;
        const heroWebpPath = `./public/uploads/single-file/${singleFile.filename.replace(
          /\.[^/.]+$/,
          '.webp',
        )}`;

        if (path.extname(heroImagePath) === '.webp') {
          // If the image is already in .webp format, use it as is
          createMultiFileUploadDto.avatar = `uploads/single-file/${singleFile.filename}`;
        } else {
          // Convert to .webp format
          await convertImageToWebp(heroImagePath, heroWebpPath);

          // Delete the original hero image after conversion
          fs.unlinkSync(heroImagePath);

          // Update the DTO with the converted .webp image
          createMultiFileUploadDto.avatar = `uploads/single-file/${singleFile.filename.replace(
            /\.[^/.]+$/,
            '.webp',
          )}`;
        }
      }

      // Convert the multiple files to .webp format
      const imageUrls = multipleFiles.map(async (file) => {
        const filePath = `./public/uploads/multiple-file/${file.filename}`;

        // Check if the file is already in .webp format
        if (path.extname(filePath) === '.webp') {
          return `uploads/multiple-file/${file.filename}`;
        }

        const webpPath = filePath.replace(/\.[^/.]+$/, '.webp');

        await convertImageToWebp(filePath, webpPath);

        // Delete the original image after conversion
        fs.unlinkSync(filePath);

        return `uploads/multiple-file/${file.filename.replace(
          /\.[^/.]+$/,
          '.webp',
        )}`;
      });

      const createPortfolioImageDto = new CreatePortfolioImageDto();
      createPortfolioImageDto.portfolio_images = await Promise.all(imageUrls);

      await this.multiFileUploadService.create(
        createMultiFileUploadDto,
        createPortfolioImageDto,
      );

      res.send(successResponse(201, commonMessage.requestSubmittedSuccess));
    } catch (error) {
      res.status(500).send(errorResponse(500, commonMessage.catchBlockError));
      saveError(error, this.apiLogRepository);
    }
  }

  @Get()
  async findAll(@Res() res: ExpressResponse) {
    try {
      const response = await this.multiFileUploadService.findAll();

      if (response.length > 0) {
        res.send(successResponse(200, commonMessage.successResponse, response));
      } else {
        res.send(successResponse(404, commonMessage.errorResponse));
      }
    } catch (error) {
      res.status(500).send(errorResponse(500, commonMessage.catchBlockError));
      saveError(error, this.apiLogRepository);
    }
  }

  @Get(':id')
  async findOne(@Param('id') id: string, @Res() res: ExpressResponse) {
    try {
      if (!isValidUuidV4(id)) {
        const errorMessage = commonMessage.invalidUuidFormat.replace('#id', id);
        return res.send(errorResponse(400, errorMessage));
      }

      const result = await this.multiFileUploadService.findOne(id);

      if (!result) {
        return res.send(errorResponse(404, commonMessage.getByIdNotFound));
      }

      res.send(successResponse(200, commonMessage.successResponse, result));
    } catch (error) {
      res.status(500).send(errorResponse(500, commonMessage.catchBlockError));
      saveError(error, this.apiLogRepository);
    }
  }

  @Patch(':id')
  @UseInterceptors(
    FileFieldsInterceptor(
      [
        { name: 'single', maxCount: 1 },
        { name: 'multiple', maxCount: 10 },
      ],
      multerOptions,
    ),
  )
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
    }),
  )
  async update(
    @Param('id') id: string,
    @Body() updateMultiFileUploadDto: UpdateMultiFileUploadDto,
    updatePortfolioImageDto: UpdatePortfolioImageDto,
    @UploadedFiles() files,
    @Res() res: ExpressResponse,
  ) {
    if (!isValidUuidV4(id)) {
      const errorMessage = commonMessage.invalidUuidFormat.replace('#id', id);
      res.send(errorResponse(400, errorMessage));
      return;
    }

    try {
      const data = await this.multiFileUploadService.findOne(id);
      const idNotFound = commonMessage.getByIdNotFound.replace('#id', id);

      if (!data) {
        return res.send(errorResponse(404, idNotFound));
      }

      if (data) {
        const singleFile = files['single'] ? files['single'][0] : null;
        const multipleFiles = files['multiple'];

        if (singleFile) {
          const avatarImagePath = `./public/uploads/single-file/${singleFile.filename}`;
          const avatarWebpPath = `./public/uploads/single-file/${singleFile.filename.replace(
            /\.[^/.]+$/,
            '.webp',
          )}`;

          if (path.extname(avatarImagePath) === '.webp') {
            // If the image is already in .webp format, use it as is
            updateMultiFileUploadDto.avatar = `uploads/single-file/${singleFile.filename}`;
          } else {
            // Convert to .webp format
            await sharp(avatarImagePath)
              .webp({ lossless: true })
              .withMetadata({ density: 72 })
              .toFile(avatarWebpPath);

            // Delete the original avatar image after conversion
            fs.unlinkSync(avatarImagePath);

            // Update the DTO with the converted .webp image
            updateMultiFileUploadDto.avatar = `uploads/single-file/${singleFile.filename.replace(
              /\.[^/.]+$/,
              '.webp',
            )}`;
          }
        }

        if (multipleFiles) {
          // Convert the attachments to .webp format
          const imageUrls = multipleFiles.map(async (file) => {
            const filePath = `./public/uploads/multiple-file/${file.filename}`;

            // Check if the file is already in .webp format
            if (path.extname(filePath) === '.webp') {
              return `uploads/multiple-file/${file.filename}`;
            }

            const webpPath = filePath.replace(/\.[^/.]+$/, '.webp');

            await sharp(filePath)
              .webp({ lossless: true })
              .withMetadata({ density: 72 })
              .toFile(webpPath);

            // Delete the original image after conversion
            fs.unlinkSync(filePath);

            return `uploads/multiple-file/${file.filename.replace(
              /\.[^/.]+$/,
              '.webp',
            )}`;
          });

          updatePortfolioImageDto = new UpdatePortfolioImageDto();
          updatePortfolioImageDto.portfolio_images = await Promise.all(
            imageUrls,
          );
        }

        await this.multiFileUploadService.update(
          id,
          updateMultiFileUploadDto,
          updatePortfolioImageDto,
        );
      }

      res.send(successResponse(200, commonMessage.patchRequestUpdateDetails));
    } catch (error) {
      res.status(500).send(errorResponse(500, commonMessage.catchBlockError));
      saveError(error, this.apiLogRepository);
    }
  }

  @Delete(':id')
  async remove(@Param('id') id: string, @Res() res: ExpressResponse) {
    if (!isValidUuidV4(id)) {
      const errorMessage = commonMessage.invalidUuidFormat.replace('#id', id);
      res.send(errorResponse(400, errorMessage));
      return;
    }

    const deletePortfolio = await this.multiFileUploadService.findOne(id);

    try {
      if (deletePortfolio) {
        if (deletePortfolio.avatar) {
          const filePath = path.join(`public/${deletePortfolio.avatar}`);
          if (fs.existsSync(filePath)) {
            fs.unlinkSync(filePath);
          }
        }

        await this.multiFileUploadService.remove(id);

        const successMessage = commonMessage.dataDeleteSuccess.replace(
          '#id',
          id,
        );
        res.send(successResponse(200, successMessage));
      } else {
        const errorMessage = commonMessage.dataDeleteError.replace('#id', id);
        res.status(404).send(errorResponse(404, errorMessage));
      }
    } catch (error) {
      res.status(500).send(errorResponse(500, commonMessage.catchBlockError));
      saveError(error, this.apiLogRepository);
    }
  }
}
