"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.CheckinService = void 0;
const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client");
const audit_service_1 = require("../audit/audit.service");
const estate_scope_1 = require("../common/estate-scope");
const guard_access_1 = require("../common/guard-access");
const prisma_service_1 = require("../database/prisma.service");
let CheckinService = class CheckinService {
    prisma;
    audit;
    constructor(prisma, audit) {
        this.prisma = prisma;
        this.audit = audit;
    }
    async checkinByQr(dto, user) {
        const estateId = (0, estate_scope_1.resolveEstateScope)(user, dto.estateId);
        const guard = await (0, guard_access_1.assertActiveGuardForEstate)(this.prisma, dto.guardId, estateId, user, dto.deviceId);
        return this.checkin({ estateId, qrToken: dto.qrToken }, dto.guardId, dto.entryGate, dto.gateId, dto.offlineEventId, dto.deviceId ?? guard.deviceId ?? undefined, dto.scanLocation, user);
    }
    async checkinByCode(dto, user) {
        const estateId = (0, estate_scope_1.resolveEstateScope)(user, dto.estateId);
        const guard = await (0, guard_access_1.assertActiveGuardForEstate)(this.prisma, dto.guardId, estateId, user, dto.deviceId);
        return this.checkin({ estateId, visitorCode: dto.visitorCode }, dto.guardId, dto.entryGate, dto.gateId, dto.offlineEventId, dto.deviceId ?? guard.deviceId ?? undefined, dto.scanLocation, user);
    }
    async checkout(dto, user) {
        const session = await this.prisma.visitSession.findUnique({
            where: { id: dto.visitSessionId },
            include: { visitorPass: true }
        });
        if (!session) {
            throw new common_1.NotFoundException('Visit session not found');
        }
        const estateId = (0, estate_scope_1.resolveEstateScope)(user, session.estateId);
        const guard = await (0, guard_access_1.assertActiveGuardForEstate)(this.prisma, dto.guardId, estateId, user, dto.deviceId);
        if (session.status === client_1.VisitSessionStatus.CHECKED_OUT) {
            throw new common_1.BadRequestException('Visit session is already checked out');
        }
        const updated = await this.prisma.visitSession.update({
            where: { id: dto.visitSessionId },
            data: {
                guardId: dto.guardId,
                exitGate: dto.exitGate,
                gateId: dto.gateId ?? session.gateId,
                checkoutTime: new Date(),
                status: client_1.VisitSessionStatus.CHECKED_OUT
            },
            include: { visitorPass: { include: { visitor: true, hostResident: true } } }
        });
        await this.createResidentVisitNotification(updated, 'Visitor checked out', 'Your visitor has checked out.');
        await this.audit.record({
            estateId: updated.estateId,
            actorType: 'GUARD',
            actorId: dto.guardId,
            action: 'VISITOR_CHECKED_OUT',
            metadata: {
                visitSessionId: updated.id,
                visitorPassId: updated.visitorPassId,
                deviceId: dto.deviceId ?? guard.deviceId ?? null
            }
        });
        return updated;
    }
    async checkin(lookup, guardId, entryGate, gateId, offlineEventId, deviceId, scanLocation, user) {
        const now = new Date();
        const where = {
            estateId: lookup.estateId,
            qrToken: lookup.qrToken,
            visitorCode: lookup.visitorCode
        };
        const pass = await this.prisma.visitorPass.findFirst({
            where,
            include: { visitor: true, hostResident: true }
        });
        if (!pass) {
            throw new common_1.NotFoundException('Visitor pass not found');
        }
        if (pass.status !== client_1.VisitorPassStatus.ACTIVE) {
            throw new common_1.BadRequestException(`Visitor pass is ${pass.status.toLowerCase()}`);
        }
        if (pass.validFrom > now || pass.validUntil < now) {
            throw new common_1.BadRequestException('Visitor pass is outside its validity window');
        }
        if (pass.entryCount >= pass.maxEntries) {
            throw new common_1.BadRequestException('Visitor pass has no remaining entries');
        }
        const resolvedGateId = await this.resolveGateId(pass.estateId, gateId, entryGate);
        const session = await this.prisma.$transaction(async (tx) => {
            const updateResult = await tx.visitorPass.updateMany({
                where: {
                    id: pass.id,
                    status: client_1.VisitorPassStatus.ACTIVE,
                    entryCount: { lt: pass.maxEntries }
                },
                data: {
                    entryCount: { increment: 1 }
                }
            });
            if (updateResult.count !== 1) {
                throw new common_1.BadRequestException('Visitor pass has no remaining entries');
            }
            const updatedPass = await tx.visitorPass.findUniqueOrThrow({
                where: { id: pass.id },
                select: { entryCount: true, maxEntries: true }
            });
            if (updatedPass.entryCount >= updatedPass.maxEntries) {
                await tx.visitorPass.update({
                    where: { id: pass.id },
                    data: { status: client_1.VisitorPassStatus.USED }
                });
            }
            return tx.visitSession.create({
                data: {
                    estateId: pass.estateId,
                    visitorPassId: pass.id,
                    guardId,
                    deviceId,
                    gateId: resolvedGateId,
                    visitorPhone: pass.visitor.phone,
                    hostResidentId: pass.hostResidentId,
                    entryGate,
                    offlineEventId,
                    scanLocation: this.toJson(scanLocation),
                    metadata: {
                        visitorId: pass.visitorId,
                        residentId: pass.hostResidentId,
                        visitorPhone: pass.visitor.phone,
                        gateLabel: entryGate ?? null
                    }
                },
                include: { visitorPass: { include: { visitor: true, hostResident: true } } }
            });
        });
        await this.createResidentVisitNotification(session, 'Visitor checked in', 'Your visitor has checked in at the gate.');
        await this.audit.record({
            estateId: pass.estateId,
            actorType: 'GUARD',
            actorId: guardId,
            action: 'VISITOR_CHECKED_IN',
            metadata: {
                visitSessionId: session.id,
                visitorPassId: pass.id,
                deviceId: deviceId ?? null,
                checkedInBy: user.id
            }
        });
        return session;
    }
    async resolveGateId(estateId, gateId, gateName) {
        if (gateId) {
            const gate = await this.prisma.estateGate.findFirst({
                where: { id: gateId, estateId, isActive: true },
                select: { id: true }
            });
            if (!gate) {
                throw new common_1.BadRequestException('Gate is not active in this estate');
            }
            return gate.id;
        }
        if (!gateName) {
            const mainGate = await this.prisma.estateGate.findFirst({
                where: { estateId, isActive: true },
                orderBy: { createdAt: 'asc' },
                select: { id: true }
            });
            return mainGate?.id;
        }
        const gate = await this.prisma.estateGate.findFirst({
            where: { estateId, name: { equals: gateName, mode: 'insensitive' }, isActive: true },
            select: { id: true }
        });
        return gate?.id;
    }
    async createResidentVisitNotification(session, title, body) {
        await this.prisma.appNotification.create({
            data: {
                estateId: session.estateId,
                userId: session.visitorPass.hostResident.userId,
                channel: client_1.NotificationChannel.IN_APP,
                title,
                body: `${body} ${session.visitorPass.visitor.name}`,
                data: {
                    visitSessionId: session.id,
                    visitorName: session.visitorPass.visitor.name,
                    visitorPhone: session.visitorPass.visitor.phone,
                    status: session.status
                }
            }
        });
    }
    toJson(value) {
        if (value === undefined) {
            return undefined;
        }
        return JSON.parse(JSON.stringify(value));
    }
};
exports.CheckinService = CheckinService;
exports.CheckinService = CheckinService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [prisma_service_1.PrismaService,
        audit_service_1.AuditService])
], CheckinService);
//# sourceMappingURL=checkin.service.js.map