import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  UsePipes,
  Res,
  UseInterceptors,
  UploadedFile,
  ValidationPipe,
  HttpStatus,
  BadRequestException,
  NotFoundException,
  UnauthorizedException,
  UseGuards,
  Query,
  ConflictException,
  Headers,
  Req,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { Response as ExpressResponse, Request } from 'express';
import * as path from 'path';
import * as fs from 'fs';
import { FileInterceptor } from '@nestjs/platform-express';
import {
  excelFileConfig,
  profilePictureConfig,
} from 'src/common/file upload/multer-config';
import {
  errorResponse,
  successResponse,
  validationErrorMessage,
} from 'src/common/errors/response-config';
import { commonMessage } from 'src/common/messages';
import isValidUuidV4 from 'src/common/uuid validator/uuid-validate';
import { isEmpty } from 'src/common/helper';
import { InjectRepository } from '@nestjs/typeorm';
import { ApiLog } from 'src/api-logs/entities/api-log.entity';
import { DataSource, Repository } from 'typeorm';
import { saveError } from 'src/api-logs/api-error-log';
import { UserLoginDto } from './dto/user-login.dto';
import { lan } from 'src/lan';
import { ChangePasswordDto } from './dto/change-password.dto';
import { AuthGuardMiddleware } from 'src/common/middleware/auth.middleware';
import { errorMailContent } from 'src/common/mail/mail-content';
import { mailConfig } from 'src/common/mail/mail-config';
import { forgetPasswordDto } from './dto/forgot-password.dto';
import * as nodemailer from 'nodemailer';
import { emailContent } from 'src/common/mail/forgot-password-template';
import { generateApprovalEmail } from 'src/common/mail/approval-tempate';
import { generateRejectionEmail } from 'src/common/mail/reject-template';
import { newUserApprovalMailContent } from 'src/common/mail/new-user-approval-template';

@Controller('users')
export class UserController {
  constructor(
    private readonly userService: UserService,
    @InjectRepository(ApiLog)
    private readonly apiLogRepository: Repository<ApiLog>,
    private readonly connection: DataSource,
  ) {}

