"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminService = void 0;
const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client");
const audit_service_1 = require("../audit/audit.service");
const otp_service_1 = require("../auth/otp.service");
const profile_completion_1 = require("../common/profile-completion");
const prisma_service_1 = require("../database/prisma.service");
const feature_access_service_1 = require("../features/feature-access.service");
const feature_catalog_1 = require("../features/feature-catalog");
const integration_config_service_1 = require("../integrations/integration-config.service");
const payment_gateway_service_1 = require("../payments/payment-gateway.service");
let AdminService = class AdminService {
    prisma;
    audit;
    paymentGateway;
    otp;
    integrations;
    featureAccess;
    constructor(prisma, audit, paymentGateway, otp, integrations, featureAccess) {
        this.prisma = prisma;
        this.audit = audit;
        this.paymentGateway = paymentGateway;
        this.otp = otp;
        this.integrations = integrations;
        this.featureAccess = featureAccess;
    }
    async dashboard(actor) {
        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: client_1.EstateSignupStatus.PENDING_APPROVAL } }),
            canViewPayments
                ? this.prisma.estatePayment.count({ where: { status: client_1.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: client_1.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 = {
            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, estateId, role, status) {
        const scopedEstateId = await this.resolveManagedEstateIdFilter(actor, estateId);
        return this.prisma.user.findMany({
            where: {
                estateId: scopedEstateId,
                role: role,
                status: status
            },
            orderBy: { createdAt: 'desc' },
            take: 100,
            include: { estate: true, resident: true, guard: true }
        });
    }
    async listEstates(actor) {
        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, actor) {
        const include = {
            users: { where: { role: client_1.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 },
            orderBy: { createdAt: 'desc' },
            take: 100,
            include
        });
    }
    async listSubscriptionPlans(includeInactive) {
        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, actor) {
        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) {
        return this.integrations.getConfig(provider);
    }
    async updateIntegrationConfig(provider, dto, actor) {
        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, actor) {
        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, dto, actor) {
        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) {
        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, dto, actor) {
        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, provider, dto, actor) {
        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: client_1.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) {
        if (actor.role !== client_1.UserRole.SALES_ADMIN) {
            throw new common_1.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 common_1.NotFoundException('Sales admin not found');
        }
        return this.serializeSalesPartner(user);
    }
    async updateSalesPartnerProfile(salesAdminId, dto, actor) {
        const salesAdmin = await this.prisma.user.findUnique({ where: { id: salesAdminId } });
        if (!salesAdmin || salesAdmin.role !== client_1.UserRole.SALES_ADMIN) {
            throw new common_1.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, dto, actor) {
        const salesAdminId = dto.salesAdminId ?? null;
        if (salesAdminId) {
            const salesAdmin = await this.prisma.user.findUnique({ where: { id: salesAdminId } });
            if (!salesAdmin || salesAdmin.role !== client_1.UserRole.SALES_ADMIN) {
                throw new common_1.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, actor) {
        if (actor.role === client_1.UserRole.SALES_ADMIN && actor.id !== salesAdminId) {
            throw new common_1.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 !== client_1.UserRole.SALES_ADMIN) {
            throw new common_1.NotFoundException('Sales admin not found');
        }
        const payments = await this.prisma.estatePayment.findMany({
            where: {
                status: client_1.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) {
        return this.getSalesPartnerEarnings(actor.id, actor);
    }
    async contactEstateAdmin(estateId, dto, actor) {
        await this.assertCanAccessEstate(actor, estateId);
        const estate = await this.prisma.estate.findUnique({
            where: { id: estateId },
            include: { users: { where: { role: client_1.UserRole.ESTATE_ADMIN }, take: 10 } }
        });
        if (!estate) {
            throw new common_1.NotFoundException('Estate not found');
        }
        if (!estate.users.length) {
            throw new common_1.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, estateId) {
        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, estateId) {
        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, estateId) {
        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, estateId, status) {
        const scopedEstateId = await this.resolveManagedEstateIdFilter(actor, estateId);
        return this.prisma.visitorPass.findMany({
            where: { estateId: scopedEstateId, status: status },
            orderBy: { createdAt: 'desc' },
            take: 100,
            include: { estate: true, visitor: true, hostResident: true }
        });
    }
    async listVisitSessions(actor, estateId) {
        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, estateId) {
        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, actor) {
        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, dto, actor) {
        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,
                branding: dto.branding
            }
        }), { estateId: id });
    }
    async updateUser(id, dto, actor) {
        const existing = await this.prisma.user.findUnique({ where: { id }, select: { estateId: true } });
        if (!existing) {
            throw new common_1.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, dto, actor) {
        const existing = await this.prisma.resident.findUnique({ where: { id } });
        if (!existing) {
            throw new common_1.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 = (0, profile_completion_1.calculateProfileCompletion)(client_1.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, dto, actor) {
        const existing = await this.prisma.guard.findUnique({ where: { id } });
        if (!existing) {
            throw new common_1.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 = (0, profile_completion_1.calculateProfileCompletion)(client_1.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, dto, actor) {
        const existing = await this.prisma.visitor.findUnique({ where: { id }, select: { estateId: true } });
        if (!existing) {
            throw new common_1.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, dto, actor) {
        const existing = await this.prisma.visitorPass.findUnique({ where: { id }, select: { estateId: true } });
        if (!existing) {
            throw new common_1.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 === client_1.VisitorPassStatus.REVOKED ? new Date() : undefined
            }
        }), { visitorPassId: id });
    }
    async updateIncident(id, dto, actor) {
        const existing = await this.prisma.incident.findUnique({ where: { id }, select: { estateId: true } });
        if (!existing) {
            throw new common_1.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
            }
        }), { incidentId: id });
    }
    async reviewEstateSignup(estateId, dto, actor) {
        await this.assertCanAccessEstate(actor, estateId);
        if (dto.status === client_1.EstateSignupStatus.PENDING_APPROVAL) {
            throw new common_1.BadRequestException('Review status must be APPROVED, REJECTED, or SUSPENDED');
        }
        const statusData = dto.status === client_1.EstateSignupStatus.APPROVED
            ? {
                status: client_1.EstateStatus.PENDING,
                signupStatus: client_1.EstateSignupStatus.APPROVED,
                rejectedAt: null,
                suspendedAt: null
            }
            : dto.status === client_1.EstateSignupStatus.REJECTED
                ? {
                    status: client_1.EstateStatus.REJECTED,
                    signupStatus: client_1.EstateSignupStatus.REJECTED,
                    rejectedAt: new Date(),
                    suspendedAt: null
                }
                : {
                    status: client_1.EstateStatus.SUSPENDED,
                    signupStatus: client_1.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 === client_1.EstateSignupStatus.REJECTED) {
                await tx.user.updateMany({
                    where: { estateId, role: client_1.UserRole.ESTATE_ADMIN },
                    data: { status: client_1.AccountStatus.DISABLED }
                });
            }
            if (dto.status === client_1.EstateSignupStatus.SUSPENDED) {
                await tx.user.updateMany({
                    where: { estateId, role: client_1.UserRole.ESTATE_ADMIN },
                    data: { status: client_1.AccountStatus.SUSPENDED }
                });
            }
            return estate;
        }), { estateId, signupStatus: dto.status, reviewNotes: dto.reviewNotes });
    }
    async confirmEstatePayment(estateId, dto, actor) {
        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: client_1.PaymentStatus.PENDING },
                    orderBy: { createdAt: 'desc' }
                })
                : await this.prisma.estatePayment.findFirst({
                    where: { estateId, status: client_1.PaymentStatus.PENDING },
                    orderBy: { createdAt: 'desc' }
                });
            if (!payment) {
                throw new common_1.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: client_1.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: client_1.EstateStatus.ACTIVE,
                        signupStatus: client_1.EstateSignupStatus.APPROVED,
                        subscriptionStatus: client_1.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: client_1.UserRole.ESTATE_ADMIN },
                    data: { status: client_1.AccountStatus.ACTIVE }
                });
                return { estate, payment: confirmedPayment };
            });
        }, { estateId, paymentReference: dto.paymentReference });
    }
    async verifyGatewayPaymentIfNeeded(payment) {
        if (!this.paymentGateway.shouldVerifyGatewayPayment(payment.metadata)) {
            return null;
        }
        if (!payment.reference) {
            throw new common_1.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 common_1.BadRequestException(`Gateway payment is not confirmed yet. Provider status: ${verification.status}`);
        }
        return verification;
    }
    assertPaymentProvider(provider) {
        if (!Object.values(client_1.PaymentProvider).includes(provider)) {
            throw new common_1.NotFoundException('Payment provider not found');
        }
    }
    jsonObject(value) {
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            return {};
        }
        return value;
    }
    toJson(value) {
        if (value === undefined) {
            return undefined;
        }
        return JSON.parse(JSON.stringify(value));
    }
    async canViewPaymentDetails(actor) {
        if (actor.role === client_1.UserRole.SUPER_ADMIN) {
            return true;
        }
        return ((await this.featureAccess.isFeatureEnabled(actor, feature_catalog_1.FeatureKey.PAYMENTS_VIEW)) ||
            (await this.featureAccess.isFeatureEnabled(actor, feature_catalog_1.FeatureKey.PAYMENTS_CONFIRM)));
    }
    auditLogPaymentVisibilityWhere(canViewPayments) {
        if (canViewPayments) {
            return undefined;
        }
        return {
            NOT: [
                { action: { contains: 'PAYMENT' } },
                { action: 'ADMIN_INTEGRATION_CONFIG_UPDATED' }
            ]
        };
    }
    async resolveManagedEstateWhere(actor) {
        if (actor.role === client_1.UserRole.SUPER_ADMIN) {
            return {};
        }
        if (actor.role === client_1.UserRole.SALES_ADMIN) {
            return { salesPartnerId: actor.id };
        }
        if (!actor.estateId) {
            throw new common_1.ForbiddenException('Estate-scoped access requires an estate');
        }
        return { id: actor.estateId };
    }
    async resolveManagedEstateIdFilter(actor, requestedEstateId) {
        if (actor.role === client_1.UserRole.SUPER_ADMIN) {
            return requestedEstateId;
        }
        if (actor.role === client_1.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 common_1.ForbiddenException('Estate-scoped access requires an estate');
        }
        if (requestedEstateId && requestedEstateId !== actor.estateId) {
            await this.assertCanAccessEstate(actor, requestedEstateId);
        }
        return actor.estateId;
    }
    async assertCanAccessEstate(actor, estateId) {
        if (actor.role === client_1.UserRole.SUPER_ADMIN) {
            return;
        }
        if (actor.role === client_1.UserRole.SALES_ADMIN) {
            const estate = await this.prisma.estate.findFirst({
                where: { id: estateId, salesPartnerId: actor.id },
                select: { id: true }
            });
            if (!estate) {
                throw new common_1.ForbiddenException('Sales admins can only access estates assigned to them');
            }
            return;
        }
        if (!actor.estateId || actor.estateId !== estateId) {
            throw new common_1.ForbiddenException('Cross-estate access is not allowed');
        }
    }
    async assertCanAccessUserEstate(actor, estateId) {
        if (actor.role === client_1.UserRole.SUPER_ADMIN) {
            return;
        }
        if (!estateId) {
            throw new common_1.ForbiddenException('Only super admins can manage platform users');
        }
        await this.assertCanAccessEstate(actor, estateId);
    }
    serializeSubscriptionPlan(plan) {
        const { features, ...rest } = plan;
        return {
            ...rest,
            features: (features ?? []).map((item) => item.feature)
        };
    }
    serializeSalesPartner(user) {
        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 ?? []
        };
    }
    async withAdminAudit(actor, estateId, action, operation, metadata) {
        try {
            const result = await operation();
            await this.audit.record({
                estateId,
                actorType: 'USER',
                actorId: actor.id,
                action,
                metadata
            });
            return result;
        }
        catch (error) {
            if (error instanceof client_1.Prisma.PrismaClientKnownRequestError) {
                if (error.code === 'P2025') {
                    throw new common_1.NotFoundException('Record not found');
                }
                if (error.code === 'P2002') {
                    throw new common_1.ConflictException('A unique field already uses this value');
                }
            }
            throw error;
        }
    }
};
exports.AdminService = AdminService;
exports.AdminService = AdminService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [prisma_service_1.PrismaService,
        audit_service_1.AuditService,
        payment_gateway_service_1.PaymentGatewayService,
        otp_service_1.OtpService,
        integration_config_service_1.IntegrationConfigService,
        feature_access_service_1.FeatureAccessService])
], AdminService);
//# sourceMappingURL=admin.service.js.map