import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Param,
  ParseIntPipe,
  Patch,
  Post,
  Req,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { memoryStorage } from 'multer';
import {
  ApiBearerAuth,
  ApiBody,
  ApiConsumes,
  ApiOperation,
  ApiResponse,
  ApiTags,
} from '@nestjs/swagger';
import type { Request } from 'express';

import { Action } from '@/casl/action.enum';
import { AppAbility } from '@/casl/casl-ability.factory';
import { CheckPolicies } from '@/casl/check-policies.decorator';
import { PoliciesGuard } from '@/casl/policies.guard';
import { JwtAuthGuard } from '../../auth/jwt-auth.guard';
import { UsersService } from './users.service';
import { UpdateUserDto } from './dto/update-user.dto';
import { ChangePasswordDto } from './dto/change-password.dto';
import { RequestEmailChangeDto } from './dto/request-email-change.dto';
import { VerifyEmailChangeDto } from './dto/verify-email-change.dto';
import { CreateUserDto } from './dto/create-user.dto';
import { AssignRolesDto } from './dto/assign-roles.dto';
import { AssignPermissionsDto } from './dto/assign-permissions.dto';

// CASL subject the admin endpoints are authorized against.
const SUBJECT = 'User';

@ApiTags('users')
@ApiBearerAuth('access-token')
@UseGuards(JwtAuthGuard, PoliciesGuard)
@Controller('users')
export class UsersController {
  constructor(private readonly users: UsersService) {}

