"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.OnboardingService = void 0;
const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client");
const audit_service_1 = require("../audit/audit.service");
const prisma_service_1 = require("../database/prisma.service");
const payment_gateway_service_1 = require("../payments/payment-gateway.service");
let OnboardingService = class OnboardingService {
    prisma;
    audit;
    paymentGateway;
    constructor(prisma, audit, paymentGateway) {
        this.prisma = prisma;
        this.audit = audit;
        this.paymentGateway = paymentGateway;
    }
    async listSubscriptionPlans() {
        const plans = await this.prisma.subscriptionPlan.findMany({
            where: { isActive: true, publicVisible: true },
            orderBy: [{ sortOrder: 'asc' }, { minResidents: 'asc' }],
            include: { features: { orderBy: { feature: 'asc' } } }
        });
        return plans.map((plan) => {
            const { features, ...rest } = plan;
            return { ...rest, features: features.map((item) => item.feature) };
        });
    }
    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);
            return {
                provider: global.provider,
                displayName: global.displayName,
                globallyEnabled: global.enabled,
                estateEnabled: override?.enabled ?? null,
                effectiveEnabled: override?.enabled ?? global.enabled,
                recommendedUseCases: global.recommendedUseCases,
                metadata: global.metadata
            };
        });
    }
    async submitEstateAdminLead(dto) {
        try {
            const result = await this.prisma.$transaction(async (tx) => {
                const estate = await tx.estate.create({
                    data: {
                        name: dto.estateName,
                        location: dto.location,
                        unitCount: dto.unitCount,
                        estateType: dto.estateType,
                        status: client_1.EstateStatus.PENDING,
                        signupStatus: client_1.EstateSignupStatus.PENDING_APPROVAL,
                        subscriptionStatus: client_1.SubscriptionStatus.NONE,
                        settings: {
                            onboarding: {
                                adminEmail: dto.adminEmail ?? null,
                                metadata: {
                                    ...(dto.metadata ?? {}),
                                    demoAccess: {
                                        requested: dto.metadata?.source === 'public-demo-request',
                                        estateLocation: dto.estateLocation ?? dto.location,
                                        gateCount: dto.gateCount ?? null,
                                        securityPersonnelCount: dto.securityPersonnelCount ?? null,
                                        currentVisitorEntryProcess: dto.currentVisitorEntryProcess ?? null,
                                        externalSecurityCompany: dto.externalSecurityCompany ?? null,
                                        managementChallenges: dto.managementChallenges ?? null,
                                        gateWiseBudget: dto.gateWiseBudget ?? null,
                                        emailVerificationProvided: Boolean(dto.emailVerificationToken),
                                        phoneVerificationProvided: Boolean(dto.phoneVerificationToken),
                                        approvalRequired: true,
                                        isolatedDemoDataOnly: true,
                                        exportDisabled: true,
                                        productionApiAccess: false
                                    }
                                }
                            }
                        }
                    }
                });
                const admin = await tx.user.create({
                    data: {
                        estateId: estate.id,
                        phone: dto.adminPhone,
                        name: dto.adminName,
                        role: client_1.UserRole.ESTATE_ADMIN,
                        status: client_1.AccountStatus.SUSPENDED
                    }
                });
                return { estate, admin };
            });
            await this.audit.record({
                estateId: result.estate.id,
                actorType: 'SYSTEM',
                actorId: 'public-onboarding',
                action: 'ESTATE_SIGNUP_SUBMITTED',
                metadata: {
                    estateId: result.estate.id,
                    estateName: result.estate.name,
                    unitCount: result.estate.unitCount,
                    estateType: result.estate.estateType,
                    adminUserId: result.admin.id
                }
            });
            await this.audit.record({
                estateId: result.estate.id,
                actorType: 'SYSTEM',
                actorId: 'public-onboarding',
                action: 'PLATFORM_REVIEW_NOTIFICATION_QUEUED',
                metadata: {
                    reason: 'Estate admin signup pending approval',
                    estateId: result.estate.id
                }
            });
            return {
                estate: result.estate,
                admin: {
                    id: result.admin.id,
                    name: result.admin.name,
                    phone: result.admin.phone,
                    email: dto.adminEmail ?? null,
                    role: result.admin.role,
                    status: result.admin.status
                },
                nextStep: 'GateWise review is required before plan selection and activation.'
            };
        }
        catch (error) {
            if (error instanceof client_1.Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
                throw new common_1.ConflictException('A user with this phone number already exists');
            }
            throw error;
        }
    }
    async selectSubscriptionPlan(estateId, dto) {
        const estate = await this.getEstateForOnboarding(estateId, dto.adminPhone);
        if (estate.signupStatus !== client_1.EstateSignupStatus.APPROVED) {
            throw new common_1.BadRequestException('Estate signup must be approved before selecting a plan');
        }
        const plan = await this.prisma.subscriptionPlan.findUnique({ where: { code: dto.planCode } });
        if (!plan || !plan.isActive) {
            throw new common_1.NotFoundException('Subscription plan not found');
        }
        await this.assertProviderEnabled(dto.paymentProvider, estateId);
        const updated = await this.prisma.estate.update({
            where: { id: estateId },
            data: {
                subscriptionPlanId: plan.id,
                selectedPaymentProvider: dto.paymentProvider,
                subscriptionStatus: client_1.SubscriptionStatus.PLAN_SELECTED
            },
            include: { subscriptionPlan: true }
        });
        await this.audit.record({
            estateId,
            actorType: 'USER',
            actorId: estate.users[0]?.id ?? 'estate-admin',
            action: 'SUBSCRIPTION_PLAN_SELECTED',
            metadata: {
                planCode: plan.code,
                paymentProvider: dto.paymentProvider
            }
        });
        return {
            estate: updated,
            nextStep: 'Initialize a hosted payment checkout, then return for payment confirmation.'
        };
    }
    async recordPayment(estateId, dto) {
        const estate = await this.getEstateForOnboarding(estateId, dto.adminPhone);
        if (estate.signupStatus !== client_1.EstateSignupStatus.APPROVED) {
            throw new common_1.BadRequestException('Estate signup must be approved before recording payment');
        }
        const plan = dto.planCode
            ? await this.prisma.subscriptionPlan.findUnique({ where: { code: dto.planCode } })
            : estate.subscriptionPlan;
        if (!plan || !plan.isActive) {
            throw new common_1.BadRequestException('Select an active subscription plan before recording payment');
        }
        const provider = dto.paymentProvider ?? estate.selectedPaymentProvider;
        if (!provider) {
            throw new common_1.BadRequestException('Select a payment provider before recording payment');
        }
        await this.assertProviderEnabled(provider, estateId);
        const amountKobo = this.resolvePaymentAmount(plan, dto.amountKobo);
        try {
            const payment = await this.prisma.$transaction(async (tx) => {
                const record = await tx.estatePayment.create({
                    data: {
                        estateId,
                        planId: plan.id,
                        provider,
                        amountKobo,
                        currency: plan.currency,
                        reference: dto.reference,
                        status: client_1.PaymentStatus.PENDING,
                        metadata: this.toJson({
                            ...(dto.metadata ?? {}),
                            gateway: {
                                initialized: false,
                                manualReference: true,
                                recordedAt: new Date().toISOString()
                            }
                        })
                    },
                    include: { plan: true }
                });
                await tx.estate.update({
                    where: { id: estateId },
                    data: {
                        subscriptionPlanId: plan.id,
                        selectedPaymentProvider: provider,
                        subscriptionStatus: client_1.SubscriptionStatus.PAYMENT_PENDING
                    }
                });
                return record;
            });
            await this.audit.record({
                estateId,
                actorType: 'USER',
                actorId: estate.users[0]?.id ?? 'estate-admin',
                action: 'ESTATE_PAYMENT_RECORDED',
                metadata: {
                    paymentId: payment.id,
                    reference: payment.reference,
                    provider: payment.provider,
                    planCode: payment.plan.code
                }
            });
            return {
                payment,
                nextStep: 'GateWise must confirm the payment before the estate becomes active.'
            };
        }
        catch (error) {
            if (error instanceof client_1.Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
                throw new common_1.ConflictException('A payment with this reference already exists');
            }
            throw error;
        }
    }
    async initializePayment(estateId, dto) {
        const estate = await this.getEstateForOnboarding(estateId, dto.adminPhone);
        if (estate.signupStatus !== client_1.EstateSignupStatus.APPROVED) {
            throw new common_1.BadRequestException('Estate signup must be approved before initializing payment');
        }
        const plan = dto.planCode
            ? await this.prisma.subscriptionPlan.findUnique({ where: { code: dto.planCode } })
            : estate.subscriptionPlan;
        if (!plan || !plan.isActive) {
            throw new common_1.BadRequestException('Select an active subscription plan before initializing payment');
        }
        const provider = dto.paymentProvider ?? estate.selectedPaymentProvider;
        if (!provider) {
            throw new common_1.BadRequestException('Select a payment provider before initializing payment');
        }
        await this.assertProviderEnabled(provider, estateId);
        const amountKobo = this.resolvePaymentAmount(plan, dto.amountKobo);
        const reference = this.paymentGateway.createReference(provider);
        const admin = estate.users[0];
        let payment = await this.prisma.estatePayment.create({
            data: {
                estateId,
                planId: plan.id,
                provider,
                amountKobo,
                currency: plan.currency,
                reference,
                status: client_1.PaymentStatus.PENDING,
                metadata: this.toJson({
                    ...(dto.metadata ?? {}),
                    returnUrl: dto.returnUrl ?? null,
                    gateway: {
                        initialized: false,
                        provider,
                        reference,
                        mode: this.paymentGateway.getProviderRuntime(provider).mode,
                        createdAt: new Date().toISOString()
                    }
                })
            },
            include: { plan: true }
        });
        try {
            const checkout = await this.paymentGateway.initializePayment({
                provider,
                reference,
                amountKobo,
                currency: plan.currency,
                customerEmail: dto.customerEmail,
                customerName: dto.customerName ?? admin?.name ?? estate.name,
                customerPhone: admin?.phone ?? dto.adminPhone,
                estateId,
                estateName: estate.name,
                planCode: plan.code,
                planName: plan.name,
                metadata: {
                    ...(dto.metadata ?? {}),
                    returnUrl: dto.returnUrl ?? null
                }
            });
            payment = await this.prisma.$transaction(async (tx) => {
                const updated = await tx.estatePayment.update({
                    where: { id: payment.id },
                    data: {
                        metadata: this.toJson({
                            ...this.jsonObject(payment.metadata),
                            gateway: {
                                initialized: true,
                                provider,
                                mode: checkout.mode,
                                reference: checkout.reference,
                                providerReference: checkout.providerReference,
                                checkoutUrl: checkout.checkoutUrl,
                                accessCode: checkout.accessCode ?? null,
                                initializedAt: new Date().toISOString(),
                                raw: checkout.raw
                            }
                        })
                    },
                    include: { plan: true }
                });
                await tx.estate.update({
                    where: { id: estateId },
                    data: {
                        subscriptionPlanId: plan.id,
                        selectedPaymentProvider: provider,
                        subscriptionStatus: client_1.SubscriptionStatus.PAYMENT_PENDING
                    }
                });
                return updated;
            });
            await this.audit.record({
                estateId,
                actorType: 'USER',
                actorId: admin?.id ?? 'estate-admin',
                action: 'ESTATE_PAYMENT_INITIALIZED',
                metadata: {
                    paymentId: payment.id,
                    reference: payment.reference,
                    provider,
                    planCode: plan.code,
                    gatewayMode: checkout.mode
                }
            });
            return {
                payment,
                checkout: {
                    provider,
                    mode: checkout.mode,
                    reference: checkout.reference,
                    providerReference: checkout.providerReference,
                    checkoutUrl: checkout.checkoutUrl,
                    accessCode: checkout.accessCode ?? null
                },
                nextStep: 'Open checkoutUrl, complete payment, then wait for GateWise confirmation.'
            };
        }
        catch (error) {
            await this.prisma.estatePayment.update({
                where: { id: payment.id },
                data: {
                    status: client_1.PaymentStatus.FAILED,
                    metadata: this.toJson({
                        ...this.jsonObject(payment.metadata),
                        gateway: {
                            ...this.jsonObject(this.jsonObject(payment.metadata).gateway),
                            initialized: false,
                            failedAt: new Date().toISOString(),
                            error: error instanceof Error ? error.message : String(error)
                        }
                    })
                }
            });
            throw error;
        }
    }
    async handlePaymentCallback(query) {
        const reference = this.queryString(query.reference) ??
            this.queryString(query.tx_ref) ??
            this.queryString(query.trxref) ??
            this.queryString(query.paymentReference);
        if (!reference) {
            return {
                status: 'received',
                nextStep: 'Payment gateway callback did not include a reference. Check the gateway dashboard for the transaction.'
            };
        }
        const payment = await this.prisma.estatePayment.findUnique({ where: { reference } });
        if (!payment) {
            return {
                status: 'received',
                reference,
                nextStep: 'Payment callback was received, but no local payment record matched this reference.'
            };
        }
        const providerTransactionId = this.queryString(query.transaction_id) ?? this.queryString(query.session_id) ?? this.queryString(query.transactionReference);
        const metadata = this.jsonObject(payment.metadata);
        const gateway = this.jsonObject(metadata.gateway);
        const updated = await this.prisma.estatePayment.update({
            where: { id: payment.id },
            data: {
                metadata: this.toJson({
                    ...metadata,
                    gateway: {
                        ...gateway,
                        providerTransactionId: providerTransactionId ?? gateway.providerTransactionId ?? null,
                        lastCallbackAt: new Date().toISOString(),
                        lastCallback: query
                    }
                })
            }
        });
        return {
            status: 'received',
            payment: {
                id: updated.id,
                estateId: updated.estateId,
                provider: updated.provider,
                reference: updated.reference,
                status: updated.status
            },
            nextStep: 'Payment callback stored. GateWise should confirm the payment to activate the estate.'
        };
    }
    async getEstateForOnboarding(estateId, adminPhone) {
        const estate = await this.prisma.estate.findUnique({
            where: { id: estateId },
            include: {
                subscriptionPlan: true,
                users: {
                    where: { phone: adminPhone, role: client_1.UserRole.ESTATE_ADMIN },
                    take: 1
                }
            }
        });
        if (!estate || estate.users.length === 0) {
            throw new common_1.NotFoundException('Estate signup was not found for this admin phone');
        }
        if (estate.status === client_1.EstateStatus.REJECTED || estate.status === client_1.EstateStatus.SUSPENDED) {
            throw new common_1.ForbiddenException('Estate signup is not eligible for onboarding changes');
        }
        return estate;
    }
    async assertProviderEnabled(provider, estateId) {
        const [global, override] = await Promise.all([
            this.prisma.globalPaymentProviderSetting.findUnique({ where: { provider } }),
            this.prisma.estatePaymentProviderSetting.findUnique({
                where: { estateId_provider: { estateId, provider } }
            })
        ]);
        const enabled = override?.enabled ?? global?.enabled ?? false;
        if (!enabled) {
            throw new common_1.BadRequestException(`${provider} is not enabled for this estate`);
        }
    }
    resolvePaymentAmount(plan, amountKobo) {
        const amount = amountKobo ?? plan.monthlyPriceKobo;
        if (!amount || amount <= 0) {
            if (plan.customPricing) {
                throw new common_1.BadRequestException('Custom-priced plans require amountKobo before payment can be recorded');
            }
            throw new common_1.BadRequestException('Subscription plan price is not configured');
        }
        return amount;
    }
    jsonObject(value) {
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            return {};
        }
        return value;
    }
    toJson(value) {
        return JSON.parse(JSON.stringify(value));
    }
    queryString(value) {
        if (typeof value === 'string' && value.length > 0)
            return value;
        if (Array.isArray(value) && typeof value[0] === 'string' && value[0].length > 0)
            return value[0];
        return null;
    }
};
exports.OnboardingService = OnboardingService;
exports.OnboardingService = OnboardingService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [prisma_service_1.PrismaService,
        audit_service_1.AuditService,
        payment_gateway_service_1.PaymentGatewayService])
], OnboardingService);
//# sourceMappingURL=onboarding.service.js.map