/*
 * Copyright 2025 Daytona Platforms Inc.
 * SPDX-License-Identifier: AGPL-3.0
 */

import { Controller, Post, Get, Delete, Param, Body, UseGuards, ForbiddenException, HttpCode } from '@nestjs/common'
import { ApiKeyService } from './api-key.service'
import { CreateApiKeyDto } from './dto/create-api-key.dto'
import { ApiHeader, ApiOAuth2, ApiOperation, ApiResponse, ApiTags, ApiBearerAuth } from '@nestjs/swagger'
import { ApiKeyResponseDto } from './dto/api-key-response.dto'
import { ApiKeyListDto } from './dto/api-key-list.dto'
import { CustomHeaders } from '../common/constants/header.constants'
import { IsOrganizationAuthContext } from '../common/decorators/auth-context.decorator'
import { OrganizationAuthContext } from '../common/interfaces/organization-auth-context.interface'
import { OrganizationMemberRole } from '../organization/enums/organization-member-role.enum'
import { OrganizationResourcePermission } from '../organization/enums/organization-resource-permission.enum'
import { OrganizationAuthContextGuard } from '../organization/guards/organization-auth-context.guard'
import { Audit, TypedRequest } from '../audit/decorators/audit.decorator'
import { AuditAction } from '../audit/enums/audit-action.enum'
import { AuditTarget } from '../audit/enums/audit-target.enum'
import { ApiKey } from './api-key.entity'
import { AuthenticatedRateLimitGuard } from '../common/guards/authenticated-rate-limit.guard'
import { AuthStrategy } from '../auth/decorators/auth-strategy.decorator'
import { AuthStrategyType } from '../auth/enums/auth-strategy-type.enum'

@Controller('api-keys')
@ApiTags('api-keys')
@ApiOAuth2(['openid', 'profile', 'email'])
@ApiBearerAuth()
@ApiHeader(CustomHeaders.ORGANIZATION_ID)
@AuthStrategy(AuthStrategyType.JWT)
@UseGuards(AuthenticatedRateLimitGuard)
@UseGuards(OrganizationAuthContextGuard)
export class ApiKeyController {
  constructor(private readonly apiKeyService: ApiKeyService) {}

  @Post()
  @ApiOperation({
    summary: 'Create API key',
    operationId: 'createApiKey',
  })
  @ApiResponse({
    status: 201,
    description: 'API key created successfully.',
    type: ApiKeyResponseDto,
  })
  @Audit({
    action: AuditAction.CREATE,
    targetType: AuditTarget.API_KEY,
    targetIdFromResult: (result: ApiKeyResponseDto) => result?.name,
    requestMetadata: {
      body: (req: TypedRequest<CreateApiKeyDto>) => ({
        name: req.body?.name,
        permissions: req.body?.permissions,
        expiresAt: req.body?.expiresAt,
      }),
    },
  })
  async createApiKey(
    @IsOrganizationAuthContext() authContext: OrganizationAuthContext,
    @Body() createApiKeyDto: CreateApiKeyDto,
  ): Promise<ApiKeyResponseDto> {
    this.validateRequestedApiKeyPermissions(authContext, createApiKeyDto.permissions)

    const { apiKey, value } = await this.apiKeyService.createApiKey(
      authContext.organizationId,
      authContext.userId,
      createApiKeyDto.name,
      createApiKeyDto.permissions,
      createApiKeyDto.expiresAt,
    )

    return ApiKeyResponseDto.fromApiKey(apiKey, value)
  }

  @Get()
  @ApiOperation({
    summary: 'List API keys',
    operationId: 'listApiKeys',
  })
  @ApiResponse({
    status: 200,
    description: 'API keys retrieved successfully.',
    type: [ApiKeyListDto],
  })
  @ApiResponse({ status: 500, description: 'Error fetching API keys.' })
  async getApiKeys(@IsOrganizationAuthContext() authContext: OrganizationAuthContext): Promise<ApiKeyListDto[]> {
    let apiKeys: ApiKey[] = []

    if (authContext.organizationUser.role === OrganizationMemberRole.OWNER) {
      apiKeys = await this.apiKeyService.getApiKeys(authContext.organizationId)
    } else {
      apiKeys = await this.apiKeyService.getApiKeys(authContext.organizationId, authContext.userId)
    }

    return apiKeys.map((apiKey) => ApiKeyListDto.fromApiKey(apiKey))
  }