  @Get()
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'Get all active users' })
  @ApiResponse({ status: 200, description: 'List of all active users' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  getAllUsers() {
    return this.users.getAllUsers();
  }

  @Post()
  @HttpCode(HttpStatus.CREATED)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Create, SUBJECT))
  @UseInterceptors(
    FileInterceptor('avatarFile', {
      storage: memoryStorage(),
      limits: {
        fileSize: 5 * 1024 * 1024,
      },
    }),
  )
  @ApiConsumes('multipart/form-data', 'application/json')
  @ApiBody({ type: CreateUserDto })
  @ApiOperation({ summary: 'Create a user with profile image admin only' })
  @ApiResponse({ status: 201, description: 'User created successfully' })
  @ApiResponse({ status: 400, description: 'Validation failed' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 409, description: 'Email already in use' })
  createUser(
    @Body() dto: CreateUserDto,
    @UploadedFile() avatarFile?: Express.Multer.File,
  ) {
    return this.users.createUser(dto, avatarFile);
  }

  @Get('me')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Get the authenticated user profile' })
  @ApiResponse({ status: 200, description: 'The authenticated user profile' })
  @ApiResponse({ status: 401, description: 'Missing or invalid token' })
  @ApiResponse({ status: 404, description: 'User not found' })
  getProfile(@Req() req: Request) {
    const user = req.user as { id: number };

    return this.users.getProfile(user.id);
  }

  @Patch('me')
  @HttpCode(HttpStatus.OK)
  @UseInterceptors(
    FileInterceptor('avatarFile', {
      storage: memoryStorage(),
      limits: {
        fileSize: 5 * 1024 * 1024,
      },
    }),
  )
  @ApiConsumes('multipart/form-data', 'application/json')
  @ApiBody({ type: UpdateUserDto })
  @ApiOperation({
    summary:
      'Update authenticated user name, position, avatar URL, or uploaded avatar file',
  })
  @ApiResponse({ status: 200, description: 'User updated successfully' })
  @ApiResponse({
    status: 400,
    description: 'Validation failed or unsupported file type',
  })
  @ApiResponse({ status: 401, description: 'Missing or invalid token' })
  updateUser(
    @Req() req: Request,
    @Body() dto: UpdateUserDto,
    @UploadedFile() avatarFile?: Express.Multer.File,
  ) {
    const user = req.user as { id: number };

    return this.users.updateUser(user.id, dto, avatarFile);
  }

  @Patch('me/password')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Change authenticated user password' })
  @ApiResponse({ status: 200, description: 'Password changed successfully' })
  @ApiResponse({ status: 400, description: 'Validation failed' })
  @ApiResponse({ status: 401, description: 'Current password is incorrect' })
  changePassword(@Req() req: Request, @Body() dto: ChangePasswordDto) {
    const user = req.user as { id: number };

    return this.users.changePassword(user.id, dto);
  }

  @Post('me/email-change')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary:
      'Request an email change. Sends a verification OTP to the new email.',
  })
  @ApiResponse({ status: 200, description: 'OTP sent to the new email' })
  @ApiResponse({ status: 400, description: 'Validation failed' })
  @ApiResponse({ status: 401, description: 'Current password is incorrect' })
  @ApiResponse({ status: 409, description: 'Email already in use' })
  requestEmailChange(@Req() req: Request, @Body() dto: RequestEmailChangeDto) {
    const user = req.user as { id: number };

    return this.users.requestEmailChange(user.id, dto);
  }

  @Post('me/email-change/verify')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Confirm email change by submitting the OTP from the new email',
  })
  @ApiResponse({ status: 200, description: 'Email changed successfully' })
  @ApiResponse({ status: 400, description: 'Invalid or expired OTP' })
  @ApiResponse({ status: 409, description: 'Email is no longer available' })
  verifyEmailChange(@Req() req: Request, @Body() dto: VerifyEmailChangeDto) {
    const user = req.user as { id: number };

    return this.users.verifyEmailChange(user.id, dto);
  }

  @Get(':id/login-trails')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'Get user login trails admin only' })
  @ApiResponse({ status: 200, description: 'User login trails' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'User not found' })
  getUserLoginTrails(@Param('id', ParseIntPipe) id: number) {
    return this.users.getUserLoginTrails(id);
  }

  @Get(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Read, SUBJECT))
  @ApiOperation({ summary: 'Get a user by ID admin only' })
  @ApiResponse({ status: 200, description: 'The user' })
  @ApiResponse({ status: 401, description: 'Missing or invalid token' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'User not found' })
  getUserById(@Param('id', ParseIntPipe) id: number) {
    return this.users.getUserById(id);
  }

  @Patch(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @UseInterceptors(
    FileInterceptor('avatarFile', {
      storage: memoryStorage(),
      limits: {
        fileSize: 5 * 1024 * 1024,
      },
    }),
  )
  @ApiConsumes('multipart/form-data', 'application/json')
  @ApiBody({ type: UpdateUserDto })
  @ApiOperation({
    summary:
      'Admin update user profile image, name, email, password, position, and status',
  })
  @ApiResponse({ status: 200, description: 'User updated successfully' })
  @ApiResponse({ status: 400, description: 'Validation failed' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'User not found' })
  @ApiResponse({ status: 409, description: 'Email already in use' })
  adminUpdateUser(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: UpdateUserDto,
    @UploadedFile() avatarFile?: Express.Multer.File,
  ) {
    return this.users.adminUpdateUser(id, dto, avatarFile);
  }

  @Delete(':id')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Delete, SUBJECT))
  @ApiOperation({ summary: 'Soft delete a user admin only' })
  @ApiResponse({ status: 200, description: 'User deleted successfully' })
  @ApiResponse({ status: 400, description: 'Cannot delete your own account' })
  @ApiResponse({ status: 401, description: 'Missing or invalid token' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'User not found' })
  deleteUser(@Param('id', ParseIntPipe) id: number, @Req() req: Request) {
    const user = req.user as { id: number };

    return this.users.deleteUser(id, user.id);
  }

  @Post(':id/roles')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Assign roles to a user' })
  @ApiResponse({ status: 200, description: 'Roles assigned; returns grants' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'User or role not found' })
  assignRoles(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: AssignRolesDto,
  ) {
    return this.users.assignRoles(id, dto);
  }

  @Delete(':id/roles/:roleId')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Remove a role from a user' })
  @ApiResponse({ status: 200, description: 'Role removed' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'User or assignment not found' })
  removeRole(
    @Param('id', ParseIntPipe) id: number,
    @Param('roleId', ParseIntPipe) roleId: number,
  ) {
    return this.users.removeRole(id, roleId);
  }

  @Post(':id/permissions')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Grant permissions directly to a user' })
  @ApiResponse({
    status: 200,
    description: 'Permissions granted; returns grants',
  })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'User or permission not found' })
  assignPermissions(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: AssignPermissionsDto,
  ) {
    return this.users.assignPermissions(id, dto);
  }

  @Delete(':id/permissions/:permissionId')
  @HttpCode(HttpStatus.OK)
  @CheckPolicies((ability: AppAbility) => ability.can(Action.Update, SUBJECT))
  @ApiOperation({ summary: 'Remove a direct permission from a user' })
  @ApiResponse({ status: 200, description: 'Permission removed' })
  @ApiResponse({ status: 403, description: 'Insufficient permissions' })
  @ApiResponse({ status: 404, description: 'User or assignment not found' })
  removePermission(
    @Param('id', ParseIntPipe) id: number,
    @Param('permissionId', ParseIntPipe) permissionId: number,
  ) {
    return this.users.removePermission(id, permissionId);
  }
}
