import {
  Body,
  Controller,
  Delete,
  ForbiddenException,
  Get,
  Param,
  ParseIntPipe,
  Post,
  Put,
  Query,
  UseGuards,
} from '@nestjs/common';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { PermissionsGuard } from '../auth/guards/permissions.guard';
import { Permissions } from '../auth/permissions.decorator';
import { AuditAction } from '../common/decorators/audit.decorator';
import { CurrentUser, AuthenticatedUser } from '../common/decorators/current-user.decorator';
import { UsersService } from './users.service';
import { AuthService } from '../auth/auth.service';
import {
  AssignRolesDto,
  CreateUserDto,
  SetPasswordDto,
  UpdateUserDto,
} from './dto/user.dto';

@Controller('api/v1/admin/users')
@UseGuards(JwtAuthGuard, PermissionsGuard)
export class UsersController {
  constructor(
    private readonly usersService: UsersService,
    private readonly authService: AuthService,
  ) {}

  @Get()
  @Permissions('user.view')
  async findAll(
    @Query('search') search?: string,
    @Query('isActive') isActive?: string,
    @Query('page') page?: string,
    @Query('limit') limit?: string,
  ) {
    const { items, meta } = await this.usersService.findAll({
      search,
      isActive: isActive === undefined ? undefined : isActive === 'true',
      page: page ? Number(page) : undefined,
      limit: limit ? Number(limit) : undefined,
    });
    return { success: true, message: 'Success', data: items, meta };
  }

  @Get(':id')
  @Permissions('user.view')
  async findById(@Param('id', ParseIntPipe) id: number) {
    const user = await this.usersService.findById(id);
    if (!user) return { success: false, message: 'User not found' };
    return { success: true, message: 'Success', data: UsersService.toPublic(user) };
  }

  @Post()
  @Permissions('user.create')
  async create(@Body() dto: CreateUserDto) {
    const passwordHash = await this.authService.hashPassword(dto.password);
    const user = await this.usersService.create({
      email: dto.email,
      passwordHash,
      displayName: dto.displayName,
      roleIds: dto.roleIds,
      isActive: dto.isActive,
    });
    const created = await this.usersService.findById(user.id);
    return { success: true, message: 'User created', data: UsersService.toPublic(created!) };
  }

  @Put(':id')
  @Permissions('user.update')
  async update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateUserDto) {
    await this.usersService.update(id, dto);
    const user = await this.usersService.findById(id);
    return { success: true, message: 'User updated', data: UsersService.toPublic(user!) };
  }

  @Post(':id/roles')
  @Permissions('user.update')
  @AuditAction('USER_ROLES_ASSIGNED')
  async assignRoles(
    @Param('id', ParseIntPipe) id: number,
    @Body() dto: AssignRolesDto,
    @CurrentUser() actor: AuthenticatedUser,
  ) {
    // Prevent an administrator from silently escalating or stripping their own access.
    if (Number(actor.id) === id) {
      throw new ForbiddenException('You cannot change your own role assignments');
    }
    await this.usersService.assignRoles(id, dto.roleIds);
    const user = await this.usersService.findById(id);
    return { success: true, message: 'Roles assigned', data: UsersService.toPublic(user!) };
  }

  @Post(':id/password')
  @Permissions('user.update')
  @AuditAction('USER_PASSWORD_RESET')
  async setPassword(@Param('id', ParseIntPipe) id: number, @Body() dto: SetPasswordDto) {
    const hash = await this.authService.hashPassword(dto.password);
    await this.usersService.setPassword(id, hash);
    await this.authService.logout(undefined, id);
    return { success: true, message: 'Password updated' };
  }

  @Post(':id/activate')
  @Permissions('user.update')
  async activate(@Param('id', ParseIntPipe) id: number) {
    const user = await this.usersService.setActive(id, true);
    return { success: true, message: 'User activated', data: UsersService.toPublic(user) };
  }

  @Post(':id/deactivate')
  @Permissions('user.update')
  async deactivate(
    @Param('id', ParseIntPipe) id: number,
    @CurrentUser() actor: AuthenticatedUser,
  ) {
    if (Number(actor.id) === id) {
      throw new ForbiddenException('You cannot deactivate your own account');
    }
    const user = await this.usersService.setActive(id, false);
    await this.authService.logout(undefined, id);
    return { success: true, message: 'User deactivated', data: UsersService.toPublic(user) };
  }

  @Delete(':id')
  @Permissions('user.delete')
  async remove(
    @Param('id', ParseIntPipe) id: number,
    @CurrentUser() actor: AuthenticatedUser,
  ) {
    if (Number(actor.id) === id) {
      throw new ForbiddenException('You cannot delete your own account');
    }
    const success = await this.usersService.deactivate(id);
    await this.authService.logout(undefined, id);
    return { success, message: success ? 'User deactivated' : 'User not found' };
  }
}