  @Get('current')
  @ApiOperation({
    summary: "Get current API key's details",
    operationId: 'getCurrentApiKey',
  })
  @ApiResponse({
    status: 200,
    description: 'API key retrieved successfully.',
    type: ApiKeyListDto,
  })
  @AuthStrategy(AuthStrategyType.API_KEY)
  async getCurrentApiKey(@IsOrganizationAuthContext() authContext: OrganizationAuthContext): Promise<ApiKeyListDto> {
    // Should not happen due to AuthStrategy guard, but needed for type safety
    if (!authContext.apiKey) {
      throw new ForbiddenException('Authenticate with an API key to use this endpoint')
    }

    return ApiKeyListDto.fromApiKey(authContext.apiKey)
  }

  @Get(':name')
  @ApiOperation({
    summary: 'Get API key',
    operationId: 'getApiKey',
  })
  @ApiResponse({
    status: 200,
    description: 'API key retrieved successfully.',
    type: ApiKeyListDto,
  })
  async getApiKey(
    @IsOrganizationAuthContext() authContext: OrganizationAuthContext,
    @Param('name') name: string,
  ): Promise<ApiKeyListDto> {
    const apiKey = await this.apiKeyService.getApiKeyByName(authContext.organizationId, authContext.userId, name)
    return ApiKeyListDto.fromApiKey(apiKey)
  }

  @Delete(':name')
  @ApiOperation({
    summary: 'Delete API key',
    operationId: 'deleteApiKey',
  })
  @ApiResponse({ status: 204, description: 'API key deleted successfully.' })
  @HttpCode(204)
  @Audit({
    action: AuditAction.DELETE,
    targetType: AuditTarget.API_KEY,
    targetIdFromRequest: (req) => req.params.name,
  })
  async deleteApiKey(@IsOrganizationAuthContext() authContext: OrganizationAuthContext, @Param('name') name: string) {
    await this.apiKeyService.deleteApiKey(authContext.organizationId, authContext.userId, name)
  }

  @Delete(':userId/:name')
  @ApiOperation({
    summary: 'Delete API key for user',
    operationId: 'deleteApiKeyForUser',
  })
  @ApiResponse({ status: 204, description: 'API key deleted successfully.' })
  @HttpCode(204)
  @Audit({
    action: AuditAction.DELETE,
    targetType: AuditTarget.API_KEY,
    targetIdFromRequest: (req) => req.params.name,
    requestMetadata: {
      params: (req) => ({
        userId: req.params.userId,
      }),
    },
  })
  async deleteApiKeyForUser(
    @IsOrganizationAuthContext() authContext: OrganizationAuthContext,
    @Param('userId') userId: string,
    @Param('name') name: string,
  ) {
    if (userId !== authContext.userId && authContext.organizationUser.role !== OrganizationMemberRole.OWNER) {
      throw new ForbiddenException('Incorrect user ID provided')
    }

    await this.apiKeyService.deleteApiKey(authContext.organizationId, userId, name)
  }

  private validateRequestedApiKeyPermissions(
    authContext: OrganizationAuthContext,
    requestedPermissions: OrganizationResourcePermission[],
  ): void {
    if (authContext.organizationUser.role === OrganizationMemberRole.OWNER) {
      return
    }

    const organizationUserPermissions = new Set(
      authContext.organizationUser.assignedRoles.flatMap((role) => role.permissions),
    )

    const forbiddenPermissions = requestedPermissions.filter(
      (permission) => !organizationUserPermissions.has(permission),
    )

    if (forbiddenPermissions.length) {
      throw new ForbiddenException(`Insufficient permissions for assigning: ${forbiddenPermissions.join(', ')}`)
    }
  }
}
