import { AccountStatus, EstateSignupStatus, PaymentProvider, VisitorPassStatus, VisitorPassType } from '@prisma/client';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
  ArrayUnique,
  IsArray,
  IsBoolean,
  IsDate,
  IsEnum,
  IsIn,
  IsInt,
  IsNumber,
  IsObject,
  IsOptional,
  IsPhoneNumber,
  IsString,
  Max,
  Min
} from 'class-validator';
import { OtpDeliveryProvider } from '../../auth/otp.types';
import { ESTATE_ID_EXAMPLE, GUARD_ID_EXAMPLE, USER_ID_EXAMPLE } from '../../common/swagger-docs';
import { FeatureKey } from '../../features/feature-catalog';

export class UpdateEstateDto {
  @ApiPropertyOptional({ example: 'GreenPark Estate', description: 'Updated estate name.' })
  @IsOptional()
  @IsString()
  name?: string;

  @ApiPropertyOptional({ example: 'Lagos, Nigeria', description: 'Updated estate location.' })
  @IsOptional()
  @IsString()
  location?: string;

  @ApiPropertyOptional({ example: 120, description: 'Updated estate unit/resident count.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  unitCount?: number;

  @ApiPropertyOptional({ example: 'Residential gated community', description: 'Updated estate type.' })
  @IsOptional()
  @IsString()
  estateType?: string;

  @ApiPropertyOptional({ example: { timezone: 'Africa/Lagos' }, description: 'Replace estate settings JSON.' })
  @IsOptional()
  @IsObject()
  settings?: Record<string, unknown>;

  @ApiPropertyOptional({ example: { primaryColor: '#0f766e' }, description: 'Replace estate branding JSON.' })
  @IsOptional()
  @IsObject()
  branding?: Record<string, unknown>;
}

export class ReviewEstateSignupDto {
  @ApiProperty({
    enum: [EstateSignupStatus.APPROVED, EstateSignupStatus.REJECTED, EstateSignupStatus.SUSPENDED],
    example: EstateSignupStatus.APPROVED,
    description: 'Super-admin review decision for a pending estate admin signup.'
  })
  @IsEnum(EstateSignupStatus)
  status!: EstateSignupStatus;

  @ApiPropertyOptional({ example: 'Payment pending. Approved for plan selection.', description: 'Internal review note.' })
  @IsOptional()
  @IsString()
  reviewNotes?: string;
}

export class ConfirmEstatePaymentDto {
  @ApiPropertyOptional({
    example: 'FLU-1778881112222-a1b2c3d4',
    description: 'Confirm a specific payment reference. Latest pending payment is used if omitted. Hosted checkout payments are verified against the provider sandbox before activation.'
  })
  @IsOptional()
  @IsString()
  paymentReference?: string;
}

export class UpdatePaymentProviderSettingDto {
  @ApiProperty({ example: true, description: 'Whether the provider is enabled.' })
  @IsBoolean()
  enabled!: boolean;
}

export class UpdateSubscriptionPlanDto {
  @ApiPropertyOptional({ example: 'Starter', description: 'Plan display name.' })
  @IsOptional()
  @IsString()
  name?: string;

  @ApiPropertyOptional({ example: 0, description: 'Minimum residents/units for this plan.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  minResidents?: number;

  @ApiPropertyOptional({ example: 100, description: 'Maximum residents/units. Omit for no upper bound.' })
  @IsOptional()
  @IsInt()
  @Min(1)
  maxResidents?: number;

  @ApiPropertyOptional({ example: 20000000, description: 'Monthly price in kobo.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  monthlyPriceKobo?: number;

  @ApiPropertyOptional({ example: 30000000, description: 'Previous monthly price in kobo for discounted/strikethrough display.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  oldMonthlyPriceKobo?: number;

  @ApiPropertyOptional({ example: 'NGN', description: 'Plan currency.' })
  @IsOptional()
  @IsString()
  currency?: string;

  @ApiPropertyOptional({ example: false, description: 'Whether pricing is custom/manual.' })
  @IsOptional()
  @IsBoolean()
  customPricing?: boolean;

  @ApiPropertyOptional({ example: '0-100 residents/units', description: 'Plan description.' })
  @IsOptional()
  @IsString()
  description?: string;

  @ApiPropertyOptional({ example: true, description: 'Whether this plan is selectable.' })
  @IsOptional()
  @IsBoolean()
  isActive?: boolean;

  @ApiPropertyOptional({ example: 'Upgrade to Secure', description: 'CTA label shown to admins.' })
  @IsOptional()
  @IsString()
  ctaLabel?: string;

  @ApiPropertyOptional({ example: 20, description: 'Ordering position for public and resident comparisons.' })
  @IsOptional()
  @IsInt()
  sortOrder?: number;

  @ApiPropertyOptional({ example: 30, description: 'Free trial days.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  trialDays?: number;

  @ApiPropertyOptional({ example: 30, description: 'Grace period days after payment or billing issue.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  gracePeriodDays?: number;

  @ApiPropertyOptional({ example: true, description: 'Whether this plan is shown publicly.' })
  @IsOptional()
  @IsBoolean()
  publicVisible?: boolean;

  @ApiPropertyOptional({ example: true, description: 'Whether residents can see feature comparisons for this plan.' })
  @IsOptional()
  @IsBoolean()
  residentMarketingEnabled?: boolean;

  @ApiPropertyOptional({ example: { gates: 3, residents: 500, guards: 10 }, description: 'Plan limits used by backend enforcement.' })
  @IsOptional()
  @IsObject()
  limits?: Record<string, unknown>;

  @ApiPropertyOptional({ example: { recommended: true }, description: 'Plan display and operational metadata.' })
  @IsOptional()
  @IsObject()
  metadata?: Record<string, unknown>;

  @ApiPropertyOptional({
    enum: FeatureKey,
    isArray: true,
    example: [FeatureKey.RESIDENTS_VIEW, FeatureKey.VISITOR_PASSES_CREATE, FeatureKey.CHECKIN_SCAN],
    description: 'Feature keys included in this plan. Supplying this replaces the plan feature list.'
  })
  @IsOptional()
  @IsArray()
  @ArrayUnique()
  @IsEnum(FeatureKey, { each: true })
  features?: FeatureKey[];
}

export class CreateSubscriptionPlanDto {
  @ApiProperty({ example: 'CUSTOM_LUXURY', description: 'Unique plan code. Use stable uppercase snake case.' })
  @IsString()
  code!: string;

  @ApiProperty({ example: 'Luxury Custom', description: 'Plan display name.' })
  @IsString()
  name!: string;

  @ApiProperty({ example: 301, description: 'Minimum residents/units for this plan.' })
  @IsInt()
  @Min(0)
  minResidents!: number;

  @ApiPropertyOptional({ example: null, description: 'Maximum residents/units. Omit for no upper bound.' })
  @IsOptional()
  @IsInt()
  @Min(1)
  maxResidents?: number;

  @ApiPropertyOptional({ example: 150000000, description: 'Monthly price in kobo. May be supplied for custom plans.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  monthlyPriceKobo?: number;

  @ApiPropertyOptional({ example: 200000000, description: 'Previous monthly price in kobo for discounted/strikethrough display.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  oldMonthlyPriceKobo?: number;

  @ApiPropertyOptional({ example: 'NGN', description: 'Plan currency.' })
  @IsOptional()
  @IsString()
  currency?: string;

  @ApiPropertyOptional({ example: true, description: 'Whether pricing is custom/manual.' })
  @IsOptional()
  @IsBoolean()
  customPricing?: boolean;

  @ApiPropertyOptional({ example: 'Custom-priced plan for high-end estates.', description: 'Plan description.' })
  @IsOptional()
  @IsString()
  description?: string;

  @ApiPropertyOptional({ example: true, description: 'Whether this plan is selectable.' })
  @IsOptional()
  @IsBoolean()
  isActive?: boolean;

  @ApiPropertyOptional({ example: 'Talk to GateWise', description: 'CTA label shown to admins.' })
  @IsOptional()
  @IsString()
  ctaLabel?: string;

  @ApiPropertyOptional({ example: 40, description: 'Ordering position for public and resident comparisons.' })
  @IsOptional()
  @IsInt()
  sortOrder?: number;

  @ApiPropertyOptional({ example: 30, description: 'Free trial days.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  trialDays?: number;

  @ApiPropertyOptional({ example: 30, description: 'Grace period days after payment or billing issue.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  gracePeriodDays?: number;

  @ApiPropertyOptional({ example: true, description: 'Whether this plan is shown publicly.' })
  @IsOptional()
  @IsBoolean()
  publicVisible?: boolean;

  @ApiPropertyOptional({ example: true, description: 'Whether residents can see feature comparisons for this plan.' })
  @IsOptional()
  @IsBoolean()
  residentMarketingEnabled?: boolean;

  @ApiPropertyOptional({ example: { gates: 10, residents: 5000 }, description: 'Plan limits used by backend enforcement.' })
  @IsOptional()
  @IsObject()
  limits?: Record<string, unknown>;

  @ApiPropertyOptional({ example: { recommendedFor: 'Large estates' }, description: 'Plan display and operational metadata.' })
  @IsOptional()
  @IsObject()
  metadata?: Record<string, unknown>;

  @ApiPropertyOptional({
    enum: FeatureKey,
    isArray: true,
    example: [FeatureKey.RESIDENTS_VIEW, FeatureKey.GUARDS_VIEW, FeatureKey.CHECKIN_SCAN],
    description: 'Feature keys included in this plan. Estate admins cannot enable features outside this list.'
  })
  @IsOptional()
  @IsArray()
  @ArrayUnique()
  @IsEnum(FeatureKey, { each: true })
  features?: FeatureKey[];
}

export class UpdateSalesPartnerProfileDto {
  @ApiPropertyOptional({ example: true, description: 'Enable partner earning status for this sales admin.' })
  @IsOptional()
  @IsBoolean()
  enabled?: boolean;

  @ApiPropertyOptional({ example: 7.5, description: 'Commission percentage earned on confirmed payments for assigned estates.' })
  @IsOptional()
  @IsNumber()
  @Min(0)
  @Max(100)
  commissionPercent?: number;

  @ApiPropertyOptional({ example: 'Strategic partner for Lekki estates.', description: 'Internal partner notes.' })
  @IsOptional()
  @IsString()
  notes?: string;
}

export class AssignEstatePartnerDto {
  @ApiPropertyOptional({
    example: USER_ID_EXAMPLE,
    description: 'Sales admin user ID to assign to this estate. Omit or set null to clear the partner assignment.'
  })
  @IsOptional()
  @IsString()
  salesAdminId?: string | null;
}

export class ContactEstateAdminDto {
  @ApiProperty({ enum: ['SMS', 'WHATSAPP', 'EMAIL', 'CALL'], example: 'SMS', description: 'Contact channel to queue or record.' })
  @IsIn(['SMS', 'WHATSAPP', 'EMAIL', 'CALL'])
  channel!: 'SMS' | 'WHATSAPP' | 'EMAIL' | 'CALL';

  @ApiProperty({
    example: 'Please complete your subscription payment so we can activate your estate.',
    description: 'Message or call note for the assigned estate admin.'
  })
  @IsString()
  message!: string;

  @ApiPropertyOptional({ example: { reason: 'PAYMENT_PENDING' }, description: 'Additional context for audit and frontend history.' })
  @IsOptional()
  @IsObject()
  metadata?: Record<string, unknown>;
}

export class UpdateOtpConfigDto {
  @ApiPropertyOptional({
    enum: OtpDeliveryProvider,
    example: OtpDeliveryProvider.DEV,
    description:
      'Shortcut for setting the primary OTP provider. DEV keeps local seeded OTP behavior; Termii and Africa\'s Talking send real SMS; CUSTOM_HTTP calls a super-admin supplied SMS endpoint.'
  })
  @IsOptional()
  @IsEnum(OtpDeliveryProvider)
  provider?: OtpDeliveryProvider;

  @ApiPropertyOptional({
    enum: OtpDeliveryProvider,
    isArray: true,
    example: [OtpDeliveryProvider.TERMII, OtpDeliveryProvider.AFRICAS_TALKING],
    description: 'Providers allowed to send OTPs. Disable a provider by removing it from this list.'
  })
  @IsOptional()
  @IsArray()
  @ArrayUnique()
  @IsEnum(OtpDeliveryProvider, { each: true })
  enabledProviders?: OtpDeliveryProvider[];

  @ApiPropertyOptional({
    enum: OtpDeliveryProvider,
    isArray: true,
    example: [OtpDeliveryProvider.TERMII, OtpDeliveryProvider.AFRICAS_TALKING, OtpDeliveryProvider.CUSTOM_HTTP],
    description: 'Fallback order for enabled providers. The first enabled provider is the primary delivery route.'
  })
  @IsOptional()
  @IsArray()
  @ArrayUnique()
  @IsEnum(OtpDeliveryProvider, { each: true })
  providerPriority?: OtpDeliveryProvider[];

  @ApiPropertyOptional({ example: 300, description: 'OTP lifetime in seconds.' })
  @IsOptional()
  @IsInt()
  @Min(60)
  ttlSeconds?: number;

  @ApiPropertyOptional({ example: 6, description: 'Numeric OTP length. Supported range is 4 to 8 digits.' })
  @IsOptional()
  @IsInt()
  @Min(4)
  @Max(8)
  codeLength?: number;

  @ApiPropertyOptional({ example: 5, description: 'Maximum verification attempts before the OTP challenge is invalidated.' })
  @IsOptional()
  @IsInt()
  @Min(1)
  maxAttempts?: number;

  @ApiPropertyOptional({ example: false, description: 'Whether login responses should include devOtp. Keep false outside local development.' })
  @IsOptional()
  @IsBoolean()
  exposeDevCode?: boolean;

  @ApiPropertyOptional({ example: 'https://api.ng.termii.com', description: 'Termii base URL from the Termii dashboard.' })
  @IsOptional()
  @IsString()
  termiiBaseUrl?: string;

  @ApiPropertyOptional({ example: 'TERMII_API_KEY', description: 'Termii API key. Stored in memory for the running process.' })
  @IsOptional()
  @IsString()
  termiiApiKey?: string;

  @ApiPropertyOptional({ example: 'SmartEstate', description: 'Approved Termii sender ID.' })
  @IsOptional()
  @IsString()
  termiiSenderId?: string;

  @ApiPropertyOptional({ example: 'dnd', description: 'Termii SMS route. Use dnd for transactional OTP when enabled on the Termii account.' })
  @IsOptional()
  @IsString()
  termiiChannel?: string;

  @ApiPropertyOptional({ example: 'https://api.africastalking.com/version1/messaging', description: "Africa's Talking SMS endpoint." })
  @IsOptional()
  @IsString()
  africasTalkingBaseUrl?: string;

  @ApiPropertyOptional({ example: 'smart-estate', description: "Africa's Talking app username. Use sandbox for their sandbox environment." })
  @IsOptional()
  @IsString()
  africasTalkingUsername?: string;

  @ApiPropertyOptional({ example: 'AFRICASTALKING_API_KEY', description: "Africa's Talking API key. Stored in memory for the running process." })
  @IsOptional()
  @IsString()
  africasTalkingApiKey?: string;

  @ApiPropertyOptional({ example: 'SmartEstate', description: "Africa's Talking sender ID, if approved for the account." })
  @IsOptional()
  @IsString()
  africasTalkingSenderId?: string;

  @ApiPropertyOptional({ example: 'https://sms.example.com/send', description: 'Custom HTTP SMS endpoint for self-managed OTP delivery.' })
  @IsOptional()
  @IsString()
  customHttpUrl?: string;

  @ApiPropertyOptional({ example: 'POST', description: 'Custom HTTP method. POST is recommended.' })
  @IsOptional()
  @IsString()
  customHttpMethod?: string;

  @ApiPropertyOptional({
    example: '{"Authorization":"Bearer sms_api_key"}',
    description: 'JSON object of headers for the custom SMS endpoint.'
  })
  @IsOptional()
  @IsString()
  customHttpHeadersJson?: string;

  @ApiPropertyOptional({
    example: '{"to":"{{phone}}","message":"{{message}}","code":"{{otp}}"}',
    description: 'JSON template for the custom SMS request body. Supported placeholders: {{phone}}, {{otp}}, {{code}}, {{message}}.'
  })
  @IsOptional()
  @IsString()
  customHttpBodyTemplate?: string;
}

export class UpdateIntegrationConfigDto {
  @ApiPropertyOptional({
    example: 'PAYMENT',
    description: 'Integration category. Required only when creating a custom provider. Supported values: PAYMENT, SMS, EMAIL, CUSTOM.'
  })
  @IsOptional()
  @IsString()
  category?: string;

  @ApiPropertyOptional({ example: 'Flutterwave', description: 'Display name shown in the super-admin UI.' })
  @IsOptional()
  @IsString()
  displayName?: string;

  @ApiPropertyOptional({ example: true, description: 'Whether this integration profile is enabled.' })
  @IsOptional()
  @IsBoolean()
  enabled?: boolean;

  @ApiPropertyOptional({
    example: { baseUrl: 'https://api.flutterwave.com/v3', publicKey: 'FLWPUBK_LIVE_xxx' },
    description: 'Non-secret values that may be returned to the super-admin UI.'
  })
  @IsOptional()
  @IsObject()
  publicConfig?: Record<string, unknown>;

  @ApiPropertyOptional({
    example: { secretKey: 'FLWSECK_LIVE_xxx', encryptionKey: 'FLW_ENCRYPTION_KEY' },
    description: 'Secret values. These are encrypted at rest and never returned by GET responses.'
  })
  @IsOptional()
  @IsObject()
  secretConfig?: Record<string, unknown>;

  @ApiPropertyOptional({ example: ['publicKey'], description: 'Public config keys to remove.' })
  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  clearPublicKeys?: string[];

  @ApiPropertyOptional({ example: ['secretKey'], description: 'Secret config keys to remove.' })
  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  clearSecretKeys?: string[];
}

export class ProviderParamDto {
  @ApiProperty({ enum: PaymentProvider, example: PaymentProvider.FLUTTERWAVE })
  @IsEnum(PaymentProvider)
  provider!: PaymentProvider;
}

export class UpdateUserDto {
  @ApiPropertyOptional({ example: 'Updated User', description: 'Updated user display name.' })
  @IsOptional()
  @IsString()
  name?: string;

  @ApiPropertyOptional({ example: '+2348000000999', description: 'Updated unique phone number.' })
  @IsOptional()
  @IsPhoneNumber()
  phone?: string;

  @ApiPropertyOptional({ example: 'Ade', description: 'Updated first name.' })
  @IsOptional()
  @IsString()
  firstName?: string;

  @ApiPropertyOptional({ example: 'Okafor', description: 'Updated last name.' })
  @IsOptional()
  @IsString()
  lastName?: string;

  @ApiPropertyOptional({ example: 'ade@example.com', description: 'Updated email address.' })
  @IsOptional()
  @IsString()
  email?: string;

  @ApiPropertyOptional({ example: 'Block A, Flat 12', description: 'Updated address.' })
  @IsOptional()
  @IsString()
  address?: string;

  @ApiPropertyOptional({ enum: AccountStatus, example: AccountStatus.SUSPENDED, description: 'Account status.' })
  @IsOptional()
  @IsEnum(AccountStatus)
  status?: AccountStatus;

  @ApiPropertyOptional({ example: ESTATE_ID_EXAMPLE, description: 'Move user to another estate. Use carefully.' })
  @IsOptional()
  @IsString()
  estateId?: string;
}

export class UpdateResidentDto {
  @ApiPropertyOptional({ example: 'Ade Okafor', description: 'Updated resident name.' })
  @IsOptional()
  @IsString()
  name?: string;

  @ApiPropertyOptional({ example: '+2348000000300', description: 'Updated resident phone.' })
  @IsOptional()
  @IsPhoneNumber()
  phone?: string;

  @ApiPropertyOptional({ example: 'A12', description: 'Updated resident unit.' })
  @IsOptional()
  @IsString()
  unit?: string;

  @ApiPropertyOptional({ example: 'Ade', description: 'Updated first name.' })
  @IsOptional()
  @IsString()
  firstName?: string;

  @ApiPropertyOptional({ example: 'Okafor', description: 'Updated last name.' })
  @IsOptional()
  @IsString()
  lastName?: string;

  @ApiPropertyOptional({ example: 'Block A', description: 'Updated block number.' })
  @IsOptional()
  @IsString()
  blockNumber?: string;

  @ApiPropertyOptional({ example: 'Flat 12', description: 'Updated flat or apartment number.' })
  @IsOptional()
  @IsString()
  flatNumber?: string;

  @ApiPropertyOptional({ example: 'Block A, Flat 12', description: 'Updated address.' })
  @IsOptional()
  @IsString()
  address?: string;

  @ApiPropertyOptional({ example: 'ade@example.com', description: 'Updated email address.' })
  @IsOptional()
  @IsString()
  email?: string;

  @ApiPropertyOptional({ example: 'https://cdn.example.com/profile.jpg', description: 'Updated profile picture URL.' })
  @IsOptional()
  @IsString()
  profilePictureUrl?: string;

  @ApiPropertyOptional({ example: true, description: 'Whether resident confirmed the profile.' })
  @IsOptional()
  @IsBoolean()
  profileConfirmed?: boolean;

  @ApiPropertyOptional({ enum: AccountStatus, example: AccountStatus.ACTIVE, description: 'Resident account status.' })
  @IsOptional()
  @IsEnum(AccountStatus)
  status?: AccountStatus;
}

export class UpdateGuardDto {
  @ApiPropertyOptional({ example: 'Ade Okafor', description: 'Updated guard name.' })
  @IsOptional()
  @IsString()
  name?: string;

  @ApiPropertyOptional({ example: '+2348000000200', description: 'Updated guard phone.' })
  @IsOptional()
  @IsPhoneNumber()
  phone?: string;

  @ApiPropertyOptional({ example: 'guard-device-002', description: 'Updated guard device assignment.' })
  @IsOptional()
  @IsString()
  deviceId?: string;

  @ApiPropertyOptional({ example: 'Musa', description: 'Updated first name.' })
  @IsOptional()
  @IsString()
  firstName?: string;

  @ApiPropertyOptional({ example: 'John', description: 'Updated last name.' })
  @IsOptional()
  @IsString()
  lastName?: string;

  @ApiPropertyOptional({ example: 'gate-uuid', description: 'Assigned gate ID.' })
  @IsOptional()
  @IsString()
  assignedGateId?: string;

  @ApiPropertyOptional({ example: '12 Unity Road', description: 'Updated guard address.' })
  @IsOptional()
  @IsString()
  address?: string;

  @ApiPropertyOptional({ example: 'https://cdn.example.com/guard.jpg', description: 'Updated profile picture URL.' })
  @IsOptional()
  @IsString()
  profilePictureUrl?: string;

  @ApiPropertyOptional({ example: { name: 'Emergency Contact', phone: '+2348000000999' }, description: 'Emergency contact object.' })
  @IsOptional()
  @IsObject()
  emergencyContact?: Record<string, unknown>;

  @ApiPropertyOptional({ example: [{ day: 'MONDAY', startsAt: '08:00', endsAt: '18:00' }], description: 'Assigned shift metadata.' })
  @IsOptional()
  assignedShifts?: unknown[];

  @ApiPropertyOptional({ example: true, description: 'Whether guard confirmed the profile.' })
  @IsOptional()
  @IsBoolean()
  profileConfirmed?: boolean;

  @ApiPropertyOptional({ enum: AccountStatus, example: AccountStatus.ACTIVE, description: 'Guard account status.' })
  @IsOptional()
  @IsEnum(AccountStatus)
  status?: AccountStatus;
}

export class UpdateVisitorDto {
  @ApiPropertyOptional({ example: ESTATE_ID_EXAMPLE, description: 'Move visitor record to another estate.' })
  @IsOptional()
  @IsString()
  estateId?: string;

  @ApiPropertyOptional({ example: 'John Osetese', description: 'Updated visitor name.' })
  @IsOptional()
  @IsString()
  name?: string;

  @ApiPropertyOptional({ example: '+2348111111111', description: 'Updated visitor phone.' })
  @IsOptional()
  @IsPhoneNumber()
  phone?: string;

  @ApiPropertyOptional({ example: 'https://example.com/photo.jpg', description: 'Updated visitor photo URL.' })
  @IsOptional()
  @IsString()
  photoUrl?: string;

  @ApiPropertyOptional({ example: 'LND-123-AB', description: 'Updated vehicle plate.' })
  @IsOptional()
  @IsString()
  vehiclePlate?: string;

  @ApiPropertyOptional({ example: 45, description: 'Visitor risk score from 0 to 100.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  @Max(100)
  riskScore?: number;

  @ApiPropertyOptional({ example: true, description: 'Whether visitor is internally flagged.' })
  @IsOptional()
  @IsBoolean()
  flagged?: boolean;

  @ApiPropertyOptional({ example: false, description: 'Whether visitor is blacklisted.' })
  @IsOptional()
  @IsBoolean()
  blacklisted?: boolean;

  @ApiPropertyOptional({ example: 'Missed checkout twice.', description: 'Internal risk notes.' })
  @IsOptional()
  @IsString()
  riskNotes?: string;
}

export class UpdateVisitorPassDto {
  @ApiPropertyOptional({ enum: VisitorPassStatus, example: VisitorPassStatus.REVOKED, description: 'Visitor pass status.' })
  @IsOptional()
  @IsEnum(VisitorPassStatus)
  status?: VisitorPassStatus;

  @ApiPropertyOptional({ enum: VisitorPassType, example: VisitorPassType.MULTI_ENTRY, description: 'Visitor pass type.' })
  @IsOptional()
  @IsEnum(VisitorPassType)
  passType?: VisitorPassType;

  @ApiPropertyOptional({ example: '2026-05-16T08:00:00.000Z', description: 'Updated validity start.' })
  @IsOptional()
  @Type(() => Date)
  @IsDate()
  validFrom?: Date;

  @ApiPropertyOptional({ example: '2026-05-16T18:00:00.000Z', description: 'Updated validity end.' })
  @IsOptional()
  @Type(() => Date)
  @IsDate()
  validUntil?: Date;

  @ApiPropertyOptional({ example: 3, description: 'Updated maximum allowed entries.' })
  @IsOptional()
  @IsInt()
  @Min(1)
  maxEntries?: number;

  @ApiPropertyOptional({ example: 0, description: 'Updated used entry count.' })
  @IsOptional()
  @IsInt()
  @Min(0)
  entryCount?: number;
}

export class UpdateIncidentDto {
  @ApiPropertyOptional({ example: GUARD_ID_EXAMPLE, description: 'Reassign incident to another guard.' })
  @IsOptional()
  @IsString()
  guardId?: string;

  @ApiPropertyOptional({ example: 'SECURITY', description: 'Updated incident type.' })
  @IsOptional()
  @IsString()
  type?: string;

  @ApiPropertyOptional({ example: 'Updated incident description', description: 'Updated incident details.' })
  @IsOptional()
  @IsString()
  description?: string;

  @ApiPropertyOptional({ example: { gate: 'Main Gate' }, description: 'Replace incident metadata JSON.' })
  @IsOptional()
  @IsObject()
  metadata?: Record<string, unknown>;
}
