"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.VisitorsService = void 0;
const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client");
const nanoid_1 = require("nanoid");
const audit_service_1 = require("../audit/audit.service");
const estate_scope_1 = require("../common/estate-scope");
const plan_limits_1 = require("../common/plan-limits");
const prisma_service_1 = require("../database/prisma.service");
const visitorCode = (0, nanoid_1.customAlphabet)('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', 8);
const qrToken = (0, nanoid_1.customAlphabet)('ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789', 32);
let VisitorsService = class VisitorsService {
    prisma;
    audit;
    constructor(prisma, audit) {
        this.prisma = prisma;
        this.audit = audit;
    }
    findVisitors(user, estateId, q) {
        const scopedEstateId = (0, estate_scope_1.resolveEstateScope)(user, estateId);
        return this.prisma.visitor.findMany({
            where: {
                estateId: scopedEstateId,
                ...(q
                    ? {
                        OR: [
                            { name: { contains: q, mode: 'insensitive' } },
                            { phone: { contains: q, mode: 'insensitive' } },
                            { vehiclePlate: { contains: q, mode: 'insensitive' } }
                        ]
                    }
                    : {})
            },
            orderBy: { createdAt: 'desc' },
            take: 50
        });
    }
    async createVisitor(dto, user) {
        const estateId = (0, estate_scope_1.resolveEstateScope)(user, dto.estateId);
        const visitor = await this.prisma.visitor.create({
            data: {
                estateId,
                name: dto.name,
                phone: dto.phone,
                vehiclePlate: dto.vehiclePlate,
                photoUrl: dto.photoUrl
            }
        });
        await this.audit.record({
            estateId,
            actorType: 'USER',
            actorId: user.id,
            action: 'VISITOR_CREATED',
            metadata: { visitorId: visitor.id }
        });
        return visitor;
    }
    findPasses(user, estateId) {
        const scopedEstateId = (0, estate_scope_1.resolveEstateScope)(user, estateId);
        return this.prisma.visitorPass.findMany({
            where: { estateId: scopedEstateId },
            include: { visitor: true, hostResident: true },
            orderBy: { createdAt: 'desc' },
            take: 100
        });
    }
    async createPass(dto, user) {
        const estateId = (0, estate_scope_1.resolveEstateScope)(user, dto.estateId);
        await this.assertMonthlyPassLimit(estateId);
        if (dto.validUntil <= dto.validFrom) {
            throw new common_1.BadRequestException('validUntil must be after validFrom');
        }
        const [host, visitor] = await Promise.all([
            this.prisma.resident.findFirst({
                where: { id: dto.hostResidentId, estateId }
            }),
            this.prisma.visitor.findFirst({
                where: { id: dto.visitorId, estateId }
            })
        ]);
        if (!host) {
            throw new common_1.NotFoundException('Host resident was not found in this estate');
        }
        if (!visitor) {
            throw new common_1.NotFoundException('Visitor was not found in this estate');
        }
        const maxEntries = dto.maxEntries ?? this.defaultMaxEntries(dto.passType);
        const pass = await this.prisma.visitorPass.create({
            data: {
                estateId,
                visitorId: dto.visitorId,
                hostResidentId: dto.hostResidentId,
                visitorCode: visitorCode(),
                qrToken: qrToken(),
                validFrom: dto.validFrom,
                validUntil: dto.validUntil,
                passType: dto.passType,
                maxEntries
            },
            include: { visitor: true, hostResident: true }
        });
        await this.audit.record({
            estateId,
            actorType: 'USER',
            actorId: user.id,
            action: 'VISITOR_PASS_CREATED',
            metadata: { visitorPassId: pass.id, visitorCode: pass.visitorCode }
        });
        return {
            ...pass,
            qrPayload: { token: pass.qrToken }
        };
    }
    async revokePass(id, user) {
        const existing = await this.prisma.visitorPass.findUnique({ where: { id } });
        if (!existing) {
            throw new common_1.NotFoundException('Visitor pass not found');
        }
        (0, estate_scope_1.resolveEstateScope)(user, existing.estateId);
        const pass = await this.prisma.visitorPass.update({
            where: { id },
            data: { status: client_1.VisitorPassStatus.REVOKED, revokedAt: new Date() }
        });
        await this.audit.record({
            estateId: pass.estateId,
            actorType: 'USER',
            actorId: user.id,
            action: 'VISITOR_PASS_REVOKED',
            metadata: { visitorPassId: pass.id }
        });
        return pass;
    }
    async getVisitorHistory(id, user) {
        const visitor = await this.prisma.visitor.findUnique({
            where: { id },
            include: {
                passes: {
                    include: {
                        hostResident: true,
                        sessions: { include: { guard: true, gate: true }, orderBy: { checkinTime: 'desc' } }
                    },
                    orderBy: { createdAt: 'desc' }
                },
                riskEvents: { orderBy: { createdAt: 'desc' }, take: 25 }
            }
        });
        if (!visitor) {
            throw new common_1.NotFoundException('Visitor not found');
        }
        if (visitor.estateId) {
            (0, estate_scope_1.resolveEstateScope)(user, visitor.estateId);
        }
        const sessions = visitor.passes.flatMap((pass) => pass.sessions.map((session) => ({ ...session, hostResident: pass.hostResident })));
        const checkouts = sessions.filter((session) => session.checkoutTime).length;
        const open = sessions.filter((session) => !session.checkoutTime).length;
        const residentsVisited = Array.from(new Set(visitor.passes.map((pass) => pass.hostResidentId)));
        const gatesUsed = Array.from(new Set(sessions.map((session) => session.gate?.name ?? session.entryGate).filter(Boolean)));
        return {
            visitor,
            intelligence: {
                totalVisits: sessions.length,
                checkouts,
                openVisits: open,
                exitReliabilityPercent: sessions.length ? Math.round((checkouts / sessions.length) * 100) : null,
                residentsVisited: residentsVisited.length,
                gatesUsed,
                repeatVisitor: sessions.length > 1,
                riskLevel: visitor.riskLevel,
                riskScore: visitor.riskScore,
                flagged: visitor.flagged,
                blacklisted: visitor.blacklisted
            }
        };
    }
    async updateRisk(id, dto, user) {
        const existing = await this.prisma.visitor.findUnique({ where: { id } });
        if (!existing) {
            throw new common_1.NotFoundException('Visitor not found');
        }
        if (existing.estateId) {
            (0, estate_scope_1.resolveEstateScope)(user, existing.estateId);
        }
        const nextScore = dto.riskScore ?? existing.riskScore;
        const nextLevel = dto.riskLevel ?? this.riskLevelFromScore(nextScore);
        const visitor = await this.prisma.$transaction(async (tx) => {
            const updated = await tx.visitor.update({
                where: { id },
                data: {
                    riskScore: nextScore,
                    riskLevel: nextLevel,
                    flagged: dto.flagged,
                    blacklisted: dto.blacklisted,
                    riskNotes: dto.riskNotes
                }
            });
            await tx.visitorRiskEvent.create({
                data: {
                    visitorId: id,
                    estateId: existing.estateId,
                    actorId: user.id,
                    score: nextScore,
                    level: nextLevel,
                    reason: dto.riskNotes ?? 'Risk profile updated',
                    metadata: {
                        flagged: dto.flagged,
                        blacklisted: dto.blacklisted
                    }
                }
            });
            return updated;
        });
        await this.audit.record({
            estateId: existing.estateId,
            actorType: 'USER',
            actorId: user.id,
            action: 'VISITOR_RISK_UPDATED',
            metadata: { visitorId: id, riskScore: visitor.riskScore, riskLevel: visitor.riskLevel }
        });
        return visitor;
    }
    async assertMonthlyPassLimit(estateId) {
        const estate = await this.prisma.estate.findUnique({
            where: { id: estateId },
            select: { subscriptionPlan: { select: { limits: true } } }
        });
        const limit = (0, plan_limits_1.getNumericPlanLimit)(estate?.subscriptionPlan?.limits, 'visitorPassesPerMonth');
        if (limit === null) {
            return;
        }
        const used = await this.prisma.visitorPass.count({
            where: { estateId, createdAt: { gte: (0, plan_limits_1.startOfCurrentMonth)() } }
        });
        if (used >= limit) {
            throw new common_1.BadRequestException('Monthly visitor pass limit for this estate plan has been reached');
        }
    }
    riskLevelFromScore(score) {
        if (score >= 70) {
            return client_1.VisitorRiskLevel.HIGH;
        }
        if (score >= 35) {
            return client_1.VisitorRiskLevel.MEDIUM;
        }
        return client_1.VisitorRiskLevel.LOW;
    }
    defaultMaxEntries(passType) {
        if (passType === client_1.VisitorPassType.SINGLE_USE) {
            return 1;
        }
        return 999;
    }
};
exports.VisitorsService = VisitorsService;
exports.VisitorsService = VisitorsService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [prisma_service_1.PrismaService,
        audit_service_1.AuditService])
], VisitorsService);
//# sourceMappingURL=visitors.service.js.map