import {
  BadRequestException,
  Body,
  Controller,
  Delete,
  Get,
  HttpStatus,
  NotFoundException,
  Param,
  Patch,
  Post,
  Query,
  Req,
  Res,
  UnauthorizedException,
  UploadedFile,
  UseGuards,
  UseInterceptors,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { InjectRepository } from '@nestjs/typeorm';
import { Response as ExpressResponse, Request } from 'express';
import * as nodemailer from 'nodemailer';
import { saveError } from 'src/api-logs/api-error-log';
import { ApiLog } from 'src/api-logs/entities/api-log.entity';
import { ChangePasswordDto } from 'src/auth/dto/change-password.dto';
import { emailContent } from 'src/common/Email/forgot-password-template';
import { mailConfig } from 'src/common/Email/mail-config';
import {
  errorResponse,
  successResponse,
  validationErrorMessage,
} from 'src/common/errors/response-config';
import { imageConfig } from 'src/common/file upload/multer-config';
import { limits } from 'src/common/file upload/multer-config-helper';
import { isEmpty } from 'src/common/helper';
import { AuthMiddleware } from 'src/common/middleware/auth.middleware';
import { lan } from 'src/lan';
import { DataSource, Repository } from 'typeorm';
import { AppUsersService } from './app_users.service';
import { CreateAppUserDto } from './dto/create-app_user.dto';
import { forgetPasswordDto } from './dto/forget-password.dto';
import { UpdateAppUserDto } from './dto/update-app_user.dto';

@Controller('app-users')
export class AppUsersController {
  constructor(
    private readonly appUsersService: AppUsersService,
    @InjectRepository(ApiLog)
    private readonly apiLogRepository: Repository<ApiLog>,
    private readonly connection: DataSource,
  ) {}

  @Post('registration')
  @UseInterceptors(FileInterceptor('profile_picture', imageConfig))
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async create(
    @Body() createAppUserDto: CreateAppUserDto,
    @UploadedFile() profile_picture,
    @Res() res: ExpressResponse,
  ) {
    const queryRunner = this.connection.createQueryRunner();
    try {
      await queryRunner.connect();
      await queryRunner.startTransaction();

      if (profile_picture && profile_picture.size > limits.fileSize['image']) {
        return res.send(errorResponse(413, lan('common.limit_has_exceeded')));
      }

      if (profile_picture) {
        const attachmentUrl = `uploads/app-users/profile-picture/${profile_picture.filename}`;
        createAppUserDto.profile_picture = attachmentUrl;
      }

      const newUser = await this.appUsersService.create(
        createAppUserDto,
        queryRunner.manager,
      );

      await queryRunner.commitTransaction();
      res.send(
        successResponse(
          201,
          createAppUserDto.email && isEmpty(createAppUserDto.social_id)
            ? lan('registration.verification_email')
            : lan('registration.account_created'),
          newUser,
        ),
      );
    } catch (error) {
      if (error instanceof BadRequestException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(400, error.message));
      } else {
        res
          .status(500)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }

      saveError(error, this.apiLogRepository);
    } finally {
      await queryRunner.release();
    }
  }

  @Post('login')
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async login(
    @Body() loginDto: CreateAppUserDto,
    @Res() res: ExpressResponse,
    @Req() req: Request,
  ) {
    const queryRunner = this.connection.createQueryRunner();

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

      const deviceId: any = req.headers['device-id'];

      const fcmToken: any = req.headers['fcm_token'];

      const user = await this.appUsersService.login(
        loginDto,
        queryRunner.manager,
        deviceId,
        fcmToken,
      );

      await queryRunner.commitTransaction();
      const steps = await this.appUsersService.findUserStepsForSingleSteps(
        user.id,
        new Date().toUTCString(),
      );

      const data = {
        ...user,
        today_steps: isEmpty(steps) ? 0 : steps,
      };

      res
        .status(HttpStatus.OK)
        .send(successResponse(200, lan('login.success'), data));
    } catch (error) {
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, error.message));
      } else if (error instanceof UnauthorizedException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(400, error.message));
      } 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);
    } finally {
      await queryRunner.release();
    }
  }

  @Post('change-password')
  @UseGuards(AuthMiddleware)
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async changePass(
    @Body() changePassDto: ChangePasswordDto,
    @Res() res: ExpressResponse,
  ) {
    try {
      const user = await this.appUsersService.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.BAD_REQUEST)
          .send(errorResponse(404, lan('login.user_not_found')));
      } else if (error instanceof UnauthorizedException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(
            errorResponse(400, lan('change.password.invalid_old_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);
    }
  }

  @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.appUsersService.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.trim().toLowerCase() ===
        inputEmail.email.trim().toLowerCase()
      ) {
        const resetPasswordLink = `${process.env.FRONTEND_DOMAIN}reset-password/${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) {
      res
        .status(500)
        .send(errorResponse(500, lan('common.internal_server_error')));

      saveError(error, this.apiLogRepository);
    }
  }

  @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.appUsersService.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('middleware.invalid_token'):
          return res.send(errorResponse(400, lan('reset.link.expire')));
        case lan('login.user_not_found'):
          return res.send(errorResponse(400, lan('login.user_not_found')));
        case lan('middleware.token_expire'):
          return res.send(errorResponse(400, lan('middleware.token_expire')));
        default:
          saveError(error, this.apiLogRepository);
          return res
            .status(500)
            .send(errorResponse(500, lan('common.internal_server_error')));
      }
    }
  }

  @Post('verify-email/:token')
  async userEmailVerification(
    @Param('token') token: string,
    @Res() res: ExpressResponse,
  ) {
    try {
      await this.appUsersService.verifyUserEmail(token);

      return res.send(successResponse(200, lan('login.email_verified')));
    } catch (error) {
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, lan('login.user_not_found')));
      } else if (error instanceof UnauthorizedException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(
            errorResponse(400, lan('change.password.invalid_old_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);
    }
  }

  @Post('send-verification-email')
  @UseGuards(AuthMiddleware)
  async verifyEmailByAdmin(
    @Body() inputEmail: any,
    @Res() res: ExpressResponse,
  ) {
    try {
      const { email } = inputEmail;

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

      await this.appUsersService.verifyUserEmailByAdmin(email);

      return res.send(
        successResponse(200, lan('registration.verification_email')),
      );
    } catch (error) {
      res
        .status(500)
        .send(errorResponse(500, lan('common.internal_server_error')));

      saveError(error, this.apiLogRepository);
    }
  }

  @Post('logout/:id')
  @UseGuards(AuthMiddleware)
  async logout(@Param('id') id: string, @Res() res: ExpressResponse) {
    try {
      await this.appUsersService.logout(id);

      res
        .status(HttpStatus.OK)
        .send(successResponse(200, lan('logout.success')));
    } catch (error) {
      if (error instanceof UnauthorizedException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(400, lan('login.user_not_found')));
      } else {
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }

      saveError(error, this.apiLogRepository);
    }
  }

  @Patch('update/:id')
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  @UseGuards(AuthMiddleware)
  @UseInterceptors(FileInterceptor('profile_picture', imageConfig))
  async updateUser(
    @Param('id') id: string,
    @UploadedFile() profile_picture,
    @Body() updateUserDto: UpdateAppUserDto,
    @Res() res: ExpressResponse,
  ) {
    const queryRunner = this.connection.createQueryRunner();

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

      if (profile_picture && profile_picture.size > limits.fileSize['image']) {
        return res.send(errorResponse(413, lan('common.limit_has_exceeded')));
      }

      if (profile_picture) {
        const attachmentUrl = `uploads/app-users/profile-picture/${profile_picture.filename}`;
        updateUserDto.profile_picture = attachmentUrl;
      }
      const updatedUser = await this.appUsersService.update(id, updateUserDto);

      res
        .status(HttpStatus.OK)
        .send(successResponse(200, updatedUser.message, updatedUser.data));
    } catch (error) {
      if (
        error instanceof NotFoundException ||
        error instanceof BadRequestException
      ) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, lan('login.user_not_found')));
      } else if (error instanceof UnauthorizedException) {
        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);
    } finally {
      await queryRunner.release();
    }
  }

  @Get('get/:id')
  @UseGuards(AuthMiddleware)
  async getUser(@Param('id') id: string, @Res() res: ExpressResponse) {
    try {
      const user = await this.appUsersService.findUser(id);
      if (user) {
        res
          .status(HttpStatus.OK)
          .send(
            successResponse(
              200,
              lan('common.data_retrieved_successfully'),
              user,
            ),
          );
      }
    } catch (error) {
      if (
        error instanceof NotFoundException ||
        error instanceof BadRequestException
      ) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, 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('get')
  @UseGuards(AuthMiddleware)
  async findAll(
    @Query('take') take: number,
    @Query('skip') skip: number,
    @Query('search') search: string,
    @Res() res: ExpressResponse,
  ) {
    try {
      const appUsers = await this.appUsersService.findAll(take, skip, search);

      if (appUsers) {
        res
          .status(HttpStatus.OK)
          .send(
            successResponse(
              200,
              lan('common.data_retrieved_successfully'),
              appUsers,
            ),
          );
      }
    } catch (error) {
      saveError(error, this.apiLogRepository);

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

  @Post('skip-login')
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async skipLogin(
    @Body() createAppUserDto: CreateAppUserDto,
    @Res() res: ExpressResponse,
    @Req() req: Request,
  ) {
    try {
      const fcmToken: any = req.headers['fcm_token'];

      const newUser = await this.appUsersService.skipLogin(
        createAppUserDto,
        fcmToken,
      );
      res.send(successResponse(201, lan('common.request_submitted'), newUser));
    } catch (error) {
      res
        .status(500)
        .send(errorResponse(500, lan('common.internal_server_error')));
    }
  }

  @Post(':userId/groups/:groupId')
  @UseGuards(AuthMiddleware)
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async associateUserWithGroup(
    @Param('userId') userId: string,
    @Param('groupId') groupId: string,
    @Res() res: ExpressResponse,
  ) {
    const queryRunner = this.connection.createQueryRunner();

    try {
      await queryRunner.connect();
      await queryRunner.startTransaction();
      await this.appUsersService.associateUserWithGroup(userId, groupId);
      await queryRunner.commitTransaction();
      res.send(successResponse(201, lan('common.request_submitted')));
    } catch (error) {
      await queryRunner.rollbackTransaction();
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, error.message));
      } 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);
    } finally {
      await queryRunner.release();
    }
  }

  @Post(':userId/remove-groups/:groupId')
  @UseGuards(AuthMiddleware)
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async removeUserFromGroup(
    @Param('userId') userId: string,
    @Param('groupId') groupId: string,
    @Res() res: ExpressResponse,
  ) {
    const queryRunner = this.connection.createQueryRunner();

    try {
      await queryRunner.connect();
      await queryRunner.startTransaction();
      await this.appUsersService.removeUserFromGroup(userId, groupId);

      res.send(successResponse(200, lan('common.request_submitted')));
    } catch (error) {
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, error.message));
      } 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);
    } finally {
      await queryRunner.release();
    }
  }

  @Get(':userId/groups')
  @UseGuards(AuthMiddleware)
  async getUserGroups(
    @Param('userId') userId: string,
    @Query('search') search: string,
    @Res() res: ExpressResponse,
  ) {
    try {
      const userGroup = await this.appUsersService.getUserGroups(
        userId,
        search,
      );

      if (userGroup) {
        res
          .status(HttpStatus.OK)
          .send(
            successResponse(
              200,
              lan('common.data_retrieved_successfully'),
              userGroup,
            ),
          );
      }
    } catch (error) {
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, error.message));
      } else {
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }

      saveError(error, this.apiLogRepository);
    }
  }

  @Get(':userId/explore-groups')
  @UseGuards(AuthMiddleware)
  async getExploreGroups(
    @Param('userId') userId: string,
    @Query('search') search: string,
    @Res() res: ExpressResponse,
  ) {
    try {
      const userGroup = await this.appUsersService.getExploreGroups(
        userId,
        search,
      );

      if (userGroup) {
        res
          .status(HttpStatus.OK)
          .send(
            successResponse(
              200,
              lan('common.data_retrieved_successfully'),
              userGroup,
            ),
          );
      }
    } catch (error) {
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, error.message));
      } else {
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }

      saveError(error, this.apiLogRepository);
    }
  }

  @Get('get-goals')
  @UseGuards(AuthMiddleware)
  async getStepGoalOfUser(@Res() res: ExpressResponse) {
    try {
      const userGroup = await this.appUsersService.getStepGoals();

      if (userGroup) {
        res
          .status(HttpStatus.OK)
          .send(
            successResponse(
              200,
              lan('common.data_retrieved_successfully'),
              userGroup,
            ),
          );
      }
    } catch (error) {
      if (error instanceof NotFoundException) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, error.message));
      } else {
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }

      saveError(error, this.apiLogRepository);
    }
  }

  @Patch('update-goals')
  @UseGuards(AuthMiddleware)
  async updateUserGoalCount(
    @Body() updateGoalsData: { count: string },
    @Res() res: ExpressResponse,
  ) {
    const { count } = updateGoalsData;
    const queryRunner = this.connection.createQueryRunner();

    try {
      await queryRunner.connect();
      await queryRunner.startTransaction();
      await this.appUsersService.updateUserGoals(count);

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

      if (
        error instanceof NotFoundException ||
        error instanceof BadRequestException
      ) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, error.message));
      } else {
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }
    } finally {
      await queryRunner.release();
    }
  }

  @Delete(':id')
  @UseGuards(AuthMiddleware)
  async remove(@Param('id') id: string, @Res() res: ExpressResponse) {
    try {
      await this.appUsersService.deleteUser(id);

      res
        .status(HttpStatus.OK)
        .send(successResponse(200, lan('user.delete_account')));
    } catch (error) {
      if (
        error instanceof NotFoundException ||
        error instanceof BadRequestException
      ) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, error.message));
      } else {
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error'), error));
      }

      saveError(error, this.apiLogRepository);
    }
  }

  @Patch(':id/step-tracking-permission')
  @UseGuards(AuthMiddleware)
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: validationErrorMessage,
      transform: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  )
  async updateStepTrackingPermission(
    @Param('id') userId: string,
    @Body('is_step_tracking_enabled') isStepTrackingEnabled: boolean,
    @Res() res: ExpressResponse,
  ) {
    try {
      await this.appUsersService.updateStepTrackingPermission(
        userId,
        isStepTrackingEnabled,
      );

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

      if (
        error instanceof NotFoundException ||
        error instanceof BadRequestException
      ) {
        res
          .status(HttpStatus.BAD_REQUEST)
          .send(errorResponse(404, error.message));
      } else {
        res
          .status(HttpStatus.INTERNAL_SERVER_ERROR)
          .send(errorResponse(500, lan('common.internal_server_error')));
      }
    }
  }
}
