import { Body, Controller, Get, Param, Patch, Post, Query } from '@nestjs/common';
import { ApiBody, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { PaymentProvider, UserRole } from '@prisma/client';
import { RequireRole } from '../auth/require-role.decorator';
import { CurrentUser } from '../common/current-user.decorator';
import { RequestUser } from '../common/request-user';
import { FeatureKey } from '../features/feature-catalog';
import { RequireFeature } from '../features/require-feature.decorator';
import {
  ApiNotFoundError,
  ApiProtectedEndpoint,
  ApiValidationError,
  ESTATE_ID_EXAMPLE,
  GUARD_ID_EXAMPLE,
  RESIDENT_ID_EXAMPLE,
  USER_ID_EXAMPLE,
  VISITOR_ID_EXAMPLE,
  VISITOR_PASS_ID_EXAMPLE,
  VISIT_SESSION_ID_EXAMPLE
} from '../common/swagger-docs';
import { AdminService } from './admin.service';
import {
  UpdateEstateDto,
  AssignEstatePartnerDto,
  ContactEstateAdminDto,
  ConfirmEstatePaymentDto,
  CreateSubscriptionPlanDto,
  UpdateGuardDto,
  UpdateIncidentDto,
  UpdatePaymentProviderSettingDto,
  UpdateResidentDto,
  UpdateSubscriptionPlanDto,
  UpdateUserDto,
  UpdateVisitorDto,
  UpdateVisitorPassDto,
  ReviewEstateSignupDto,
  UpdateSalesPartnerProfileDto,
  UpdateOtpConfigDto,
  UpdateIntegrationConfigDto
} from './dto/admin.dto';

@ApiTags('Admin')
@ApiProtectedEndpoint()
@Controller({ path: 'admin', version: '1' })
@RequireRole(UserRole.SUPER_ADMIN)
export class AdminController {
  constructor(private readonly adminService: AdminService) {}

  @Get('dashboard')
  @RequireFeature(FeatureKey.PLATFORM_DASHBOARD)
  @ApiOperation({
    summary: 'Super admin dashboard summary',
    description: 'Returns platform-wide counts and recent audit logs for the super-admin dashboard.'
  })
  @ApiResponse({
    status: 200,
    description: 'Dashboard summary returned.',
    schema: {
      example: {
        totals: {
          estates: 1,
          users: 4,
          residents: 1,
          guards: 1,
          visitors: 10,
          activePasses: 3,
          openVisits: 1,
          incidents: 2
        },
        recentAuditLogs: []
      }
    }
  })
  dashboard(@CurrentUser() user: RequestUser) {
    return this.adminService.dashboard(user);
  }

  @Get('users')
  @RequireRole(UserRole.SUPER_ADMIN, UserRole.ESTATE_ADMIN)
  @RequireFeature(FeatureKey.USERS_VIEW)
  @ApiOperation({
    summary: 'List users',
    description: 'Lists platform users for super admins or same-estate users for estate admins, with optional role and status filters.'
  })
  @ApiQuery({ name: 'estateId', required: false, example: ESTATE_ID_EXAMPLE })
  @ApiQuery({ name: 'role', required: false, example: 'RESIDENT' })
  @ApiQuery({ name: 'status', required: false, example: 'ACTIVE' })
  @ApiResponse({
    status: 200,
    description: 'Users returned.',
    schema: { example: [{ id: USER_ID_EXAMPLE, estateId: ESTATE_ID_EXAMPLE, phone: '+2348000000003', name: 'Ade Okafor', role: 'RESIDENT', status: 'ACTIVE' }] }
  })
  listUsers(
    @CurrentUser() user: RequestUser,
    @Query('estateId') estateId?: string,
    @Query('role') role?: string,
    @Query('status') status?: string
  ) {
    return this.adminService.listUsers(user, estateId, role, status);
  }

  @Patch('users/:id')
  @RequireRole(UserRole.SUPER_ADMIN, UserRole.ESTATE_ADMIN)
  @RequireFeature(FeatureKey.USERS_MANAGE)
  @ApiOperation({
    summary: 'Update user',
    description: 'Updates a user account and synchronizes matching resident or guard profile fields where applicable. Estate admins are restricted to users in their estate.'
  })
  @ApiParam({ name: 'id', example: USER_ID_EXAMPLE, description: 'User ID to update.' })
  @ApiBody({ type: UpdateUserDto })
  @ApiResponse({ status: 200, description: 'User updated.', schema: { example: { id: USER_ID_EXAMPLE, status: 'SUSPENDED' } } })
  @ApiNotFoundError('User')
  @ApiValidationError()
  updateUser(@Param('id') id: string, @Body() dto: UpdateUserDto, @CurrentUser() user: RequestUser) {
    return this.adminService.updateUser(id, dto, user);
  }

  @Get('estates')
  @RequireFeature(FeatureKey.ESTATES_VIEW)
  @ApiOperation({ summary: 'List all estates', description: 'Lists all estates for super-admin operations.' })
  @ApiResponse({ status: 200, description: 'Estates returned.', schema: { example: [{ id: ESTATE_ID_EXAMPLE, name: 'GreenPark Estate', location: 'Lagos, Nigeria' }] } })
  listEstates(@CurrentUser() user: RequestUser) {
    return this.adminService.listEstates(user);
  }

  @Get('estate-signups')
  @RequireFeature(FeatureKey.ESTATE_SIGNUPS_VIEW)
  @ApiOperation({
    summary: 'List estate admin signup leads',
    description: 'Lists estate-admin lead records by signup status for super-admin review.'
  })
  @ApiQuery({ name: 'status', required: false, example: 'PENDING_APPROVAL' })
  @ApiResponse({
    status: 200,
    description: 'Estate signup leads returned.',
    schema: {
      example: [
        {
          id: ESTATE_ID_EXAMPLE,
          name: 'GreenPark Estate',
          unitCount: 120,
          estateType: 'Residential gated community',
          status: 'PENDING',
          signupStatus: 'PENDING_APPROVAL'
        }
      ]
    }
  })
  listEstateSignups(@Query('status') status: string | undefined, @CurrentUser() user: RequestUser) {
    return this.adminService.listEstateSignups(status, user);
  }

  @Patch('estate-signups/:estateId/review')
  @RequireFeature(FeatureKey.ESTATE_SIGNUPS_REVIEW)
  @ApiOperation({
    summary: 'Review estate admin signup',
    description:
      'Approves, rejects, or suspends an estate-admin signup lead. Approval allows plan selection, but app access remains disabled until payment confirmation.'
  })
  @ApiParam({ name: 'estateId', example: ESTATE_ID_EXAMPLE })
  @ApiBody({ type: ReviewEstateSignupDto })
  @ApiResponse({
    status: 200,
    description: 'Estate signup reviewed.',
    schema: { example: { id: ESTATE_ID_EXAMPLE, signupStatus: 'APPROVED', status: 'PENDING' } }
  })
  reviewEstateSignup(
    @Param('estateId') estateId: string,
    @Body() dto: ReviewEstateSignupDto,
    @CurrentUser() user: RequestUser
  ) {
    return this.adminService.reviewEstateSignup(estateId, dto, user);
  }

  @Patch('estate-signups/:estateId/confirm-payment')
  @RequireFeature(FeatureKey.PAYMENTS_CONFIRM)
  @ApiOperation({
    summary: 'Confirm estate payment and activate estate',
    description:
      'Confirms the latest pending payment, verifies hosted checkout payments against the provider sandbox, activates the estate, activates the estate admin account, and enables normal app access.'
  })
  @ApiParam({ name: 'estateId', example: ESTATE_ID_EXAMPLE })
  @ApiBody({ type: ConfirmEstatePaymentDto })
  @ApiResponse({
    status: 200,
    description: 'Payment confirmed and estate activated.',
    schema: {
      example: {
        estate: { id: ESTATE_ID_EXAMPLE, status: 'ACTIVE', subscriptionStatus: 'ACTIVE' },
        payment: { id: 'payment-uuid', status: 'CONFIRMED' }
      }
    }
  })
  confirmEstatePayment(
    @Param('estateId') estateId: string,
    @Body() dto: ConfirmEstatePaymentDto,
    @CurrentUser() user: RequestUser
  ) {
    return this.adminService.confirmEstatePayment(estateId, dto, user);
  }

  @Get('subscription-plans')
  @RequireFeature(FeatureKey.SUBSCRIPTION_PLANS_VIEW)
  @ApiOperation({ summary: 'List subscription plans', description: 'Lists plan definitions and prices for super-admin management.' })
  @ApiQuery({ name: 'includeInactive', required: false, example: 'true' })
  @ApiResponse({
    status: 200,
    description: 'Plans returned.',
    schema: {
      example: [{ code: 'STARTER', name: 'GateWise Starter', minResidents: 0, maxResidents: 100, monthlyPriceKobo: 20000000 }]
    }
  })
  listSubscriptionPlans(@Query('includeInactive') includeInactive?: string) {
    return this.adminService.listSubscriptionPlans(includeInactive);
  }

  @Post('subscription-plans')
  @ApiOperation({
    summary: 'Create subscription plan',
    description:
      'Creates a super-admin-owned subscription plan with its allowed feature list. Estate admins cannot enable resident or guard features outside their estate plan.'
  })
  @ApiBody({ type: CreateSubscriptionPlanDto })
  @ApiResponse({ status: 201, description: 'Plan created.', schema: { example: { code: 'CUSTOM_LUXURY', features: ['CHECKIN_SCAN'] } } })
  @ApiValidationError()
  createSubscriptionPlan(@Body() dto: CreateSubscriptionPlanDto, @CurrentUser() user: RequestUser) {
    return this.adminService.createSubscriptionPlan(dto, user);
  }

  @Get('otp/config')
  @RequireFeature(FeatureKey.OTP_PROVIDERS_MANAGE)
  @ApiOperation({
    summary: 'View OTP delivery configuration',
    description:
      'Shows the active OTP provider, OTP expiry policy, and whether Termii, Africa\'s Talking, or a custom HTTP SMS endpoint is configured. Secrets are never returned.'
  })
  @ApiResponse({
    status: 200,
    description: 'OTP configuration returned.',
    schema: {
      example: {
        provider: 'DEV',
        enabledProviders: ['DEV'],
        providerPriority: ['DEV'],
        availableProviders: ['DEV', 'TERMII', 'AFRICAS_TALKING', 'CUSTOM_HTTP'],
        providers: [
          { provider: 'DEV', enabled: true, priority: 1, configured: true, activePrimary: true },
          { provider: 'TERMII', enabled: false, priority: null, configured: false, activePrimary: false }
        ],
        codeLength: 6,
        expiresInSeconds: 300,
        maxAttempts: 5,
        devOtpExposed: true,
        termii: { configured: false, baseUrl: 'https://api.ng.termii.com', senderId: 'SmartEstate', channel: 'dnd' },
        africasTalking: { configured: false, baseUrl: 'https://api.africastalking.com/version1/messaging', username: 'sandbox' },
        customHttp: { configured: false, url: '', method: 'POST', hasHeaders: false, hasBodyTemplate: false }
      }
    }
  })
  getOtpConfig() {
    return this.adminService.getOtpConfig();
  }

  @Patch('otp/config')
  @RequireFeature(FeatureKey.OTP_PROVIDERS_MANAGE)
  @ApiOperation({
    summary: 'Update OTP delivery configuration',
    description:
      'Switches OTP delivery between DEV, Termii, Africa\'s Talking, and a custom HTTP SMS endpoint at runtime. Super admins can enable or disable providers and define fallback priority. Runtime changes are in-memory; copy stable values into .env for production.'
  })
  @ApiBody({ type: UpdateOtpConfigDto })
  @ApiResponse({
    status: 200,
    description: 'OTP configuration updated. Secrets are not returned.',
    schema: {
      example: {
        provider: 'TERMII',
        enabledProviders: ['TERMII', 'AFRICAS_TALKING'],
        providerPriority: ['TERMII', 'AFRICAS_TALKING'],
        expiresInSeconds: 300,
        termii: { configured: true, channel: 'dnd' }
      }
    }
  })
  @ApiValidationError()
  updateOtpConfig(@Body() dto: UpdateOtpConfigDto, @CurrentUser() user: RequestUser) {
    return this.adminService.updateOtpConfig(dto, user);
  }

  @Get('integrations/templates')
  @RequireFeature(FeatureKey.INTEGRATIONS_MANAGE)
  @ApiOperation({
    summary: 'List third-party integration templates',
    description:
      'Returns known payment, SMS, and email providers with public and secret key names so a super-admin UI can render update forms.'
  })
  @ApiResponse({
    status: 200,
    description: 'Integration templates returned.',
    schema: {
      example: [
        {
          category: 'PAYMENT',
          provider: 'FLUTTERWAVE',
          displayName: 'Flutterwave',
          publicKeys: ['baseUrl', 'publicKey'],
          secretKeys: ['secretKey', 'encryptionKey']
        }
      ]
    }
  })
  listIntegrationTemplates() {
    return this.adminService.listIntegrationTemplates();
  }

  @Get('integrations/config')
  @RequireFeature(FeatureKey.INTEGRATIONS_MANAGE)
  @ApiOperation({
    summary: 'List third-party integration configuration',
    description:
      'Lists configured payment gateways, SMS providers, and email providers. Secret values are never returned; each secret key only reports configured status and a masked suffix.'
  })
  @ApiResponse({
    status: 200,
    description: 'Integration configuration returned.',
    schema: {
      example: [
        {
          category: 'PAYMENT',
          provider: 'PAYSTACK',
          displayName: 'Paystack',
          enabled: true,
          configured: true,
          publicConfig: { baseUrl: 'https://api.paystack.co', publicKey: 'pk_live_xxx' },
          secrets: { secretKey: { configured: true, masked: '****abcd' } }
        }
      ]
    }
  })
  listIntegrationConfigs() {
    return this.adminService.listIntegrationConfigs();
  }

  @Get('integrations/config/:provider')
  @RequireFeature(FeatureKey.INTEGRATIONS_MANAGE)
  @ApiOperation({
    summary: 'View one third-party integration configuration',
    description: 'Returns sanitized config for one provider. Secrets are masked and never returned in full.'
  })
  @ApiParam({ name: 'provider', example: 'FLUTTERWAVE' })
  @ApiResponse({ status: 200, description: 'Integration configuration returned.' })
  getIntegrationConfig(@Param('provider') provider: string) {
    return this.adminService.getIntegrationConfig(provider);
  }

  @Patch('integrations/config/:provider')
  @RequireFeature(FeatureKey.INTEGRATIONS_MANAGE)
  @ApiOperation({
    summary: 'Update third-party integration keys',
    description:
      'Creates or updates payment gateway, SMS provider, email provider, or custom integration keys. publicConfig is readable by super admins; secretConfig is encrypted at rest and write-only. Updates take effect immediately for payment and OTP providers without redeploying.'
  })
  @ApiParam({ name: 'provider', example: 'FLUTTERWAVE' })
  @ApiBody({ type: UpdateIntegrationConfigDto })
  @ApiResponse({
    status: 200,
    description: 'Integration configuration updated. Secrets are not returned.',
    schema: {
      example: {
        category: 'PAYMENT',
        provider: 'FLUTTERWAVE',
        enabled: true,
        configured: true,
        publicConfig: { baseUrl: 'https://api.flutterwave.com/v3' },
        secrets: { secretKey: { configured: true, masked: '****abcd' } }
      }
    }
  })
  @ApiValidationError()
  updateIntegrationConfig(
    @Param('provider') provider: string,
    @Body() dto: UpdateIntegrationConfigDto,
    @CurrentUser() user: RequestUser
  ) {
    return this.adminService.updateIntegrationConfig(provider, dto, user);
  }

  @Patch('subscription-plans/:code')
  @ApiOperation({ summary: 'Update subscription plan', description: 'Updates subscription plan pricing, resident ranges, or availability.' })
  @ApiParam({ name: 'code', example: 'STARTER' })
  @ApiBody({ type: UpdateSubscriptionPlanDto })
  @ApiResponse({ status: 200, description: 'Plan updated.', schema: { example: { code: 'STARTER', monthlyPriceKobo: 20000000 } } })
  updateSubscriptionPlan(@Param('code') code: string, @Body() dto: UpdateSubscriptionPlanDto, @CurrentUser() user: RequestUser) {
    return this.adminService.updateSubscriptionPlan(code, dto, user);
  }

  @Get('payment-providers')
  @RequireFeature(FeatureKey.PAYMENT_PROVIDERS_VIEW)
  @ApiOperation({
    summary: 'List payment provider settings',
    description: 'Lists global provider settings and effective estate overrides when estateId is supplied.'
  })
  @ApiQuery({ name: 'estateId', required: false, example: ESTATE_ID_EXAMPLE })
  @ApiResponse({
    status: 200,
    description: 'Payment providers returned.',
    schema: {
      example: [
        {
          provider: 'FLUTTERWAVE',
          displayName: 'Flutterwave',
          globallyEnabled: true,
          estateEnabled: null,
          effectiveEnabled: true,
          sandboxMode: 'sandbox',
          sandboxConfigured: true,
          sandboxBaseUrl: 'https://api.flutterwave.com/v3'
        }
      ]
    }
  })
  listPaymentProviders(@Query('estateId') estateId?: string) {
    return this.adminService.listPaymentProviders(estateId);
  }

  @Patch('payment-providers/:provider')
  @RequireFeature(FeatureKey.PAYMENT_PROVIDERS_MANAGE)
  @ApiOperation({ summary: 'Toggle global payment provider', description: 'Enables or disables a payment provider globally.' })
  @ApiParam({ name: 'provider', enum: PaymentProvider, example: PaymentProvider.FLUTTERWAVE })
  @ApiBody({ type: UpdatePaymentProviderSettingDto })
  @ApiResponse({ status: 200, description: 'Global provider setting updated.', schema: { example: { provider: 'FLUTTERWAVE', enabled: true } } })
  updateGlobalPaymentProvider(
    @Param('provider') provider: PaymentProvider,
    @Body() dto: UpdatePaymentProviderSettingDto,
    @CurrentUser() user: RequestUser
  ) {
    return this.adminService.updateGlobalPaymentProvider(provider, dto, user);
  }

  @Patch('estates/:estateId/payment-providers/:provider')
  @RequireFeature(FeatureKey.PAYMENT_PROVIDERS_MANAGE)
  @ApiOperation({
    summary: 'Toggle estate payment provider override',
    description: 'Enables or disables a provider for one estate, overriding the global setting.'
  })
  @ApiParam({ name: 'estateId', example: ESTATE_ID_EXAMPLE })
  @ApiParam({ name: 'provider', enum: PaymentProvider, example: PaymentProvider.PAYSTACK })
  @ApiBody({ type: UpdatePaymentProviderSettingDto })
  @ApiResponse({ status: 200, description: 'Estate provider override updated.', schema: { example: { estateId: ESTATE_ID_EXAMPLE, provider: 'PAYSTACK', enabled: true } } })
  updateEstatePaymentProvider(
    @Param('estateId') estateId: string,
    @Param('provider') provider: PaymentProvider,
    @Body() dto: UpdatePaymentProviderSettingDto,
    @CurrentUser() user: RequestUser
  ) {
    return this.adminService.updateEstatePaymentProvider(estateId, provider, dto, user);
  }

  @Get('sales-partners')
  @ApiOperation({
    summary: 'List sales admins and partner status',
    description: 'Super-admin-only list of sales admins, partner earning status, commission percentage, and assigned estates.'
  })
  @ApiResponse({ status: 200, description: 'Sales partner profiles returned.' })
  listSalesPartners() {
    return this.adminService.listSalesPartners();
  }

  @Get('sales-partners/me')
  @RequireFeature(FeatureKey.SALES_PARTNER_PROFILE_VIEW)
  @ApiOperation({
    summary: 'View my sales partner profile',
    description:
      'Returns the signed-in sales admin profile. profileLabel is Partner only when super admin has activated partner earnings.'
  })
  @ApiResponse({ status: 200, description: 'Partner profile returned.' })
  getMySalesPartnerProfile(@CurrentUser() user: RequestUser) {
    return this.adminService.getMySalesPartnerProfile(user);
  }

  @Get('sales-partners/me/earnings')
  @RequireFeature(FeatureKey.SALES_PARTNER_EARNINGS_VIEW)
  @ApiOperation({
    summary: 'View my partner earnings',
    description: 'Returns estimated commission for confirmed payments on estates assigned to the signed-in sales admin.'
  })
  @ApiResponse({ status: 200, description: 'Partner earnings returned.' })
  getMySalesPartnerEarnings(@CurrentUser() user: RequestUser) {
    return this.adminService.getMySalesPartnerEarnings(user);
  }

  @Get('sales-partners/:salesAdminId/earnings')
  @ApiOperation({
    summary: 'View sales partner earnings',
    description: 'Super-admin-only earnings view for a sales admin partner profile.'
  })
  @ApiParam({ name: 'salesAdminId', example: USER_ID_EXAMPLE })
  @ApiResponse({ status: 200, description: 'Partner earnings returned.' })
  getSalesPartnerEarnings(@Param('salesAdminId') salesAdminId: string, @CurrentUser() user: RequestUser) {
    return this.adminService.getSalesPartnerEarnings(salesAdminId, user);
  }

  @Patch('sales-partners/:salesAdminId')
  @ApiOperation({
    summary: 'Update sales partner earnings',
    description:
      'Super-admin-only control for enabling partner status and setting commission percentage for a sales admin. Enabling this makes the profileLabel return Partner.'
  })
  @ApiParam({ name: 'salesAdminId', example: USER_ID_EXAMPLE })
  @ApiBody({ type: UpdateSalesPartnerProfileDto })
  @ApiResponse({ status: 200, description: 'Sales partner profile updated.' })
  @ApiValidationError()
  updateSalesPartnerProfile(
    @Param('salesAdminId') salesAdminId: string,
    @Body() dto: UpdateSalesPartnerProfileDto,
    @CurrentUser() user: RequestUser
  ) {
    return this.adminService.updateSalesPartnerProfile(salesAdminId, dto, user);
  }

  @Patch('estates/:id/sales-partner')
  @ApiOperation({
    summary: 'Assign estate to sales partner',
    description:
      'Super-admin-only assignment of an estate to a sales admin. Sales admins can access only estates assigned to them.'
  })
  @ApiParam({ name: 'id', example: ESTATE_ID_EXAMPLE })
  @ApiBody({ type: AssignEstatePartnerDto })
  @ApiResponse({ status: 200, description: 'Estate partner assignment updated.' })
  @ApiValidationError()
  assignEstateSalesPartner(
    @Param('id') id: string,
    @Body() dto: AssignEstatePartnerDto,
    @CurrentUser() user: RequestUser
  ) {
    return this.adminService.assignEstateSalesPartner(id, dto, user);
  }

  @Post('estates/:estateId/admin-contact')
  @RequireFeature(FeatureKey.SALES_PARTNER_ADMIN_CONTACT)
  @ApiOperation({
    summary: 'Contact assigned estate admins',
    description:
      'Queues or records a sales partner message to estate admins. Sales admins can contact only admins for estates assigned to them.'
  })
  @ApiParam({ name: 'estateId', example: ESTATE_ID_EXAMPLE })
  @ApiBody({ type: ContactEstateAdminDto })
  @ApiResponse({ status: 201, description: 'Contact request queued.' })
  @ApiValidationError()
  contactEstateAdmin(
    @Param('estateId') estateId: string,
    @Body() dto: ContactEstateAdminDto,
    @CurrentUser() user: RequestUser
  ) {
    return this.adminService.contactEstateAdmin(estateId, dto, user);
  }

  @Patch('estates/:id')
  @RequireFeature(FeatureKey.ESTATES_MANAGE)
  @ApiOperation({ summary: 'Update estate', description: 'Updates estate details, settings, and branding JSON.' })
  @ApiParam({ name: 'id', example: ESTATE_ID_EXAMPLE, description: 'Estate ID to update.' })
  @ApiBody({ type: UpdateEstateDto })
  @ApiResponse({ status: 200, description: 'Estate updated.', schema: { example: { id: ESTATE_ID_EXAMPLE, name: 'GreenPark Estate', location: 'Lagos, Nigeria' } } })
  @ApiNotFoundError('Estate')
  @ApiValidationError()
  updateEstate(@Param('id') id: string, @Body() dto: UpdateEstateDto, @CurrentUser() user: RequestUser) {
    return this.adminService.updateEstate(id, dto, user);
  }

  @Get('residents')
  @RequireFeature(FeatureKey.RESIDENTS_VIEW)
  @ApiOperation({ summary: 'List residents', description: 'Lists residents across the platform or within a single estate.' })
  @ApiQuery({ name: 'estateId', required: false, example: ESTATE_ID_EXAMPLE })
  @ApiResponse({ status: 200, description: 'Residents returned.', schema: { example: [{ id: RESIDENT_ID_EXAMPLE, estateId: ESTATE_ID_EXAMPLE, name: 'Ade Okafor', unit: 'A12', status: 'ACTIVE' }] } })
  listResidents(@CurrentUser() user: RequestUser, @Query('estateId') estateId?: string) {
    return this.adminService.listResidents(user, estateId);
  }

  @Patch('residents/:id')
  @RequireFeature(FeatureKey.RESIDENTS_MANAGE)
  @ApiOperation({ summary: 'Update resident', description: 'Updates resident profile fields and synchronized user fields.' })
  @ApiParam({ name: 'id', example: RESIDENT_ID_EXAMPLE, description: 'Resident ID to update.' })
  @ApiBody({ type: UpdateResidentDto })
  @ApiResponse({ status: 200, description: 'Resident updated.', schema: { example: { id: RESIDENT_ID_EXAMPLE, unit: 'B14', status: 'ACTIVE' } } })
  @ApiNotFoundError('Resident')
  @ApiValidationError()
  updateResident(@Param('id') id: string, @Body() dto: UpdateResidentDto, @CurrentUser() user: RequestUser) {
    return this.adminService.updateResident(id, dto, user);
  }

  @Get('guards')
  @RequireFeature(FeatureKey.GUARDS_VIEW)
  @ApiOperation({ summary: 'List guards', description: 'Lists guards across the platform or within a single estate.' })
  @ApiQuery({ name: 'estateId', required: false, example: ESTATE_ID_EXAMPLE })
  @ApiResponse({ status: 200, description: 'Guards returned.', schema: { example: [{ id: GUARD_ID_EXAMPLE, estateId: ESTATE_ID_EXAMPLE, name: 'Main Gate Guard', deviceId: 'guard-device-001', status: 'ACTIVE' }] } })
  listGuards(@CurrentUser() user: RequestUser, @Query('estateId') estateId?: string) {
    return this.adminService.listGuards(user, estateId);
  }

  @Patch('guards/:id')
  @RequireFeature(FeatureKey.GUARDS_MANAGE)
  @ApiOperation({ summary: 'Update guard', description: 'Updates guard profile fields and synchronized user fields.' })
  @ApiParam({ name: 'id', example: GUARD_ID_EXAMPLE, description: 'Guard ID to update.' })
  @ApiBody({ type: UpdateGuardDto })
  @ApiResponse({ status: 200, description: 'Guard updated.', schema: { example: { id: GUARD_ID_EXAMPLE, deviceId: 'guard-device-002', status: 'ACTIVE' } } })
  @ApiNotFoundError('Guard')
  @ApiValidationError()
  updateGuard(@Param('id') id: string, @Body() dto: UpdateGuardDto, @CurrentUser() user: RequestUser) {
    return this.adminService.updateGuard(id, dto, user);
  }

  @Get('visitors')
  @RequireFeature(FeatureKey.VISITORS_VIEW)
  @ApiOperation({ summary: 'List visitors', description: 'Lists estate-scoped visitor records across the platform or within a single estate.' })
  @ApiQuery({ name: 'estateId', required: false, example: ESTATE_ID_EXAMPLE })
  @ApiResponse({ status: 200, description: 'Visitors returned.', schema: { example: [{ id: VISITOR_ID_EXAMPLE, estateId: ESTATE_ID_EXAMPLE, name: 'Ade Okafor', vehiclePlate: 'LND-123-AB' }] } })
  listVisitors(@CurrentUser() user: RequestUser, @Query('estateId') estateId?: string) {
    return this.adminService.listVisitors(user, estateId);
  }

  @Patch('visitors/:id')
  @RequireFeature(FeatureKey.VISITORS_MANAGE)
  @ApiOperation({ summary: 'Update visitor', description: 'Updates visitor identity, vehicle, photo, or estate assignment.' })
  @ApiParam({ name: 'id', example: VISITOR_ID_EXAMPLE, description: 'Visitor ID to update.' })
  @ApiBody({ type: UpdateVisitorDto })
  @ApiResponse({ status: 200, description: 'Visitor updated.', schema: { example: { id: VISITOR_ID_EXAMPLE, name: 'Ade Okafor', vehiclePlate: 'LND-123-AB' } } })
  @ApiNotFoundError('Visitor')
  @ApiValidationError()
  updateVisitor(@Param('id') id: string, @Body() dto: UpdateVisitorDto, @CurrentUser() user: RequestUser) {
    return this.adminService.updateVisitor(id, dto, user);
  }

  @Get('visitor-passes')
  @RequireFeature(FeatureKey.VISITOR_PASSES_VIEW)
  @ApiOperation({ summary: 'List visitor passes', description: 'Lists visitor passes with optional estate and status filters.' })
  @ApiQuery({ name: 'estateId', required: false, example: ESTATE_ID_EXAMPLE })
  @ApiQuery({ name: 'status', required: false, example: 'ACTIVE' })
  @ApiResponse({ status: 200, description: 'Visitor passes returned.', schema: { example: [{ id: VISITOR_PASS_ID_EXAMPLE, estateId: ESTATE_ID_EXAMPLE, status: 'ACTIVE', passType: 'SINGLE_USE' }] } })
  listVisitorPasses(@CurrentUser() user: RequestUser, @Query('estateId') estateId?: string, @Query('status') status?: string) {
    return this.adminService.listVisitorPasses(user, estateId, status);
  }

  @Patch('visitor-passes/:id')
  @RequireFeature(FeatureKey.VISITOR_PASSES_REVOKE)
  @ApiOperation({ summary: 'Update visitor pass', description: 'Updates pass status, type, validity window, or entry counts.' })
  @ApiParam({ name: 'id', example: VISITOR_PASS_ID_EXAMPLE, description: 'Visitor pass ID to update.' })
  @ApiBody({ type: UpdateVisitorPassDto })
  @ApiResponse({ status: 200, description: 'Visitor pass updated.', schema: { example: { id: VISITOR_PASS_ID_EXAMPLE, status: 'REVOKED', revokedAt: '2026-05-16T12:00:00.000Z' } } })
  @ApiNotFoundError('Visitor pass')
  @ApiValidationError()
  updateVisitorPass(@Param('id') id: string, @Body() dto: UpdateVisitorPassDto, @CurrentUser() user: RequestUser) {
    return this.adminService.updateVisitorPass(id, dto, user);
  }

  @Get('visit-sessions')
  @RequireFeature(FeatureKey.VISIT_SESSIONS_VIEW)
  @ApiOperation({ summary: 'List visit sessions', description: 'Lists visit sessions with visitor pass and guard context.' })
  @ApiQuery({ name: 'estateId', required: false, example: ESTATE_ID_EXAMPLE })
  @ApiResponse({ status: 200, description: 'Visit sessions returned.', schema: { example: [{ id: VISIT_SESSION_ID_EXAMPLE, estateId: ESTATE_ID_EXAMPLE, guardId: GUARD_ID_EXAMPLE, status: 'CHECKED_IN' }] } })
  listVisitSessions(@CurrentUser() user: RequestUser, @Query('estateId') estateId?: string) {
    return this.adminService.listVisitSessions(user, estateId);
  }

  @Get('incidents')
  @RequireFeature(FeatureKey.INCIDENTS_VIEW)
  @ApiOperation({ summary: 'List incidents', description: 'Lists incident reports with guard and estate context.' })
  @ApiQuery({ name: 'estateId', required: false, example: ESTATE_ID_EXAMPLE })
  @ApiResponse({ status: 200, description: 'Incidents returned.', schema: { example: [{ id: 'incident-uuid', estateId: ESTATE_ID_EXAMPLE, guardId: GUARD_ID_EXAMPLE, type: 'SECURITY' }] } })
  listIncidents(@CurrentUser() user: RequestUser, @Query('estateId') estateId?: string) {
    return this.adminService.listIncidents(user, estateId);
  }

  @Patch('incidents/:id')
  @RequireFeature(FeatureKey.INCIDENTS_MANAGE)
  @ApiOperation({ summary: 'Update incident', description: 'Updates incident assignment, type, description, or metadata.' })
  @ApiParam({ name: 'id', example: 'incident-uuid', description: 'Incident ID to update.' })
  @ApiBody({ type: UpdateIncidentDto })
  @ApiResponse({ status: 200, description: 'Incident updated.', schema: { example: { id: 'incident-uuid', type: 'SECURITY', description: 'Updated incident description' } } })
  @ApiNotFoundError('Incident')
  @ApiValidationError()
  updateIncident(@Param('id') id: string, @Body() dto: UpdateIncidentDto, @CurrentUser() user: RequestUser) {
    return this.adminService.updateIncident(id, dto, user);
  }

  @Get('audit-logs')
  @RequireFeature(FeatureKey.PLATFORM_DASHBOARD)
  @ApiOperation({ summary: 'List audit logs', description: 'Lists recent audit logs across the platform or within a single estate.' })
  @ApiQuery({ name: 'estateId', required: false, example: ESTATE_ID_EXAMPLE })
  @ApiResponse({ status: 200, description: 'Audit logs returned.', schema: { example: [{ id: 'audit-log-uuid', estateId: ESTATE_ID_EXAMPLE, actorType: 'USER', actorId: USER_ID_EXAMPLE, action: 'USER_SIGNED_UP' }] } })
  listAuditLogs(@Query('estateId') estateId: string | undefined, @CurrentUser() user: RequestUser) {
    return this.adminService.listAuditLogs(estateId, user);
  }
}
