import {
  Controller,
  Get,
  Post,
  Patch,
  Delete,
  Param,
  Body,
  Query,
  HttpCode,
  HttpStatus,
  Logger,
  UsePipes,
  ValidationPipe,
  NotFoundException,
  BadRequestException,
  ParseUUIDPipe,
  ParseIntPipe,
  DefaultValuePipe,
  UseGuards,
  ForbiddenException,
  ConflictException,
  Inject,
} from '@nestjs/common';
import {
  ApiTags,
  ApiOperation,
  ApiResponse,
  ApiBadRequestResponse,
  ApiNotFoundResponse,
  ApiQuery,
  ApiParam,
  ApiBearerAuth,
} from '@nestjs/swagger';
import {
  CreateUserDto,
  UpdateUserDto,
  UpdatePasswordDto,
  UserResponseDto,
  UserListResponseDto,
} from '../dto/user.dto';
import { UserMapper } from '../mappers/user.mapper';
import { UserRepository, USER_REPOSITORY } from '@domain/ports/out/user.repository';
import { User, UserRole as DomainUserRole } from '@domain/entities/user.entity';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { RolesGuard } from '../guards/roles.guard';
import { FeatureFlagGuard } from '../guards/feature-flag.guard';
import { CurrentUser, UserPayload } from '../decorators/current-user.decorator';
import { Roles } from '../decorators/roles.decorator';
import { RequiresFeature } from '../decorators/requires-feature.decorator';
import { v4 as uuidv4 } from 'uuid';
import * as argon2 from 'argon2';
import * as crypto from 'crypto';
import { SubscriptionService } from '../services/subscription.service';

/**
 * Users Controller
 *
 * Manages user CRUD operations:
 * - Create user / Invite user (admin/manager)
 * - Get user details
 * - Update user (admin/manager)
 * - Delete/deactivate user (admin)
 * - List users in organization
 * - Update own password
 */
@ApiTags('Users')
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard, FeatureFlagGuard)
@RequiresFeature('user_management')
@ApiBearerAuth()
export class UsersController {
  private readonly logger = new Logger(UsersController.name);

  constructor(
    @Inject(USER_REPOSITORY) private readonly userRepository: UserRepository,
    private readonly subscriptionService: SubscriptionService
  ) {}

  /**
   * Create/Invite a new user
   *
   * Admin can create users in any organization.
   * Manager can only create users in their own organization.
   */
  @Post()
  @HttpCode(HttpStatus.CREATED)
  @Roles('admin', 'manager')
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({
    summary: 'Create/Invite new user',
    description:
      'Create a new user account. Admin can create in any org, manager only in their own.',
  })
  @ApiResponse({
    status: HttpStatus.CREATED,
    description: 'User created successfully',
    type: UserResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - missing or invalid token',
  })
  @ApiResponse({
    status: 403,
    description: 'Forbidden - requires admin or manager role',
  })
  @ApiBadRequestResponse({
    description: 'Invalid request parameters',
  })
  async createUser(
    @Body() dto: CreateUserDto,
    @CurrentUser() user: UserPayload
  ): Promise<UserResponseDto> {
    this.logger.log(`[User: ${user.email}] Creating user: ${dto.email} (${dto.role})`);

    // Authorization: Only ADMIN can assign ADMIN role
    if (dto.role === 'ADMIN' && user.role !== 'ADMIN') {
      throw new ForbiddenException('Only platform administrators can create users with ADMIN role');
    }

    // Authorization: Managers can only create users in their own organization
    if (user.role === 'MANAGER' && dto.organizationId !== user.organizationId) {
      throw new ForbiddenException('You can only create users in your own organization');
    }

    // Check if user already exists
    const existingUser = await this.userRepository.findByEmail(dto.email);
    if (existingUser) {
      throw new ConflictException('User with this email already exists');
    }

    // Generate temporary password if not provided
    const tempPassword = dto.password || this.generateTemporaryPassword();

    // Hash password with Argon2id
    const passwordHash = await argon2.hash(tempPassword, {
      type: argon2.argon2id,
      memoryCost: 65536, // 64 MB
      timeCost: 3,
      parallelism: 4,
    });

    // Map DTO role to Domain role
    const domainRole = dto.role as unknown as DomainUserRole;

    // Create user entity
    const newUser = User.create({
      id: uuidv4(),
      organizationId: dto.organizationId,
      email: dto.email,
      passwordHash,
      firstName: dto.firstName,
      lastName: dto.lastName,
      role: domainRole,
    });

    // Save to database
    const savedUser = await this.userRepository.save(newUser);

    this.logger.log(`User created successfully: ${savedUser.id}`);

    // TODO: Send invitation email with temporary password
    this.logger.warn(
      `TODO: Send invitation email to ${dto.email} with temp password: ${tempPassword}`
    );

    return UserMapper.toDto(savedUser);
  }

