"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.ProfileService = void 0;
const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client");
const audit_service_1 = require("../audit/audit.service");
const profile_completion_1 = require("../common/profile-completion");
const prisma_service_1 = require("../database/prisma.service");
let ProfileService = class ProfileService {
    prisma;
    audit;
    constructor(prisma, audit) {
        this.prisma = prisma;
        this.audit = audit;
    }
    async getMe(user) {
        const account = await this.prisma.user.findUnique({
            where: { id: user.id },
            include: {
                estate: {
                    include: {
                        subscriptionPlan: {
                            include: { features: { orderBy: { feature: 'asc' } } }
                        },
                        gates: { where: { isActive: true }, orderBy: { name: 'asc' } }
                    }
                },
                resident: true,
                guard: { include: { assignedGate: true } }
            }
        });
        if (!account) {
            throw new common_1.NotFoundException('Profile not found');
        }
        const profile = account.role === client_1.UserRole.RESIDENT ? account.resident : account.role === client_1.UserRole.GUARD ? account.guard : null;
        const completionOptions = this.profileCompletionOptions(account.estate?.settings, account.role);
        const completion = profile
            ? (0, profile_completion_1.calculateProfileCompletion)(account.role, profile, completionOptions)
            : (0, profile_completion_1.calculateProfileCompletion)(account.role, account, completionOptions);
        const currentPlan = account.estate?.subscriptionPlan ? this.serializePlan(account.estate.subscriptionPlan, false) : null;
        return {
            user: this.redactAccount(account),
            profile,
            completion,
            estate: account.estate ? this.serializeEstate(account.estate) : null,
            currentPlan,
            featureAvailability: this.serializeFeatureAvailability(currentPlan),
            planComparison: await this.getResidentPlanComparison(account.estate?.subscriptionPlan ?? null, account.role)
        };
    }
    async updateMe(user, dto) {
        const account = await this.prisma.user.findUnique({
            where: { id: user.id },
            include: { resident: true, guard: true, estate: { select: { settings: true } } }
        });
        if (!account) {
            throw new common_1.NotFoundException('Profile not found');
        }
        const fullName = this.fullName(dto.firstName, dto.lastName);
        const imageMetadata = dto.profileImageMetadata
            ? {
                profileImage: {
                    processing: {
                        compressed: true,
                        resized: true,
                        thumbnailGenerated: Boolean(dto.profilePictureThumbnailUrl),
                        storageOptimized: true,
                        ...dto.profileImageMetadata
                    }
                }
            }
            : undefined;
        const updated = await this.prisma.$transaction(async (tx) => {
            const userUpdate = await tx.user.update({
                where: { id: user.id },
                data: {
                    firstName: this.clean(dto.firstName),
                    lastName: this.clean(dto.lastName),
                    name: fullName ?? undefined,
                    email: this.clean(dto.email),
                    address: this.clean(dto.address),
                    profilePictureUrl: this.clean(dto.profilePictureUrl),
                    profilePictureThumbnailUrl: this.clean(dto.profilePictureThumbnailUrl),
                    profileConfirmed: dto.profileConfirmed,
                    metadata: imageMetadata ? this.mergeJson(account.metadata, imageMetadata) : undefined
                }
            });
            let profile = null;
            if (account.role === client_1.UserRole.RESIDENT && account.resident) {
                const completionOptions = this.profileCompletionOptions(account.estate?.settings, client_1.UserRole.RESIDENT);
                const draft = {
                    ...account.resident,
                    firstName: dto.firstName ?? account.resident.firstName,
                    lastName: dto.lastName ?? account.resident.lastName,
                    phone: account.resident.phone,
                    blockNumber: dto.blockNumber ?? account.resident.blockNumber,
                    flatNumber: dto.flatNumber ?? account.resident.flatNumber,
                    unit: dto.flatNumber ?? account.resident.unit,
                    address: dto.address ?? account.resident.address,
                    profileConfirmed: dto.profileConfirmed ?? account.resident.profileConfirmed
                };
                const completion = (0, profile_completion_1.calculateProfileCompletion)(client_1.UserRole.RESIDENT, draft, completionOptions);
                profile = await tx.resident.update({
                    where: { userId: user.id },
                    data: {
                        firstName: this.clean(dto.firstName),
                        lastName: this.clean(dto.lastName),
                        name: fullName ?? undefined,
                        blockNumber: this.clean(dto.blockNumber),
                        flatNumber: this.clean(dto.flatNumber),
                        unit: this.clean(dto.flatNumber),
                        address: this.clean(dto.address),
                        email: this.clean(dto.email),
                        profilePictureUrl: this.clean(dto.profilePictureUrl),
                        profilePictureThumbnailUrl: this.clean(dto.profilePictureThumbnailUrl),
                        emergencyContact: this.toJson(dto.emergencyContact),
                        householdMembers: this.toJson(dto.householdMembers),
                        profileConfirmed: dto.profileConfirmed,
                        profileCompletion: completion.completion,
                        metadata: imageMetadata ? this.mergeJson(account.resident.metadata, imageMetadata) : undefined
                    }
                });
            }
            if (account.role === client_1.UserRole.GUARD && account.guard) {
                const completionOptions = this.profileCompletionOptions(account.estate?.settings, client_1.UserRole.GUARD);
                const draft = {
                    ...account.guard,
                    firstName: dto.firstName ?? account.guard.firstName,
                    lastName: dto.lastName ?? account.guard.lastName,
                    address: dto.address ?? account.guard.address,
                    profilePictureUrl: dto.profilePictureUrl ?? account.guard.profilePictureUrl,
                    emergencyContact: dto.emergencyContact ?? account.guard.emergencyContact,
                    profileConfirmed: dto.profileConfirmed ?? account.guard.profileConfirmed
                };
                const completion = (0, profile_completion_1.calculateProfileCompletion)(client_1.UserRole.GUARD, draft, completionOptions);
                profile = await tx.guard.update({
                    where: { userId: user.id },
                    data: {
                        firstName: this.clean(dto.firstName),
                        lastName: this.clean(dto.lastName),
                        name: fullName ?? undefined,
                        address: this.clean(dto.address),
                        profilePictureUrl: this.clean(dto.profilePictureUrl),
                        profilePictureThumbnailUrl: this.clean(dto.profilePictureThumbnailUrl),
                        emergencyContact: this.toJson(dto.emergencyContact),
                        profileConfirmed: dto.profileConfirmed,
                        profileCompletion: completion.completion,
                        metadata: imageMetadata ? this.mergeJson(account.guard.metadata, imageMetadata) : undefined
                    }
                });
            }
            const completion = account.role === client_1.UserRole.RESIDENT && profile
                ? (0, profile_completion_1.calculateProfileCompletion)(client_1.UserRole.RESIDENT, profile, this.profileCompletionOptions(account.estate?.settings, client_1.UserRole.RESIDENT))
                : account.role === client_1.UserRole.GUARD && profile
                    ? (0, profile_completion_1.calculateProfileCompletion)(client_1.UserRole.GUARD, profile, this.profileCompletionOptions(account.estate?.settings, client_1.UserRole.GUARD))
                    : (0, profile_completion_1.calculateProfileCompletion)(account.role, userUpdate, this.profileCompletionOptions(account.estate?.settings, account.role));
            await tx.user.update({
                where: { id: user.id },
                data: {
                    profileCompletion: completion.completion,
                    profileConfirmed: dto.profileConfirmed
                }
            });
            return { user: userUpdate, profile, completion };
        });
        await this.audit.record({
            estateId: user.estateId,
            actorType: 'USER',
            actorId: user.id,
            action: 'PROFILE_UPDATED',
            metadata: {
                role: user.role,
                completion: updated.completion.completion,
                imageProcessed: Boolean(dto.profileImageMetadata)
            }
        });
        return updated;
    }
    async getDemoAccess(user) {
        if (user.role !== client_1.UserRole.ESTATE_ADMIN) {
            throw new common_1.ForbiddenException('Approved estate admin access is required for the demo');
        }
        const account = await this.prisma.user.findUnique({
            where: { id: user.id },
            select: {
                id: true,
                role: true,
                status: true,
                estate: {
                    select: {
                        id: true,
                        name: true,
                        status: true,
                        signupStatus: true,
                        settings: true
                    }
                }
            }
        });
        const estate = account?.estate;
        if (!account || !estate) {
            throw new common_1.ForbiddenException('Estate approval is required for demo access');
        }
        const demoAccess = this.jsonObject(this.jsonObject(estate.settings).demoAccess);
        const expiresAt = this.dateString(demoAccess.expiresAt) ?? this.futureIsoDate(120);
        const maxSessionMinutes = Number(demoAccess.maxSessionMinutes ?? 120);
        const approved = account.status === 'ACTIVE' &&
            estate.signupStatus === client_1.EstateSignupStatus.APPROVED &&
            estate.status === client_1.EstateStatus.ACTIVE &&
            demoAccess.approved !== false &&
            new Date(expiresAt).getTime() > Date.now();
        return {
            approved,
            roleRequired: client_1.UserRole.ESTATE_ADMIN,
            estate: { id: estate.id, name: estate.name },
            expiresAt,
            maxSessionMinutes,
            reason: approved ? null : 'Estate registration, verification, approval, and active demo time are required'
        };
    }
    async assertUsableProfile(user, feature) {
        if (user.role !== client_1.UserRole.RESIDENT && user.role !== client_1.UserRole.GUARD) {
            return;
        }
        const alwaysAllowed = new Set(['PROFILE_VIEW', 'PROFILE_MANAGE', 'PLAN_COMPARISON_VIEW', 'ANNOUNCEMENTS_VIEW']);
        if (alwaysAllowed.has(feature)) {
            return;
        }
        const account = await this.prisma.user.findUnique({
            where: { id: user.id },
            include: { resident: true, guard: true, estate: { select: { settings: true } } }
        });
        if (!account) {
            throw new common_1.ForbiddenException('Profile is required before using the app');
        }
        const profile = user.role === client_1.UserRole.RESIDENT ? account.resident : account.guard;
        if (!profile) {
            throw new common_1.ForbiddenException('Role profile is required before using the app');
        }
        const completion = (0, profile_completion_1.calculateProfileCompletion)(user.role, profile, this.profileCompletionOptions(account.estate?.settings, user.role));
        if (!completion.complete) {
            throw new common_1.ForbiddenException({
                message: 'Complete your profile before using this feature',
                profileCompletion: completion.completion,
                missing: completion.missing
            });
        }
    }
    async getResidentPlanComparison(currentPlan, role) {
        if (!currentPlan || role !== client_1.UserRole.RESIDENT || !currentPlan.residentMarketingEnabled) {
            return null;
        }
        const higherPlans = await this.prisma.subscriptionPlan.findMany({
            where: {
                isActive: true,
                publicVisible: true,
                sortOrder: { gt: currentPlan.sortOrder }
            },
            orderBy: { sortOrder: 'asc' },
            include: { features: { orderBy: { feature: 'asc' } } }
        });
        if (!higherPlans.length) {
            return null;
        }
        return {
            showPricing: false,
            current: this.serializePlan(currentPlan, false),
            upgrades: higherPlans.map((plan) => this.serializePlan(plan, false))
        };
    }
    serializePlan(plan, includePricing) {
        const { monthlyPriceKobo, oldMonthlyPriceKobo, currency, customPricing, ...rest } = plan;
        return {
            ...rest,
            features: plan.features.map((item) => item.feature),
            ...(includePricing ? { monthlyPriceKobo, oldMonthlyPriceKobo, currency, customPricing } : {})
        };
    }
    serializeEstate(estate) {
        const plan = estate.subscriptionPlan ? this.serializePlan(estate.subscriptionPlan, false) : null;
        return {
            id: estate.id,
            name: estate.name,
            location: estate.location,
            currentPlan: plan,
            profileRequirements: this.publicProfileRequirements(estate.settings),
            gates: (estate.gates ?? []).map((gate) => ({
                id: gate.id,
                name: gate.name,
                type: gate.type,
                isActive: gate.isActive
            })),
            settings: {
                announcements: this.publicAnnouncementSettings(estate.settings),
                serviceChargePaymentsEnabled: this.planSupportsServiceChargePayments(plan),
                residentRequestsEnabled: true,
                visitorFeaturesEnabled: true
            }
        };
    }
    serializeFeatureAvailability(plan) {
        return {
            serviceChargePayments: this.planSupportsServiceChargePayments(plan),
            residentRequests: true,
            announcements: true,
            visitorPasses: true
        };
    }
    planSupportsServiceChargePayments(plan) {
        if (!plan) {
            return false;
        }
        return plan.code === 'BUSINESS' || plan.code === 'ENTERPRISE' || Number(plan.sortOrder ?? 0) >= 40;
    }
    publicProfileRequirements(settings) {
        return {
            resident: this.residentRequiredFields(settings),
            guard: this.guardRequiredFields(settings)
        };
    }
    publicAnnouncementSettings(settings) {
        const announcementSettings = this.jsonObject(this.jsonObject(settings).announcements);
        return {
            allowedCategories: this.stringArray(announcementSettings.allowedCategories) ?? [
                'SECURITY',
                'MAINTENANCE',
                'NEWS',
                'EVENTS',
                'NOTICES',
                'EMERGENCY'
            ],
            dailyPostingLimit: Number(announcementSettings.dailyPostingLimit ?? 10),
            approvalRequiredForFlaggedKeywords: announcementSettings.approvalRequiredForFlaggedKeywords !== false
        };
    }
    profileCompletionOptions(settings, role) {
        if (role === client_1.UserRole.RESIDENT) {
            return { residentRequiredFields: this.residentRequiredFields(settings) };
        }
        if (role === client_1.UserRole.GUARD) {
            return { guardRequiredFields: this.guardRequiredFields(settings) };
        }
        return {};
    }
    residentRequiredFields(settings) {
        const configured = this.settingsArray(settings, 'resident');
        const fields = configured?.length
            ? this.filterRequirements(configured, [
                'firstName',
                'lastName',
                'phone',
                'blockNumber',
                'flatNumber',
                'address',
                'estateId',
                'profileConfirmed'
            ])
            : ['firstName', 'lastName', 'phone', 'blockNumber', 'flatNumber', 'address', 'estateId', 'profileConfirmed'];
        const residentSettings = this.roleProfileSettings(settings, 'resident');
        return fields.filter((field) => {
            if (field === 'blockNumber' && residentSettings.usesBlocks === false) {
                return false;
            }
            if (field === 'flatNumber' && residentSettings.usesFlats === false) {
                return false;
            }
            return true;
        });
    }
    guardRequiredFields(settings) {
        const configured = this.settingsArray(settings, 'guard');
        return configured?.length
            ? this.filterRequirements(configured, [
                'firstName',
                'lastName',
                'phone',
                'guardCode',
                'assignedGateId',
                'assignedShifts',
                'profilePictureUrl',
                'address',
                'emergencyContact',
                'estateId',
                'profileConfirmed'
            ])
            : [
                'firstName',
                'lastName',
                'phone',
                'guardCode',
                'assignedGateId',
                'assignedShifts',
                'profilePictureUrl',
                'address',
                'emergencyContact',
                'estateId',
                'profileConfirmed'
            ];
    }
    settingsArray(settings, role) {
        return this.stringArray(this.roleProfileSettings(settings, role).requiredFields);
    }
    roleProfileSettings(settings, role) {
        const profileRequirements = this.jsonObject(this.jsonObject(settings).profileRequirements);
        return this.jsonObject(profileRequirements[role]);
    }
    filterRequirements(values, allowed) {
        const allowedSet = new Set(allowed);
        return values.filter((value) => allowedSet.has(value));
    }
    stringArray(value) {
        if (!Array.isArray(value)) {
            return null;
        }
        return value.filter((item) => typeof item === 'string' && item.trim().length > 0);
    }
    jsonObject(value) {
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            return {};
        }
        return value;
    }
    dateString(value) {
        if (typeof value !== 'string') {
            return null;
        }
        const time = new Date(value).getTime();
        return Number.isFinite(time) ? value : null;
    }
    futureIsoDate(minutes) {
        return new Date(Date.now() + minutes * 60 * 1000).toISOString();
    }
    redactAccount(account) {
        const rest = { ...account };
        delete rest.refreshToken;
        delete rest.estate;
        return rest;
    }
    fullName(firstName, lastName) {
        const value = [this.clean(firstName), this.clean(lastName)].filter(Boolean).join(' ').trim();
        return value || undefined;
    }
    clean(value) {
        return typeof value === 'string' ? value.trim() || undefined : undefined;
    }
    mergeJson(existing, patch) {
        const existingObject = existing && typeof existing === 'object' && !Array.isArray(existing) ? existing : {};
        return { ...existingObject, ...patch };
    }
    toJson(value) {
        if (value === undefined) {
            return undefined;
        }
        return JSON.parse(JSON.stringify(value));
    }
};
exports.ProfileService = ProfileService;
exports.ProfileService = ProfileService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [prisma_service_1.PrismaService,
        audit_service_1.AuditService])
], ProfileService);
//# sourceMappingURL=profile.service.js.map