import {
  Body,
  Controller,
  Delete,
  Get,
  HttpCode,
  HttpStatus,
  Param,
  Post,
  Req,
} from '@nestjs/common';
import { ApiCookieAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import type { Request } from 'express';
import { AuditWriter } from '../audit/audit.service';
import { RequireAdmin } from './require-admin.decorator';
import { GrantUserScopeDto, type UserScopeDto, type UserScopesPageDto } from './user-scopes.dto';
import { UserScopesService } from './user-scopes.service';

/**
 * `/api/admin/users/:oid/scopes` per ADR-0026 PR 2b — admin
 * scope-management screen for an existing portal User. Each write
 * emits a typed audit row (blocking per ADR-0013); reads are NOT
 * audited (low signal, high noise).
 *
 * The `:oid` is the affected user's Entra `oid` — same identifier
 * the v1 user-directory list (ADR-0020) uses, so the SPA can link
 * directly from the existing `/admin/users` rows.
 */
@ApiTags('admin (user scopes)')
@ApiCookieAuth('portal_admin_session')
@Controller('admin/users/:oid/scopes')
@RequireAdmin()
export class UserScopesController {
  constructor(
    private readonly userScopes: UserScopesService,
    private readonly audit: AuditWriter,
  ) {}

  @ApiOperation({
    summary: "List a user's scopes (no audit — read).",
  })
  @Get()
  async list(@Param('oid') oid: string): Promise<UserScopesPageDto> {
    return this.userScopes.list(oid);
  }

  @ApiOperation({
    summary: 'Grant a scope to a user — emits `admin.scope_granted` (blocking).',
  })
  @Post()
  async grant(
    @Req() req: Request,
    @Param('oid') oid: string,
    @Body() body: GrantUserScopeDto,
  ): Promise<UserScopeDto> {
    const { user, scope } = await this.userScopes.grant(oid, body);
    const actorOid = req.session.user?.oid;
    if (actorOid !== undefined) {
      await this.audit.adminScopeGranted({
        actor: { oid: actorOid },
        target: { oid: user.oid },
        scopeId: scope.id,
        kind: scope.kind,
        value: scope.value,
        expiresAt: scope.expiresAt,
      });
    }
    return scope;
  }

  @ApiOperation({
    summary: 'Revoke a scope — emits `admin.scope_revoked` (blocking).',
  })
  @Delete(':scopeId')
  @HttpCode(HttpStatus.NO_CONTENT)
  async revoke(
    @Req() req: Request,
    @Param('oid') oid: string,
    @Param('scopeId') scopeId: string,
  ): Promise<void> {
    const { user, revoked } = await this.userScopes.revoke(oid, scopeId);
    const actorOid = req.session.user?.oid;
    if (actorOid !== undefined) {
      await this.audit.adminScopeRevoked({
        actor: { oid: actorOid },
        target: { oid: user.oid },
        scopeId,
        kind: revoked.kind,
        value: revoked.value,
      });
    }
  }
}