  /**
   * Get user by ID
   */
  @Get(':id')
  @Roles('admin', 'manager')
  @ApiOperation({
    summary: 'Get user by ID',
    description: 'Retrieve user details. Only ADMIN and MANAGER can access this endpoint.',
  })
  @ApiParam({
    name: 'id',
    description: 'User ID (UUID)',
    example: '550e8400-e29b-41d4-a716-446655440000',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'User details retrieved successfully',
    type: UserResponseDto,
  })
  @ApiNotFoundResponse({
    description: 'User not found',
  })
  async getUser(
    @Param('id', ParseUUIDPipe) id: string,
    @CurrentUser() currentUser: UserPayload
  ): Promise<UserResponseDto> {
    this.logger.log(`[User: ${currentUser.email}] Fetching user: ${id}`);

    const user = await this.userRepository.findById(id);
    if (!user) {
      throw new NotFoundException(`User ${id} not found`);
    }

    // Authorization: Can only view users in same organization (unless admin)
    if (currentUser.role !== 'ADMIN' && user.organizationId !== currentUser.organizationId) {
      throw new ForbiddenException('You can only view users in your organization');
    }

    return UserMapper.toDto(user);
  }

  /**
   * Update user
   */
  @Patch(':id')
  @Roles('admin', 'manager')
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({
    summary: 'Update user',
    description: 'Update user details (name, role, status). Admin/manager only.',
  })
  @ApiParam({
    name: 'id',
    description: 'User ID (UUID)',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'User updated successfully',
    type: UserResponseDto,
  })
  @ApiResponse({
    status: 403,
    description: 'Forbidden - requires admin or manager role',
  })
  @ApiNotFoundResponse({
    description: 'User not found',
  })
  async updateUser(
    @Param('id', ParseUUIDPipe) id: string,
    @Body() dto: UpdateUserDto,
    @CurrentUser() currentUser: UserPayload
  ): Promise<UserResponseDto> {
    this.logger.log(`[User: ${currentUser.email}] Updating user: ${id}`);

    const user = await this.userRepository.findById(id);
    if (!user) {
      throw new NotFoundException(`User ${id} not found`);
    }

    // Security: Prevent users from changing their own role
    if (dto.role && id === currentUser.id) {
      this.logger.warn(`[SECURITY] User ${currentUser.email} attempted to change their own role`);
      throw new BadRequestException('You cannot change your own role');
    }

    // Authorization: Only ADMIN can assign ADMIN role
    if (dto.role === 'ADMIN' && currentUser.role !== 'ADMIN') {
      throw new ForbiddenException('Only platform administrators can assign ADMIN role');
    }

    // Authorization: Managers can only update users in their own organization
    if (currentUser.role === 'MANAGER' && user.organizationId !== currentUser.organizationId) {
      throw new ForbiddenException('You can only update users in your own organization');
    }

    // Update fields
    if (dto.firstName) {
      user.updateFirstName(dto.firstName);
    }

    if (dto.lastName) {
      user.updateLastName(dto.lastName);
    }

    if (dto.role) {
      const domainRole = dto.role as unknown as DomainUserRole;
      user.updateRole(domainRole);
    }

    if (dto.isActive !== undefined) {
      if (dto.isActive) {
        user.activate();
        // Reallocate license if reactivating user
        try {
          await this.subscriptionService.allocateLicense(id, user.organizationId);
          this.logger.log(`License reallocated for reactivated user: ${id}`);
        } catch (error) {
          this.logger.error(`Failed to reallocate license for user ${id}:`, error);
          throw new ForbiddenException(
            'Cannot reactivate user: no licenses available. Please upgrade your subscription.'
          );
        }
      } else {
        user.deactivate();
        // Revoke license when deactivating user
        await this.subscriptionService.revokeLicense(id);
        this.logger.log(`License revoked for deactivated user: ${id}`);
      }
    }

    // Save updated user
    const updatedUser = await this.userRepository.save(user);

    this.logger.log(`User updated successfully: ${updatedUser.id}`);

    return UserMapper.toDto(updatedUser);
  }

  /**
   * Delete/deactivate user
   */
  @Delete(':id')
  @Roles('admin')
  @ApiOperation({
    summary: 'Delete user',
    description: 'Deactivate a user account. Admin only.',
  })
  @ApiParam({
    name: 'id',
    description: 'User ID (UUID)',
  })
  @ApiResponse({
    status: HttpStatus.NO_CONTENT,
    description: 'User deactivated successfully',
  })
  @ApiResponse({
    status: 403,
    description: 'Forbidden - requires admin role',
  })
  @ApiNotFoundResponse({
    description: 'User not found',
  })
  async deleteUser(
    @Param('id', ParseUUIDPipe) id: string,
    @CurrentUser() currentUser: UserPayload
  ): Promise<void> {
    this.logger.log(`[Admin: ${currentUser.email}] Deleting user: ${id}`);

    const user = await this.userRepository.findById(id);
    if (!user) {
      throw new NotFoundException(`User ${id} not found`);
    }

    // Revoke license before deleting user
    await this.subscriptionService.revokeLicense(id);
    this.logger.log(`License revoked for user being deleted: ${id}`);

    // Permanently delete user from database
    await this.userRepository.deleteById(id);

    this.logger.log(`User deleted successfully: ${id}`);
  }

