import { BadRequestException, ConflictException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import {
  AccountStatus,
  EstateSignupStatus,
  EstateStatus,
  PaymentProvider,
  PaymentStatus,
  Prisma,
  SubscriptionStatus,
  UserRole,
  VisitorPassStatus
} from '@prisma/client';
import { AuditService } from '../audit/audit.service';
import { OtpService } from '../auth/otp.service';
import { calculateProfileCompletion } from '../common/profile-completion';
import { RequestUser } from '../common/request-user';
import { PrismaService } from '../database/prisma.service';
import { FeatureAccessService } from '../features/feature-access.service';
import { FeatureKey } from '../features/feature-catalog';
import { IntegrationConfigService } from '../integrations/integration-config.service';
import { PaymentGatewayService } from '../payments/payment-gateway.service';
import {
  UpdateEstateDto,
  AssignEstatePartnerDto,
  ContactEstateAdminDto,
  ConfirmEstatePaymentDto,
  CreateSubscriptionPlanDto,
  UpdateGuardDto,
  UpdateIncidentDto,
  UpdatePaymentProviderSettingDto,
  UpdateResidentDto,
  UpdateSubscriptionPlanDto,
  UpdateUserDto,
  UpdateVisitorDto,
  UpdateVisitorPassDto,
  ReviewEstateSignupDto,
  UpdateSalesPartnerProfileDto,
  UpdateOtpConfigDto,
  UpdateIntegrationConfigDto
} from './dto/admin.dto';

@Injectable()
export class AdminService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly audit: AuditService,
    private readonly paymentGateway: PaymentGatewayService,
    private readonly otp: OtpService,
    private readonly integrations: IntegrationConfigService,
    private readonly featureAccess: FeatureAccessService
  ) {}

  async dashboard(actor: RequestUser) {
    const canViewPayments = await this.canViewPaymentDetails(actor);
    const estateWhere = await this.resolveManagedEstateWhere(actor);
    const estateId = await this.resolveManagedEstateIdFilter(actor);
    const [
      estates,
      pendingEstateSignups,
      pendingPayments,
      users,
      residents,
      guards,
      visitors,
      activePasses,
      openVisits,
      gates,
      residentRequests,
      announcements,
      incidents,
      recentAuditLogs
    ] =
      await Promise.all([
        this.prisma.estate.count({ where: estateWhere }),
        this.prisma.estate.count({ where: { ...estateWhere, signupStatus: EstateSignupStatus.PENDING_APPROVAL } }),
        canViewPayments
          ? this.prisma.estatePayment.count({ where: { status: PaymentStatus.PENDING, estate: estateWhere } })
          : Promise.resolve(null),
        this.prisma.user.count({ where: { estateId } }),
        this.prisma.resident.count({ where: { estateId } }),
        this.prisma.guard.count({ where: { estateId } }),
        this.prisma.visitor.count({ where: { estateId } }),
        this.prisma.visitorPass.count({ where: { estateId, status: VisitorPassStatus.ACTIVE } }),
        this.prisma.visitSession.count({ where: { estateId, status: 'CHECKED_IN' } }),
        this.prisma.estateGate.count({ where: { estateId } }),
        this.prisma.residentRequest.count({ where: { estateId } }),
        this.prisma.estateAnnouncement.count({ where: { estateId } }),
        this.prisma.incident.count({ where: { estateId } }),
        this.prisma.auditLog.findMany({
          where: { estateId, ...this.auditLogPaymentVisibilityWhere(canViewPayments) },
          orderBy: { timestamp: 'desc' },
          take: 20
        })
      ]);

    const totals: Record<string, number> = {
      estates,
      pendingEstateSignups,
      users,
      residents,
      guards,
      visitors,
      activePasses,
      openVisits,
      gates,
      residentRequests,
      announcements,
      incidents
    };
    if (typeof pendingPayments === 'number') {
      totals.pendingPayments = pendingPayments;
    }

    return {
      totals,
      recentAuditLogs
    };
  }

  async listUsers(actor: RequestUser, estateId?: string, role?: string, status?: string) {
    const scopedEstateId = await this.resolveManagedEstateIdFilter(actor, estateId);
    return this.prisma.user.findMany({
      where: {
        estateId: scopedEstateId,
        role: role as Prisma.EnumUserRoleFilter | undefined,
        status: status as Prisma.EnumAccountStatusFilter | undefined
      },
      orderBy: { createdAt: 'desc' },
      take: 100,
      include: { estate: true, resident: true, guard: true }
    });
  }

  async listEstates(actor: RequestUser) {
    return this.prisma.estate.findMany({
      where: await this.resolveManagedEstateWhere(actor),
      orderBy: { createdAt: 'desc' },
      take: 100,
      include: { salesPartner: { select: { id: true, name: true, phone: true, role: true } } }
    });
  }

  async listEstateSignups(status: string | undefined, actor: RequestUser) {
    const include: Prisma.EstateInclude = {
      users: { where: { role: UserRole.ESTATE_ADMIN }, take: 1 },
      subscriptionPlan: true
    };
    if (await this.canViewPaymentDetails(actor)) {
      include.payments = { orderBy: { createdAt: 'desc' }, take: 5 };
    }

    return this.prisma.estate.findMany({
      where: { ...(await this.resolveManagedEstateWhere(actor)), signupStatus: status as Prisma.EnumEstateSignupStatusFilter | undefined },
      orderBy: { createdAt: 'desc' },
      take: 100,
      include
    });
  }

  async listSubscriptionPlans(includeInactive?: string) {
    const plans = await this.prisma.subscriptionPlan.findMany({
      where: includeInactive === 'true' ? undefined : { isActive: true },
      orderBy: { minResidents: 'asc' },
      include: { features: { orderBy: { feature: 'asc' } } }
    });
    return plans.map((plan) => this.serializeSubscriptionPlan(plan));
  }

  getOtpConfig() {
    return this.otp.getRuntimeConfig();
  }

  async updateOtpConfig(dto: UpdateOtpConfigDto, actor: RequestUser) {
    return this.withAdminAudit(
      actor,
      null,
      'ADMIN_OTP_CONFIG_UPDATED',
      async () => this.otp.updateRuntimeConfig(dto),
      {
        provider: dto.provider,
        enabledProviders: dto.enabledProviders,
        providerPriority: dto.providerPriority,
        ttlSeconds: dto.ttlSeconds,
        codeLength: dto.codeLength,
        maxAttempts: dto.maxAttempts,
        exposeDevCode: dto.exposeDevCode,
        termiiConfigured: Boolean(dto.termiiApiKey || dto.termiiSenderId || dto.termiiBaseUrl),
        africasTalkingConfigured: Boolean(dto.africasTalkingApiKey || dto.africasTalkingUsername || dto.africasTalkingBaseUrl),
        customHttpConfigured: Boolean(dto.customHttpUrl)
      }
    );
  }

  listIntegrationTemplates() {
    return this.integrations.listTemplates();
  }

  listIntegrationConfigs() {
    return this.integrations.listConfigs();
  }

  getIntegrationConfig(provider: string) {
    return this.integrations.getConfig(provider);
  }

  async updateIntegrationConfig(provider: string, dto: UpdateIntegrationConfigDto, actor: RequestUser) {
    return this.withAdminAudit(
      actor,
      null,
      'ADMIN_INTEGRATION_CONFIG_UPDATED',
      () => this.integrations.updateConfig(provider, dto, actor.id),
      {
        provider,
        category: dto.category,
        displayName: dto.displayName,
        enabled: dto.enabled,
        publicKeys: Object.keys(dto.publicConfig ?? {}),
        secretKeys: Object.keys(dto.secretConfig ?? {}),
        clearedPublicKeys: dto.clearPublicKeys,
        clearedSecretKeys: dto.clearSecretKeys
      }
    );
  }

  async createSubscriptionPlan(dto: CreateSubscriptionPlanDto, actor: RequestUser) {
    const { features, code, limits, metadata, ...data } = dto;
    return this.withAdminAudit(
      actor,
      null,
      'ADMIN_SUBSCRIPTION_PLAN_CREATED',
      async () => {
        const plan = await this.prisma.subscriptionPlan.create({
          data: {
            ...data,
            code: code.trim().toUpperCase(),
            limits: this.toJson(limits ?? {}),
            metadata: this.toJson(metadata ?? {}),
            features: {
              create: (features ?? []).map((feature) => ({ feature }))
            }
          },
          include: { features: { orderBy: { feature: 'asc' } } }
        });
        return this.serializeSubscriptionPlan(plan);
      },
      { planCode: code, features: features ?? [] }
    );
  }

  async updateSubscriptionPlan(code: string, dto: UpdateSubscriptionPlanDto, actor: RequestUser) {
    const { features, limits, metadata, ...data } = dto;
    return this.withAdminAudit(
      actor,
      null,
      'ADMIN_SUBSCRIPTION_PLAN_UPDATED',
      async () => {
        const normalizedCode = code.trim().toUpperCase();
        const plan = await this.prisma.$transaction(async (tx) => {
          await tx.subscriptionPlan.update({
            where: { code: normalizedCode },
            data: {
              ...data,
              limits: limits === undefined ? undefined : this.toJson(limits),
              metadata: metadata === undefined ? undefined : this.toJson(metadata)
            }
          });

          if (features) {
            await tx.subscriptionPlanFeature.deleteMany({ where: { plan: { code: normalizedCode } } });
            if (features.length) {
              const updatedPlan = await tx.subscriptionPlan.findUniqueOrThrow({ where: { code: normalizedCode } });
              await tx.subscriptionPlanFeature.createMany({
                data: features.map((feature) => ({ planId: updatedPlan.id, feature })),
                skipDuplicates: true
              });
            }
          }

          return tx.subscriptionPlan.findUniqueOrThrow({
            where: { code: normalizedCode },
            include: { features: { orderBy: { feature: 'asc' } } }
          });
        });
        return this.serializeSubscriptionPlan(plan);
      },
      { planCode: code, features }
    );
  }

  async listPaymentProviders(estateId?: string) {
    const globals = await this.prisma.globalPaymentProviderSetting.findMany({ orderBy: { displayName: 'asc' } });
    const overrides = estateId
      ? await this.prisma.estatePaymentProviderSetting.findMany({ where: { estateId } })
      : [];

    return globals.map((global) => {
      const override = overrides.find((item) => item.provider === global.provider);
      const runtime = this.paymentGateway.getProviderRuntime(global.provider);
      return {
        provider: global.provider,
        displayName: global.displayName,
        globallyEnabled: global.enabled,
        estateEnabled: override?.enabled ?? null,
        effectiveEnabled: override?.enabled ?? global.enabled,
        sandboxMode: runtime.mode,
        sandboxConfigured: runtime.configured,
        sandboxBaseUrl: runtime.baseUrl,
        recommendedUseCases: global.recommendedUseCases,
        metadata: global.metadata
      };
    });
  }

  async updateGlobalPaymentProvider(provider: PaymentProvider, dto: UpdatePaymentProviderSettingDto, actor: RequestUser) {
    this.assertPaymentProvider(provider);
    return this.withAdminAudit(
      actor,
      null,
      'ADMIN_GLOBAL_PAYMENT_PROVIDER_UPDATED',
      () =>
        this.prisma.globalPaymentProviderSetting.update({
          where: { provider },
          data: { enabled: dto.enabled }
        }),
      { provider, enabled: dto.enabled }
    );
  }

  async updateEstatePaymentProvider(
    estateId: string,
    provider: PaymentProvider,
    dto: UpdatePaymentProviderSettingDto,
    actor: RequestUser
  ) {
    this.assertPaymentProvider(provider);
    return this.withAdminAudit(
      actor,
      estateId,
      'ADMIN_ESTATE_PAYMENT_PROVIDER_UPDATED',
      () =>
        this.prisma.estatePaymentProviderSetting.upsert({
          where: { estateId_provider: { estateId, provider } },
          create: { estateId, provider, enabled: dto.enabled },
          update: { enabled: dto.enabled }
        }),
      { estateId, provider, enabled: dto.enabled }
    );
  }

  async listSalesPartners() {
    const users = await this.prisma.user.findMany({
      where: { role: UserRole.SALES_ADMIN },
      orderBy: { createdAt: 'desc' },
      include: {
        salesPartnerProfile: true,
        salesPartnerEstates: {
          select: { id: true, name: true, location: true, status: true, signupStatus: true, subscriptionStatus: true }
        }
      }
    });
    return users.map((user) => this.serializeSalesPartner(user));
  }

  async getMySalesPartnerProfile(actor: RequestUser) {
    if (actor.role !== UserRole.SALES_ADMIN) {
      throw new ForbiddenException('Only sales admins can use the partner profile endpoint');
    }
    const user = await this.prisma.user.findUnique({
      where: { id: actor.id },
      include: {
        salesPartnerProfile: true,
        salesPartnerEstates: {
          select: { id: true, name: true, location: true, status: true, signupStatus: true, subscriptionStatus: true }
        }
      }
    });
    if (!user) {
      throw new NotFoundException('Sales admin not found');
    }
    return this.serializeSalesPartner(user);
  }

  async updateSalesPartnerProfile(salesAdminId: string, dto: UpdateSalesPartnerProfileDto, actor: RequestUser) {
    const salesAdmin = await this.prisma.user.findUnique({ where: { id: salesAdminId } });
    if (!salesAdmin || salesAdmin.role !== UserRole.SALES_ADMIN) {
      throw new NotFoundException('Sales admin not found');
    }

    return this.withAdminAudit(
      actor,
      null,
      'ADMIN_SALES_PARTNER_PROFILE_UPDATED',
      async () => {
        await this.prisma.salesPartnerProfile.upsert({
          where: { salesAdminId },
          create: {
            salesAdminId,
            enabled: dto.enabled ?? false,
            commissionPercent: dto.commissionPercent,
            notes: dto.notes,
            updatedById: actor.id
          },
          update: {
            enabled: dto.enabled,
            commissionPercent: dto.commissionPercent,
            notes: dto.notes,
            updatedById: actor.id
          }
        });
        const updated = await this.prisma.user.findUniqueOrThrow({
          where: { id: salesAdminId },
          include: {
            salesPartnerProfile: true,
            salesPartnerEstates: {
              select: { id: true, name: true, location: true, status: true, signupStatus: true, subscriptionStatus: true }
            }
          }
        });
        return this.serializeSalesPartner(updated);
      },
      {
        salesAdminId,
        enabled: dto.enabled,
        commissionPercent: dto.commissionPercent
      }
    );
  }

  async assignEstateSalesPartner(estateId: string, dto: AssignEstatePartnerDto, actor: RequestUser) {
    const salesAdminId = dto.salesAdminId ?? null;
    if (salesAdminId) {
      const salesAdmin = await this.prisma.user.findUnique({ where: { id: salesAdminId } });
      if (!salesAdmin || salesAdmin.role !== UserRole.SALES_ADMIN) {
        throw new NotFoundException('Sales admin not found');
      }
      await this.prisma.salesPartnerProfile.upsert({
        where: { salesAdminId },
        create: { salesAdminId, enabled: false, updatedById: actor.id },
        update: { updatedById: actor.id }
      });
    }

    return this.withAdminAudit(
      actor,
      estateId,
      'ADMIN_ESTATE_SALES_PARTNER_ASSIGNED',
      () =>
        this.prisma.estate.update({
          where: { id: estateId },
          data: { salesPartnerId: salesAdminId },
          include: { salesPartner: { select: { id: true, name: true, phone: true, role: true } } }
        }),
      { estateId, salesAdminId }
    );
  }

  async getSalesPartnerEarnings(salesAdminId: string, actor: RequestUser) {
    if (actor.role === UserRole.SALES_ADMIN && actor.id !== salesAdminId) {
      throw new ForbiddenException('Sales admins can only view their own partner earnings');
    }

    const salesAdmin = await this.prisma.user.findUnique({
      where: { id: salesAdminId },
      include: { salesPartnerProfile: true }
    });
    if (!salesAdmin || salesAdmin.role !== UserRole.SALES_ADMIN) {
      throw new NotFoundException('Sales admin not found');
    }

    const payments = await this.prisma.estatePayment.findMany({
      where: {
        status: PaymentStatus.CONFIRMED,
        estate: { salesPartnerId: salesAdminId }
      },
      include: {
        estate: { select: { id: true, name: true, location: true } },
        plan: { select: { code: true, name: true } }
      },
      orderBy: { confirmedAt: 'desc' },
      take: 250
    });

    const profile = salesAdmin.salesPartnerProfile;
    const commissionPercent = profile?.enabled ? Number(profile.commissionPercent ?? 0) : 0;
    const totalSalesKobo = payments.reduce((sum, payment) => sum + (payment.amountKobo ?? 0), 0);
    const estimatedEarningsKobo = Math.round((totalSalesKobo * commissionPercent) / 100);

    return {
      salesAdmin: {
        id: salesAdmin.id,
        name: salesAdmin.name,
        phone: salesAdmin.phone,
        role: salesAdmin.role,
        profileLabel: profile?.enabled ? 'Partner' : 'Sales Admin'
      },
      partnerProfile: {
        enabled: profile?.enabled ?? false,
        commissionPercent,
        notes: profile?.notes ?? null
      },
      totals: {
        confirmedPayments: payments.length,
        totalSalesKobo,
        estimatedEarningsKobo,
        currency: payments[0]?.currency ?? 'NGN'
      },
      payments: payments.map((payment) => ({
        id: payment.id,
        estateId: payment.estateId,
        estate: payment.estate,
        plan: payment.plan,
        amountKobo: payment.amountKobo,
        currency: payment.currency,
        reference: payment.reference,
        confirmedAt: payment.confirmedAt
      }))
    };
  }

  getMySalesPartnerEarnings(actor: RequestUser) {
    return this.getSalesPartnerEarnings(actor.id, actor);
  }

  async contactEstateAdmin(estateId: string, dto: ContactEstateAdminDto, actor: RequestUser) {
    await this.assertCanAccessEstate(actor, estateId);
    const estate = await this.prisma.estate.findUnique({
      where: { id: estateId },
      include: { users: { where: { role: UserRole.ESTATE_ADMIN }, take: 10 } }
    });
    if (!estate) {
      throw new NotFoundException('Estate not found');
    }
    if (!estate.users.length) {
      throw new NotFoundException('Estate admin not found');
    }

    await this.audit.record({
      estateId,
      actorType: 'USER',
      actorId: actor.id,
      action: 'SALES_PARTNER_ESTATE_ADMIN_CONTACT_QUEUED',
      metadata: {
        channel: dto.channel,
        message: dto.message,
        recipients: estate.users.map((admin) => ({ id: admin.id, name: admin.name, phone: admin.phone })),
        ...dto.metadata
      }
    });

    return {
      status: 'QUEUED',
      estate: { id: estate.id, name: estate.name },
      recipients: estate.users.map((admin) => ({ id: admin.id, name: admin.name, phone: admin.phone })),
      channel: dto.channel
    };
  }

  async listResidents(actor: RequestUser, estateId?: string) {
    const scopedEstateId = await this.resolveManagedEstateIdFilter(actor, estateId);
    return this.prisma.resident.findMany({
      where: { estateId: scopedEstateId },
      orderBy: { createdAt: 'desc' },
      take: 100,
      include: { estate: true, user: true }
    });
  }

  async listGuards(actor: RequestUser, estateId?: string) {
    const scopedEstateId = await this.resolveManagedEstateIdFilter(actor, estateId);
    return this.prisma.guard.findMany({
      where: { estateId: scopedEstateId },
      orderBy: { createdAt: 'desc' },
      take: 100,
      include: { estate: true, user: true }
    });
  }

  async listVisitors(actor: RequestUser, estateId?: string) {
    const scopedEstateId = await this.resolveManagedEstateIdFilter(actor, estateId);
    return this.prisma.visitor.findMany({
      where: { estateId: scopedEstateId },
      orderBy: { createdAt: 'desc' },
      take: 100,
      include: { estate: true }
    });
  }

  async listVisitorPasses(actor: RequestUser, estateId?: string, status?: string) {
    const scopedEstateId = await this.resolveManagedEstateIdFilter(actor, estateId);
    return this.prisma.visitorPass.findMany({
      where: { estateId: scopedEstateId, status: status as Prisma.EnumVisitorPassStatusFilter | undefined },
      orderBy: { createdAt: 'desc' },
      take: 100,
      include: { estate: true, visitor: true, hostResident: true }
    });
  }

  async listVisitSessions(actor: RequestUser, estateId?: string) {
    const scopedEstateId = await this.resolveManagedEstateIdFilter(actor, estateId);
    return this.prisma.visitSession.findMany({
      where: { estateId: scopedEstateId },
      orderBy: { checkinTime: 'desc' },
      take: 100,
      include: { estate: true, visitorPass: { include: { visitor: true, hostResident: true } }, guard: true, gate: true }
    });
  }

  async listIncidents(actor: RequestUser, estateId?: string) {
    const scopedEstateId = await this.resolveManagedEstateIdFilter(actor, estateId);
    return this.prisma.incident.findMany({
      where: { estateId: scopedEstateId },
      orderBy: { timestamp: 'desc' },
      take: 100,
      include: { estate: true, guard: true }
    });
  }

  async listAuditLogs(estateId: string | undefined, actor: RequestUser) {
    const canViewPayments = await this.canViewPaymentDetails(actor);
    const scopedEstateId = await this.resolveManagedEstateIdFilter(actor, estateId);
    return this.prisma.auditLog.findMany({
      where: { estateId: scopedEstateId, ...this.auditLogPaymentVisibilityWhere(canViewPayments) },
      orderBy: { timestamp: 'desc' },
      take: 100
    });
  }

  async updateEstate(id: string, dto: UpdateEstateDto, actor: RequestUser) {
    await this.assertCanAccessEstate(actor, id);
    return this.withAdminAudit(
      actor,
      id,
      'ADMIN_ESTATE_UPDATED',
      () =>
        this.prisma.estate.update({
          where: { id },
          data: {
            name: dto.name,
            location: dto.location,
            unitCount: dto.unitCount,
            estateType: dto.estateType,
            settings: dto.settings as Prisma.InputJsonValue | undefined,
            branding: dto.branding as Prisma.InputJsonValue | undefined
          }
        }),
      { estateId: id }
    );
  }

  async updateUser(id: string, dto: UpdateUserDto, actor: RequestUser) {
    const existing = await this.prisma.user.findUnique({ where: { id }, select: { estateId: true } });
    if (!existing) {
      throw new NotFoundException('Record not found');
    }
    await this.assertCanAccessUserEstate(actor, existing.estateId);
    if (dto.estateId) {
      await this.assertCanAccessEstate(actor, dto.estateId);
    }
    const auditEstateId = dto.estateId ?? existing.estateId ?? null;
    return this.withAdminAudit(
      actor,
      auditEstateId,
      'ADMIN_USER_UPDATED',
      () =>
        this.prisma.$transaction(async (tx) => {
          const user = await tx.user.update({
            where: { id },
            data: {
              name: dto.name,
              phone: dto.phone,
              firstName: dto.firstName,
              lastName: dto.lastName,
              email: dto.email,
              address: dto.address,
              status: dto.status,
              estateId: dto.estateId
            }
          });

          await tx.resident.updateMany({
            where: { userId: id },
            data: {
              name: dto.name,
              phone: dto.phone,
              firstName: dto.firstName,
              lastName: dto.lastName,
              email: dto.email,
              address: dto.address,
              status: dto.status,
              estateId: dto.estateId
            }
          });
          await tx.guard.updateMany({
            where: { userId: id },
            data: {
              name: dto.name,
              phone: dto.phone,
              firstName: dto.firstName,
              lastName: dto.lastName,
              address: dto.address,
              status: dto.status,
              estateId: dto.estateId
            }
          });

          return user;
        }),
      { userId: id }
    );
  }

  async updateResident(id: string, dto: UpdateResidentDto, actor: RequestUser) {
    const existing = await this.prisma.resident.findUnique({ where: { id } });
    if (!existing) {
      throw new NotFoundException('Record not found');
    }
    await this.assertCanAccessEstate(actor, existing.estateId);
    return this.withAdminAudit(
      actor,
      null,
      'ADMIN_RESIDENT_UPDATED',
      () =>
        this.prisma.$transaction(async (tx) => {
          const completion = calculateProfileCompletion(UserRole.RESIDENT, {
            ...existing,
            ...dto,
            flatNumber: dto.flatNumber ?? dto.unit ?? existing.flatNumber,
            unit: dto.flatNumber ?? dto.unit ?? existing.unit,
            profileConfirmed: dto.profileConfirmed ?? existing.profileConfirmed
          });
          const resident = await tx.resident.update({
            where: { id },
            data: {
              name: dto.name,
              phone: dto.phone,
              unit: dto.flatNumber ?? dto.unit,
              firstName: dto.firstName,
              lastName: dto.lastName,
              blockNumber: dto.blockNumber,
              flatNumber: dto.flatNumber,
              address: dto.address,
              email: dto.email,
              profilePictureUrl: dto.profilePictureUrl,
              profileConfirmed: dto.profileConfirmed,
              status: dto.status,
              profileCompletion: completion.completion
            }
          });
          await tx.user.update({
            where: { id: resident.userId },
            data: {
              name: dto.name,
              phone: dto.phone,
              firstName: dto.firstName,
              lastName: dto.lastName,
              email: dto.email,
              address: dto.address,
              profilePictureUrl: dto.profilePictureUrl,
              profileConfirmed: dto.profileConfirmed,
              profileCompletion: completion.completion,
              status: dto.status
            }
          });
          return resident;
        }),
      { residentId: id }
    );
  }

  async updateGuard(id: string, dto: UpdateGuardDto, actor: RequestUser) {
    const existing = await this.prisma.guard.findUnique({ where: { id } });
    if (!existing) {
      throw new NotFoundException('Record not found');
    }
    await this.assertCanAccessEstate(actor, existing.estateId);
    return this.withAdminAudit(
      actor,
      null,
      'ADMIN_GUARD_UPDATED',
      () =>
        this.prisma.$transaction(async (tx) => {
          const completion = calculateProfileCompletion(UserRole.GUARD, {
            ...existing,
            ...dto,
            profileConfirmed: dto.profileConfirmed ?? existing.profileConfirmed
          });
          const guard = await tx.guard.update({
            where: { id },
            data: {
              name: dto.name,
              phone: dto.phone,
              deviceId: dto.deviceId,
              firstName: dto.firstName,
              lastName: dto.lastName,
              assignedGateId: dto.assignedGateId,
              assignedShifts: this.toJson(dto.assignedShifts),
              address: dto.address,
              emergencyContact: this.toJson(dto.emergencyContact),
              profilePictureUrl: dto.profilePictureUrl,
              profileConfirmed: dto.profileConfirmed,
              status: dto.status,
              profileCompletion: completion.completion
            }
          });
          await tx.user.update({
            where: { id: guard.userId },
            data: {
              name: dto.name,
              phone: dto.phone,
              firstName: dto.firstName,
              lastName: dto.lastName,
              address: dto.address,
              profilePictureUrl: dto.profilePictureUrl,
              profileConfirmed: dto.profileConfirmed,
              profileCompletion: completion.completion,
              status: dto.status
            }
          });
          return guard;
        }),
      { guardId: id }
    );
  }

  async updateVisitor(id: string, dto: UpdateVisitorDto, actor: RequestUser) {
    const existing = await this.prisma.visitor.findUnique({ where: { id }, select: { estateId: true } });
    if (!existing) {
      throw new NotFoundException('Record not found');
    }
    if (existing.estateId) {
      await this.assertCanAccessEstate(actor, existing.estateId);
    }
    if (dto.estateId) {
      await this.assertCanAccessEstate(actor, dto.estateId);
    }
    return this.withAdminAudit(
      actor,
      dto.estateId ?? null,
      'ADMIN_VISITOR_UPDATED',
      () => this.prisma.visitor.update({ where: { id }, data: dto }),
      { visitorId: id }
    );
  }

  async updateVisitorPass(id: string, dto: UpdateVisitorPassDto, actor: RequestUser) {
    const existing = await this.prisma.visitorPass.findUnique({ where: { id }, select: { estateId: true } });
    if (!existing) {
      throw new NotFoundException('Record not found');
    }
    await this.assertCanAccessEstate(actor, existing.estateId);
    return this.withAdminAudit(
      actor,
      null,
      'ADMIN_VISITOR_PASS_UPDATED',
      () =>
        this.prisma.visitorPass.update({
          where: { id },
          data: {
            status: dto.status,
            passType: dto.passType,
            validFrom: dto.validFrom,
            validUntil: dto.validUntil,
            maxEntries: dto.maxEntries,
            entryCount: dto.entryCount,
            revokedAt: dto.status === VisitorPassStatus.REVOKED ? new Date() : undefined
          }
        }),
      { visitorPassId: id }
    );
  }

  async updateIncident(id: string, dto: UpdateIncidentDto, actor: RequestUser) {
    const existing = await this.prisma.incident.findUnique({ where: { id }, select: { estateId: true } });
    if (!existing) {
      throw new NotFoundException('Record not found');
    }
    await this.assertCanAccessEstate(actor, existing.estateId);
    return this.withAdminAudit(
      actor,
      null,
      'ADMIN_INCIDENT_UPDATED',
      () =>
        this.prisma.incident.update({
          where: { id },
          data: {
            guardId: dto.guardId,
            type: dto.type,
            description: dto.description,
            metadata: dto.metadata as Prisma.InputJsonValue | undefined
          }
        }),
      { incidentId: id }
    );
  }

  async reviewEstateSignup(estateId: string, dto: ReviewEstateSignupDto, actor: RequestUser) {
    await this.assertCanAccessEstate(actor, estateId);
    if (dto.status === EstateSignupStatus.PENDING_APPROVAL) {
      throw new BadRequestException('Review status must be APPROVED, REJECTED, or SUSPENDED');
    }

    const statusData =
      dto.status === EstateSignupStatus.APPROVED
        ? {
            status: EstateStatus.PENDING,
            signupStatus: EstateSignupStatus.APPROVED,
            rejectedAt: null,
            suspendedAt: null
          }
        : dto.status === EstateSignupStatus.REJECTED
          ? {
              status: EstateStatus.REJECTED,
              signupStatus: EstateSignupStatus.REJECTED,
              rejectedAt: new Date(),
              suspendedAt: null
            }
          : {
              status: EstateStatus.SUSPENDED,
              signupStatus: EstateSignupStatus.SUSPENDED,
              suspendedAt: new Date()
            };

    return this.withAdminAudit(
      actor,
      estateId,
      'ADMIN_ESTATE_SIGNUP_REVIEWED',
      () =>
        this.prisma.$transaction(async (tx) => {
          const estate = await tx.estate.update({
            where: { id: estateId },
            data: {
              ...statusData,
              reviewNotes: dto.reviewNotes
            }
          });

          if (dto.status === EstateSignupStatus.REJECTED) {
            await tx.user.updateMany({
              where: { estateId, role: UserRole.ESTATE_ADMIN },
              data: { status: AccountStatus.DISABLED }
            });
          }

          if (dto.status === EstateSignupStatus.SUSPENDED) {
            await tx.user.updateMany({
              where: { estateId, role: UserRole.ESTATE_ADMIN },
              data: { status: AccountStatus.SUSPENDED }
            });
          }

          return estate;
        }),
      { estateId, signupStatus: dto.status, reviewNotes: dto.reviewNotes }
    );
  }

  async confirmEstatePayment(estateId: string, dto: ConfirmEstatePaymentDto, actor: RequestUser) {
    await this.assertCanAccessEstate(actor, estateId);
    return this.withAdminAudit(
      actor,
      estateId,
      'ADMIN_ESTATE_PAYMENT_CONFIRMED',
      async () => {
        const payment = dto.paymentReference
          ? await this.prisma.estatePayment.findFirst({
              where: { estateId, reference: dto.paymentReference, status: PaymentStatus.PENDING },
              orderBy: { createdAt: 'desc' }
            })
          : await this.prisma.estatePayment.findFirst({
              where: { estateId, status: PaymentStatus.PENDING },
              orderBy: { createdAt: 'desc' }
            });

        if (!payment) {
          throw new NotFoundException('Pending payment not found');
        }

        const gatewayVerification = await this.verifyGatewayPaymentIfNeeded(payment);

        return this.prisma.$transaction(async (tx) => {
          const confirmedPayment = await tx.estatePayment.update({
            where: { id: payment.id },
            data: {
              status: PaymentStatus.CONFIRMED,
              confirmedById: actor.id,
              confirmedAt: new Date(),
              metadata: gatewayVerification
                ? this.toJson({
                    ...this.jsonObject(payment.metadata),
                    gatewayVerification
                  })
                : undefined
            },
            include: { plan: true }
          });

          const estate = await tx.estate.update({
            where: { id: estateId },
            data: {
              status: EstateStatus.ACTIVE,
              signupStatus: EstateSignupStatus.APPROVED,
              subscriptionStatus: SubscriptionStatus.ACTIVE,
              subscriptionPlanId: confirmedPayment.planId,
              selectedPaymentProvider: confirmedPayment.provider,
              activatedAt: new Date(),
              rejectedAt: null,
              suspendedAt: null
            },
            include: { subscriptionPlan: true }
          });

          await tx.user.updateMany({
            where: { estateId, role: UserRole.ESTATE_ADMIN },
            data: { status: AccountStatus.ACTIVE }
          });

          return { estate, payment: confirmedPayment };
        });
      },
      { estateId, paymentReference: dto.paymentReference }
    );
  }

  private async verifyGatewayPaymentIfNeeded(payment: {
    provider: PaymentProvider;
    reference: string | null;
    amountKobo: number | null;
    currency: string;
    metadata: Prisma.JsonValue;
  }) {
    if (!this.paymentGateway.shouldVerifyGatewayPayment(payment.metadata)) {
      return null;
    }
    if (!payment.reference) {
      throw new BadRequestException('Gateway payment reference is missing');
    }

    const verification = await this.paymentGateway.verifyPayment({
      provider: payment.provider,
      reference: payment.reference,
      amountKobo: payment.amountKobo,
      currency: payment.currency,
      metadata: payment.metadata
    });

    if (!verification.paid) {
      throw new BadRequestException(`Gateway payment is not confirmed yet. Provider status: ${verification.status}`);
    }
    return verification;
  }

  private assertPaymentProvider(provider: PaymentProvider) {
    if (!Object.values(PaymentProvider).includes(provider)) {
      throw new NotFoundException('Payment provider not found');
    }
  }

  private jsonObject(value: unknown): Record<string, unknown> {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
      return {};
    }
    return value as Record<string, unknown>;
  }

  private toJson(value: unknown): Prisma.InputJsonValue | undefined {
    if (value === undefined) {
      return undefined;
    }
    return JSON.parse(JSON.stringify(value)) as Prisma.InputJsonValue;
  }

  private async canViewPaymentDetails(actor: RequestUser) {
    if (actor.role === UserRole.SUPER_ADMIN) {
      return true;
    }

    return (
      (await this.featureAccess.isFeatureEnabled(actor, FeatureKey.PAYMENTS_VIEW)) ||
      (await this.featureAccess.isFeatureEnabled(actor, FeatureKey.PAYMENTS_CONFIRM))
    );
  }

  private auditLogPaymentVisibilityWhere(canViewPayments: boolean): Prisma.AuditLogWhereInput | undefined {
    if (canViewPayments) {
      return undefined;
    }

    return {
      NOT: [
        { action: { contains: 'PAYMENT' } },
        { action: 'ADMIN_INTEGRATION_CONFIG_UPDATED' }
      ]
    };
  }

  private async resolveManagedEstateWhere(actor: RequestUser): Promise<Prisma.EstateWhereInput> {
    if (actor.role === UserRole.SUPER_ADMIN) {
      return {};
    }
    if (actor.role === UserRole.SALES_ADMIN) {
      return { salesPartnerId: actor.id };
    }
    if (!actor.estateId) {
      throw new ForbiddenException('Estate-scoped access requires an estate');
    }
    return { id: actor.estateId };
  }

  private async resolveManagedEstateIdFilter(
    actor: RequestUser,
    requestedEstateId?: string
  ): Promise<string | Prisma.StringFilter | undefined> {
    if (actor.role === UserRole.SUPER_ADMIN) {
      return requestedEstateId;
    }

    if (actor.role === UserRole.SALES_ADMIN) {
      if (requestedEstateId) {
        await this.assertCanAccessEstate(actor, requestedEstateId);
        return requestedEstateId;
      }

      const estates = await this.prisma.estate.findMany({
        where: { salesPartnerId: actor.id },
        select: { id: true }
      });
      return { in: estates.map((estate) => estate.id) };
    }

    if (!actor.estateId) {
      throw new ForbiddenException('Estate-scoped access requires an estate');
    }
    if (requestedEstateId && requestedEstateId !== actor.estateId) {
      await this.assertCanAccessEstate(actor, requestedEstateId);
    }
    return actor.estateId;
  }

  private async assertCanAccessEstate(actor: RequestUser, estateId: string) {
    if (actor.role === UserRole.SUPER_ADMIN) {
      return;
    }

    if (actor.role === UserRole.SALES_ADMIN) {
      const estate = await this.prisma.estate.findFirst({
        where: { id: estateId, salesPartnerId: actor.id },
        select: { id: true }
      });
      if (!estate) {
        throw new ForbiddenException('Sales admins can only access estates assigned to them');
      }
      return;
    }

    if (!actor.estateId || actor.estateId !== estateId) {
      throw new ForbiddenException('Cross-estate access is not allowed');
    }
  }

  private async assertCanAccessUserEstate(actor: RequestUser, estateId: string | null) {
    if (actor.role === UserRole.SUPER_ADMIN) {
      return;
    }
    if (!estateId) {
      throw new ForbiddenException('Only super admins can manage platform users');
    }
    await this.assertCanAccessEstate(actor, estateId);
  }

  private serializeSubscriptionPlan(plan: {
    features?: { feature: string }[];
    [key: string]: unknown;
  }) {
    const { features, ...rest } = plan;
    return {
      ...rest,
      features: (features ?? []).map((item) => item.feature)
    };
  }

  private serializeSalesPartner(user: {
    id: string;
    name: string;
    phone: string;
    role: UserRole;
    status: AccountStatus;
    salesPartnerProfile?: {
      enabled: boolean;
      commissionPercent: Prisma.Decimal | null;
      notes: string | null;
    } | null;
    salesPartnerEstates?: Array<{
      id: string;
      name: string;
      location: string;
      status: EstateStatus;
      signupStatus: EstateSignupStatus;
      subscriptionStatus: SubscriptionStatus;
    }>;
  }) {
    const profile = user.salesPartnerProfile;
    return {
      id: user.id,
      name: user.name,
      phone: user.phone,
      role: user.role,
      status: user.status,
      profileLabel: profile?.enabled ? 'Partner' : 'Sales Admin',
      partnerProfile: {
        enabled: profile?.enabled ?? false,
        commissionPercent: profile?.enabled ? Number(profile.commissionPercent ?? 0) : 0,
        notes: profile?.notes ?? null
      },
      assignedEstates: user.salesPartnerEstates ?? []
    };
  }

  private async withAdminAudit<T>(
    actor: RequestUser,
    estateId: string | null,
    action: string,
    operation: () => Promise<T>,
    metadata: Record<string, unknown>
  ) {
    try {
      const result = await operation();
      await this.audit.record({
        estateId,
        actorType: 'USER',
        actorId: actor.id,
        action,
        metadata
      });
      return result;
    } catch (error) {
      if (error instanceof Prisma.PrismaClientKnownRequestError) {
        if (error.code === 'P2025') {
          throw new NotFoundException('Record not found');
        }
        if (error.code === 'P2002') {
          throw new ConflictException('A unique field already uses this value');
        }
      }
      throw error;
    }
  }
}