  @Post('admin/add/upload-excel')
  @UseGuards(AuthGuardMiddleware)
  @UseInterceptors(FileInterceptor('file', excelFileConfig))
  async uploadExcelFile(
    @Res() res: ExpressResponse,
    @UploadedFile() file: Express.Multer.File,
  ): Promise<void> {
    const queryRunner = this.connection.createQueryRunner();

    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      if (!file) {
        throw new BadRequestException('No file uploaded');
      }

      await this.userService.processExcelFile(file.path, queryRunner.manager);
      await queryRunner.commitTransaction();
      res.send(successResponse(201, lan('common.request_submitted')));
    } catch (error) {
      await queryRunner.rollbackTransaction();
      res
        .status(HttpStatus.INTERNAL_SERVER_ERROR)
        .send(errorResponse(500, commonMessage.catchBlockError, error));
    } finally {
      await queryRunner.release();
    }
  }

  @Post('registration')
  @UseInterceptors(FileInterceptor('attachment', profilePictureConfig))
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async create(
    @Body() createUserDto: CreateUserDto,
    @UploadedFile() attachment,
    @Res() res: ExpressResponse,
    @Req() req: Request,
  ) {
    const queryRunner = this.connection.createQueryRunner();
    try {
      await queryRunner.connect();
      await queryRunner.startTransaction();

      if (attachment) {
        const attachmentUrl = `uploads/profile-picture/${attachment.filename}`;
        createUserDto.profile_picture = attachmentUrl;
      }

      const user = await this.userService.create(
        createUserDto,
        queryRunner.manager,
      );
      await queryRunner.commitTransaction();

      const deviceType = req?.headers['device-type'];
      if (deviceType !== 'web' && user) {
        const fullName = `${user.first_name} ${user.last_name}`;
        const adminPanelLink =
          process.env.ADMIN_PANEL_LINK || process.env.FRONTEND_DOMAIN;
        const emailTo = process.env.SEND_RESET_PASSWORD_EMAIL;

        if (emailTo) {
          const content = newUserApprovalMailContent({
            fullName,
            email: user.email,
            adminPanelLink,
          });

          const transporter = nodemailer.createTransport(mailConfig);

          const mailOptions = {
            from: process.env.EMAIL_FROM,
            to: emailTo,
            subject: 'New User Approval Required',
            html: content,
          };

          await transporter.sendMail(mailOptions);
        }
      }
      res.send(successResponse(201, lan('common.request_submitted')));
    } catch (error) {
      saveError(error, this.apiLogRepository);

      if (error instanceof ConflictException) {
        res.status(400).send(errorResponse(400, lan('email.already.exist')));
      } else {
        res
          .status(500)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }
    } finally {
      await queryRunner.release();
    }
  }

  @Post('login')
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async login(
    @Body() loginDto: UserLoginDto,
    @Res() res: ExpressResponse,
    @Headers() headers: any,
    @Req() req: Request,
  ) {
    try {
      const deviceType = req?.headers['device-type'];

      const user = await this.userService.login(loginDto, deviceType);

      if (user.status === 0) {
        return res.send(errorResponse(422, lan('user.not.approved')));
      }

      if (user.status === 2) {
        return res.send(errorResponse(422, lan('common.unauthorized')));
      }
      ``;
      res
        .status(HttpStatus.OK)
        .send(successResponse(200, lan('login.success'), user));
    } catch (error) {
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.NOT_FOUND)
          .send(errorResponse(404, lan('login.user_not_found')));
      } else if (error instanceof UnauthorizedException) {
        res
          .status(HttpStatus.UNAUTHORIZED)
          .send(errorResponse(401, error.message));
      } else if (error instanceof BadRequestException) {
        res
          .status(HttpStatus.UNAUTHORIZED)
          .send(errorResponse(400, error.message));
      } else {
        saveError(error, this.apiLogRepository);
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }
    }
  }

  @Post('logout/:id')
  @UseGuards(AuthGuardMiddleware)
  async logout(@Param('id') id: string, @Res() res: ExpressResponse) {
    try {
      await this.userService.logout(id);
      res
        .status(HttpStatus.OK)
        .send(successResponse(200, lan('logout.success')));
    } catch (error) {
      if (error instanceof UnauthorizedException) {
        res
          .status(HttpStatus.UNAUTHORIZED)
          .send(errorResponse(401, lan('login.user_not_found')));
      } else {
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }
      saveError(error, this.apiLogRepository);
    }
  }

  @Get('approval')
  @UseGuards(AuthGuardMiddleware)
  async findForApproval(
    @Res() res: ExpressResponse,
    @Query('take') take: number,
    @Query('skip') skip: number,
  ) {
    try {
      const response = await this.userService.findForApproval(take, skip);

      response?.data.forEach((user: any) => {
        delete user.password;
      });

      if (response?.data.length > 0) {
        res.send(
          successResponse(
            200,
            lan('common.data_retrieved_successfully'),
            response,
          ),
        );
      } else {
        res.send(successResponse(404, lan('common.not_found')));
      }
    } catch (error) {
      res
        .status(500)
        .send(errorResponse(500, lan('common.internal_server_error')));
      saveError(error, this.apiLogRepository);
    }
  }

  @Patch('approve/:id')
  @UseGuards(AuthGuardMiddleware)
  async approveUser(
    @Param('id') id: string,
    @Body('status') status: number,
    @Res() res: ExpressResponse,
  ) {
    try {
      const approvalRequest = await this.userService.approveUser(id, status);

      if (approvalRequest.status === 1) {
        const content = generateApprovalEmail(approvalRequest.first_name);

        const transporter = nodemailer.createTransport(mailConfig);

        const mailOptions = {
          from: `Alumni<no-reply@Alumni.com>`,
          to: `${approvalRequest.email}`,
          subject: `Request has been approve`,
          html: content,
        };

        transporter.sendMail(mailOptions);
      }
      if (approvalRequest.status === 2) {
        const content = generateRejectionEmail(approvalRequest.first_name);

        const transporter = nodemailer.createTransport(mailConfig);

        const mailOptions = {
          from: `Alumni<no-reply@Alumni.com>`,
          to: `${approvalRequest.email}`,
          subject: `Request has been rejected`,
          html: content,
        };

        transporter.sendMail(mailOptions);
      }
      res
        .status(HttpStatus.OK)
        .send(successResponse(200, lan('common.data_updated')));
    } catch (error) {
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.NOT_FOUND)
          .send(errorResponse(404, lan('common.not_found')));
      } else {
        saveError(error, this.apiLogRepository);
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }
    }
  }

  @Post('change-password')
  @UseGuards(AuthGuardMiddleware)
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async changePass(
    @Body() changePassDto: ChangePasswordDto,
    @Res() res: ExpressResponse,
  ) {
    try {
      const user = await this.userService.changePassword(changePassDto);

      res
        .status(HttpStatus.OK)
        .send(
          successResponse(
            200,
            lan('change.password.password_changed_success'),
            user,
          ),
        );
    } catch (error) {
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.NOT_FOUND)
          .send(errorResponse(404, lan('login.user_not_found')));
      } else if (error instanceof ConflictException) {
        res
          .status(HttpStatus.CONFLICT)
          .send(
            errorResponse(409, lan('change.password.new_password_not_same')),
          );
      } else if (error instanceof UnauthorizedException) {
        res
          .status(HttpStatus.UNAUTHORIZED)
          .send(errorResponse(400, lan('login.invalid_current_password')));
      } else if (error instanceof BadRequestException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(400, error.message));
      } else {
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }
      saveError(error, this.apiLogRepository);
    }
  }

  @Get('filter')
  // @UseGuards(AuthGuardMiddleware)
  async findAllByFilter(
    @Res() res: ExpressResponse,
    @Query('years') years: number[],
    @Query('courseId') courseId: string[],
  ) {
    try {
      const yearsArray = years || [];
      const response = await this.userService.findAllUserByFilter(
        yearsArray,
        courseId,
      );
      const removePassword = response.map((result) => {
        const { password, ...rest } = result;
        return rest;
      });

      if (response.length > 0) {
        res.send(
          successResponse(
            200,
            lan('common.data_retrieved_successfully'),
            removePassword,
          ),
        );
      } else {
        res.send(successResponse(404, lan('common.not_found')));
      }
    } catch (error) {
      saveError(error, this.apiLogRepository);

      res
        .status(500)
        .send(errorResponse(500, lan('common.internal_server_error')));
    }
  }

  @Get('get')
  @UseGuards(AuthGuardMiddleware)
  async findAll(
    @Query('take') take: number,
    @Query('skip') skip: number,
    @Query('search') search: string,
    @Query('years') years: number[],
    @Query('courseIds') courseIds: string[],
    @Query('date_of_birth') dateOfBirth: string,
    @Query('city_ids') cityIds: string[],
    @Query('job_ids') jobIds: string[],
    @Query('key') key: string,
    @Query('order') order: string,
    @Res() res: ExpressResponse,
  ) {
    try {
      const response = await this.userService.findAllActiveUser(
        take,
        skip,
        search,
        years,
        courseIds,
        dateOfBirth,
        cityIds,
        jobIds,
        key,
        order,
      );

      response.data.forEach((user: any) => {
        delete user.password;
      });

      if (response.data.length > 0) {
        res.send(
          successResponse(
            200,
            lan('common.data_retrieved_successfully'),
            response,
          ),
        );
      } else {
        res.send(successResponse(200, lan('common.data_not_found')));
      }
    } catch (error) {
      saveError(error, this.apiLogRepository);

      res
        .status(500)
        .send(errorResponse(500, lan('common.internal_server_error')));
    }
  }

  @Get(':id')
  @UseGuards(AuthGuardMiddleware)
  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.userService.findOne(id);

      delete result.password;

      if (isEmpty(result)) {
        return res.send(errorResponse(404, lan('common.not_found')));
      }

      res.send(
        successResponse(200, lan('common.data_retrieved_successfully'), result),
      );
    } catch (error) {
      res
        .status(500)
        .send(errorResponse(500, lan('common.internal_server_error')));
      saveError(error, this.apiLogRepository);
    }
  }

  @Post('forgot-password')
  async forgotPassword(@Body() inputEmail: any, @Res() res: ExpressResponse) {
    try {
      const { email } = inputEmail;

      if (isEmpty(email)) {
        return res.send(errorResponse(422, lan('email.empty')));
      }

      const result = await this.userService.forgotPassword(email);

      if (isEmpty(result)) {
        return res.send(successResponse(200, lan('mail.success.user')));
      }

      const { userDetail, token } = result;

      if (isEmpty(userDetail)) {
        return res.send(successResponse(200, lan('mail.success.user')));
      }

      if (
        userDetail.email?.toLowerCase().trim() ===
        inputEmail.email?.toLowerCase().trim()
      ) {
        const resetPasswordLink = `${process.env.FRONTEND_DOMAIN}reset-password?token=${token}`;

        const content = emailContent(resetPasswordLink);
        const transporter = nodemailer.createTransport(mailConfig);

        const mailOptions = {
          from: process.env.EMAIL_FROM,
          to: email,
          subject: 'Reset Password Email',
          html: content,
        };

        await transporter.sendMail(mailOptions);
      }

      return res.send(successResponse(200, lan('mail.success.user')));
    } catch (error) {
      saveError(error, this.apiLogRepository);

      res
        .status(500)
        .send(errorResponse(500, lan('common.internal_server_error')));
    }
  }

  @Post('reset-password/:token')
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async resetPassword(
    @Body() resetPasswordDto: forgetPasswordDto,
    @Param('token') token: string,
    @Res() res: ExpressResponse,
  ) {
    try {
      const { email, new_password, confirm_password } = resetPasswordDto;

      if (new_password !== confirm_password) {
        return res.send(
          errorResponse(422, lan('change.password.password_do_not_match')),
        );
      }

      await this.userService.resetPassword(email, token, new_password);

      return res.send(
        successResponse(200, lan('change.password.password_changed_success')),
      );
    } catch (error) {
      switch (error.message) {
        case lan('middleware.token_not_found'):
          return res.send(
            errorResponse(400, lan('middleware.token_not_found')),
          );
        case lan('login.user_not_found'):
          return res.send(errorResponse(400, lan('login.user_not_found')));
        case lan('middleware.invalid_token'):
          return res.send(errorResponse(400, lan('reset.link.expire')));
        case lan('middleware.token_expire'):
          return res.send(errorResponse(400, lan('middleware.token_expire')));
        case lan('change.password.new_password_same_as_old'):
          return res.send(
            errorResponse(400, lan('change.password.new_password_same_as_old')),
          );
        default:
          saveError(error, this.apiLogRepository);

          return res
            .status(500)
            .send(errorResponse(500, lan('common.internal_server_error')));
      }
    }
  }

  @Patch('update/:id')
  @UseGuards(AuthGuardMiddleware)
  @UseInterceptors(FileInterceptor('attachment', profilePictureConfig))
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async update(
    @Param('id') id: string,
    @UploadedFile() attachment,
    @Body() updateUserDto: UpdateUserDto,
    @Res() res: ExpressResponse,
  ) {
    try {
      if (!isValidUuidV4(id)) {
        const errorMessage = commonMessage.invalidUuidFormat.replace('#id', id);
        res.send(errorResponse(400, errorMessage));
        return;
      }

      const data = await this.userService.findOne(id);

      if (isEmpty(data)) {
        return res.send(errorResponse(404, lan('common.data_not_found')));
      }

      if (data) {
        if (attachment) {
          const attachmentUrl = `uploads/profile-picture/${attachment.filename}`;
          updateUserDto.profile_picture = attachmentUrl;
        }

        await this.userService.update(id, updateUserDto);
      }

      res.send(successResponse(200, lan('common.data_updated')));
    } catch (error) {
      saveError(error, this.apiLogRepository);

      res
        .status(500)
        .send(errorResponse(500, lan('common.internal_server_error')));
    }
  }

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

      const deleteAppliedJob = await this.userService.findOne(id);

      if (isEmpty(deleteAppliedJob)) {
        return res.send(errorResponse(404, lan('common.data_not_found')));
      }

      if (deleteAppliedJob) {
        if (deleteAppliedJob.profile_picture) {
          const resumeDeletePath = deleteAppliedJob.profile_picture.replace(
            process.env.DOMAIN,
            '',
          );
          const filePath = path.join(`public/${resumeDeletePath}`);
          if (fs.existsSync(filePath)) {
            fs.unlinkSync(filePath);
          }
        }

        await this.userService.remove(id);

        res.send(successResponse(200, lan('common.data_deleted')));
      } else {
        res.status(404).send(errorResponse(404, lan('common.delete_error')));
      }
    } catch (error) {
      saveError(error, this.apiLogRepository);

      res
        .status(500)
        .send(errorResponse(500, lan('common.internal_server_error')));
    }
  }

  @Patch('reset-password/:id')
  @UseGuards(AuthGuardMiddleware)
  async resetPasswordById(
    @Param('id') id: string,
    @Res() res: ExpressResponse,
    @Req() req: Request,
  ) {
    try {
      if (!isValidUuidV4(id)) {
        const errorMessage = commonMessage.invalidUuidFormat.replace('#id', id);
        return res.send(errorResponse(400, errorMessage));
      }

      const user = await this.userService.resetPasswordById(id);

      if (user) {
        delete user.password;
      }

      res.send(
        successResponse(
          200,
          lan('change.password.password_changed_success'),
          user,
        ),
      );
    } catch (error) {
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.NOT_FOUND)
          .send(errorResponse(404, lan('login.user_not_found')));
      } else if (error instanceof BadRequestException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(400, error.message));
      } else {
        saveError(error, this.apiLogRepository);
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }
    }
  }
}