  /**
   * List users in organization
   */
  @Get()
  @Roles('admin', 'manager')
  @ApiOperation({
    summary: 'List users',
    description:
      'Retrieve a paginated list of users in your organization. Only ADMIN and MANAGER can access this endpoint.',
  })
  @ApiQuery({
    name: 'page',
    required: false,
    description: 'Page number (1-based)',
    example: 1,
  })
  @ApiQuery({
    name: 'pageSize',
    required: false,
    description: 'Number of items per page',
    example: 20,
  })
  @ApiQuery({
    name: 'role',
    required: false,
    description: 'Filter by role',
    enum: ['admin', 'manager', 'user', 'viewer'],
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'Users list retrieved successfully',
    type: UserListResponseDto,
  })
  async listUsers(
    @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
    @Query('pageSize', new DefaultValuePipe(20), ParseIntPipe) pageSize: number,
    @Query('role') role: string | undefined,
    @CurrentUser() currentUser: UserPayload
  ): Promise<UserListResponseDto> {
    this.logger.log(
      `[User: ${currentUser.email}] Listing users: page=${page}, pageSize=${pageSize}, role=${role}`
    );

    // Fetch users from current user's organization
    this.logger.log(
      `[User: ${currentUser.email}] Fetching users from organization: ${currentUser.organizationId}`
    );
    let users = await this.userRepository.findByOrganization(currentUser.organizationId);

    // Security: Non-admin users cannot see ADMIN users
    if (currentUser.role !== 'ADMIN') {
      users = users.filter(u => u.role !== DomainUserRole.ADMIN);
      this.logger.log(`[SECURITY] Non-admin user ${currentUser.email} - filtered out ADMIN users`);
    } else {
      this.logger.log(
        `[ADMIN] User ${currentUser.email} can see all users including ADMINs in their organization`
      );
    }

    // Filter by role if provided
    const filteredUsers = role ? users.filter(u => u.role === role) : users;

    // Paginate
    const startIndex = (page - 1) * pageSize;
    const endIndex = startIndex + pageSize;
    const paginatedUsers = filteredUsers.slice(startIndex, endIndex);

    // Convert to DTOs
    const userDtos = UserMapper.toDtoArray(paginatedUsers);

    const totalPages = Math.ceil(filteredUsers.length / pageSize);

    return {
      users: userDtos,
      total: filteredUsers.length,
      page,
      pageSize,
      totalPages,
    };
  }

  /**
   * Update own password
   */
  @Patch('me/password')
  @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
  @ApiOperation({
    summary: 'Update own password',
    description: 'Update your own password. Requires current password.',
  })
  @ApiResponse({
    status: HttpStatus.OK,
    description: 'Password updated successfully',
    schema: {
      properties: {
        message: { type: 'string', example: 'Password updated successfully' },
      },
    },
  })
  @ApiBadRequestResponse({
    description: 'Invalid current password',
  })
  async updatePassword(
    @Body() dto: UpdatePasswordDto,
    @CurrentUser() currentUser: UserPayload
  ): Promise<{ message: string }> {
    this.logger.log(`[User: ${currentUser.email}] Updating password`);

    const user = await this.userRepository.findById(currentUser.id);
    if (!user) {
      throw new NotFoundException('User not found');
    }

    // Verify current password
    const isPasswordValid = await argon2.verify(user.passwordHash, dto.currentPassword);

    if (!isPasswordValid) {
      throw new ForbiddenException('Current password is incorrect');
    }

    // Hash new password
    const newPasswordHash = await argon2.hash(dto.newPassword, {
      type: argon2.argon2id,
      memoryCost: 65536,
      timeCost: 3,
      parallelism: 4,
    });

    // Update password
    user.updatePassword(newPasswordHash);
    await this.userRepository.save(user);

    this.logger.log(`Password updated successfully for user: ${user.id}`);

    return { message: 'Password updated successfully' };
  }

  /**
   * Generate a secure temporary password
   */
  private generateTemporaryPassword(): string {
    const length = 16;
    const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
    let password = '';

    const randomBytes = crypto.randomBytes(length);
    for (let i = 0; i < length; i++) {
      password += charset[randomBytes[i] % charset.length];
    }

    return password;
  }
}
