"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
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 __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransportService = void 0;
const common_1 = require("@nestjs/common");
const mongoose_1 = require("@nestjs/mongoose");
const mongoose_2 = require("mongoose");
const crypto = __importStar(require("crypto"));
const razorpay_1 = __importDefault(require("razorpay"));
const timing_safe_equal_1 = require("../common/utils/timing-safe-equal");
const transport_route_schema_1 = require("./schemas/transport-route.schema");
const transport_assignment_schema_1 = require("./schemas/transport-assignment.schema");
const student_schema_1 = require("../students/schemas/student.schema");
const academic_year_schema_1 = require("../academic-year/academic-year.schema");
const audit_service_1 = require("../audit/audit.service");
const fees_installment_dates_1 = require("../fees/fees.installment-dates");
const MONTHS = [
    '', 'January', 'February', 'March', 'April', 'May', 'June',
    'July', 'August', 'September', 'October', 'November', 'December',
];
let TransportService = class TransportService {
    routeModel;
    assignmentModel;
    studentModel;
    academicYearModel;
    auditService;
    razor = null;
    razorpayKeyId;
    razorpayKeySecret;
    razorpayWebhookSecret;
    constructor(routeModel, assignmentModel, studentModel, academicYearModel, auditService) {
        this.routeModel = routeModel;
        this.assignmentModel = assignmentModel;
        this.studentModel = studentModel;
        this.academicYearModel = academicYearModel;
        this.auditService = auditService;
        this.razorpayKeyId = process.env.RAZORPAY_KEY_ID || '';
        this.razorpayKeySecret = process.env.RAZORPAY_KEY_SECRET || '';
        this.razorpayWebhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET || '';
    }
    getRazorpay() {
        if (this.razor)
            return this.razor;
        if (!this.razorpayKeyId || !this.razorpayKeySecret) {
            throw new common_1.BadRequestException('Online payments are not configured. Set RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET.');
        }
        this.razor = new razorpay_1.default({ key_id: this.razorpayKeyId, key_secret: this.razorpayKeySecret });
        return this.razor;
    }
    async createRoute(dto, ctx) {
        const ay = await this.academicYearModel.findById(dto.academicYearId).lean();
        if (!ay)
            throw new common_1.BadRequestException('Academic year not found.');
        const route = await this.routeModel.create({
            name: dto.name.trim(),
            description: dto.description,
            academicYearId: dto.academicYearId,
            monthlyFee: dto.monthlyFee,
            dueDay: dto.dueDay,
            stops: dto.stops ?? [],
            vehicleNumber: dto.vehicleNumber,
            driverName: dto.driverName,
            driverPhone: dto.driverPhone,
            lateFine: dto.lateFine ?? { enabled: false, type: 'per_day', amountPerDay: 0, percentage: 0, slabs: [] },
            isActive: dto.isActive ?? true,
        });
        await this.auditService.log({
            entity: 'TransportRoute', entityId: String(route._id),
            action: 'create', after: route.toObject(), ctx,
        });
        return route.toObject();
    }
    async getRoutes(academicYearId, activeOnly) {
        const q = {};
        if (academicYearId) {
            if (!(0, mongoose_2.isValidObjectId)(academicYearId))
                throw new common_1.BadRequestException('Invalid academic year ID');
            q.academicYearId = academicYearId;
        }
        if (activeOnly)
            q.isActive = true;
        return this.routeModel.find(q).sort({ createdAt: -1 }).lean();
    }
    async getRoute(id) {
        const route = await this.routeModel.findById(id).lean();
        if (!route)
            throw new common_1.NotFoundException('Transport route not found');
        return route;
    }
    async updateRoute(id, dto, ctx) {
        const before = await this.routeModel.findById(id).lean();
        if (!before)
            throw new common_1.NotFoundException('Transport route not found');
        if (dto.academicYearId && dto.academicYearId !== before.academicYearId) {
            throw new common_1.BadRequestException('Cannot change academic year. Delete and recreate the route instead.');
        }
        const { academicYearId, ...updateFields } = dto;
        const updated = await this.routeModel
            .findByIdAndUpdate(id, { $set: updateFields }, { new: true, runValidators: true })
            .lean();
        if (updateFields.name && updateFields.name !== before.name) {
            await this.assignmentModel.updateMany({ routeId: id, status: { $ne: 'paid' } }, { $set: { routeName: updateFields.name } });
        }
        await this.auditService.log({
            entity: 'TransportRoute', entityId: id,
            action: 'update', before, after: updated, ctx,
        });
        return updated;
    }
    async deleteRoute(id, ctx) {
        const route = await this.routeModel.findById(id).lean();
        if (!route)
            throw new common_1.NotFoundException('Transport route not found');
        const paidCount = await this.assignmentModel.countDocuments({ routeId: id, status: 'paid' });
        if (paidCount > 0) {
            throw new common_1.BadRequestException('Cannot delete route — some students have already paid.');
        }
        await this.routeModel.findByIdAndUpdate(id, { $set: { isActive: false } });
        const finalPaidCount = await this.assignmentModel.countDocuments({ routeId: id, status: 'paid' });
        if (finalPaidCount > 0) {
            await this.routeModel.findByIdAndUpdate(id, { $set: { isActive: true } });
            throw new common_1.BadRequestException('A payment was recorded while deleting. Route has been preserved and re-activated.');
        }
        await this.assignmentModel.deleteMany({ routeId: id, status: { $ne: 'paid' } });
        await this.routeModel.findByIdAndDelete(id);
        await this.auditService.log({
            entity: 'TransportRoute', entityId: id,
            action: 'delete', before: route, ctx,
        });
        return { deleted: true };
    }
    async assignStudents(dto, ctx) {
        const route = await this.routeModel.findById(dto.routeId).lean();
        if (!route)
            throw new common_1.NotFoundException('Transport route not found');
        const students = await this.studentModel
            .find({ _id: { $in: dto.studentIds }, isActive: true }, { _id: 1 })
            .limit(5000)
            .lean();
        if (students.length === 0) {
            throw new common_1.BadRequestException('No active students found for the given IDs.');
        }
        const ay = await this.academicYearModel.findById(route.academicYearId).lean();
        if (!ay)
            throw new common_1.NotFoundException('Academic year not found for this transport route');
        const ayStart = new Date(ay.startDate);
        const amount = dto.customAmount ?? route.monthlyFee;
        const months = dto.months && dto.months.length > 0
            ? dto.months.filter((m) => m >= 1 && m <= 12)
            : Array.from({ length: 12 }, (_, i) => i + 1);
        const makeDueDate = (month) => {
            const year = (0, fees_installment_dates_1.yearForMonthInAY)(ayStart, month);
            const day = (0, fees_installment_dates_1.clampDayToMonth)(year, month, route.dueDay);
            return new Date(Date.UTC(year, month - 1, day));
        };
        const docs = [];
        for (const stu of students) {
            for (const month of months) {
                docs.push({
                    studentId: String(stu._id),
                    routeId: String(route._id),
                    academicYearId: route.academicYearId,
                    routeName: route.name,
                    installment: MONTHS[month],
                    amount,
                    fineAmount: 0,
                    dueDate: makeDueDate(month),
                    status: 'pending',
                    pickupPoint: dto.pickupPoint,
                });
            }
        }
        let created = 0;
        for (let i = 0; i < docs.length; i += 5000) {
            const batch = docs.slice(i, i + 5000);
            const result = await this.assignmentModel.insertMany(batch, { ordered: false }).catch((err) => {
                if (err?.code === 11000 || err?.writeErrors) {
                    return { insertedCount: batch.length - (err.writeErrors?.length ?? 0) };
                }
                throw err;
            });
            created += result.insertedCount ?? result.length ?? batch.length;
        }
        await this.auditService.log({
            entity: 'TransportAssignment', entityId: String(route._id),
            action: 'create', after: { routeId: dto.routeId, studentCount: students.length, months, amount, created }, ctx,
        });
        return { created };
    }
    async getAssignments(filter) {
        const q = {};
        if (filter.academicYearId)
            q.academicYearId = filter.academicYearId;
        if (filter.routeId)
            q.routeId = filter.routeId;
        if (filter.studentId)
            q.studentId = filter.studentId;
        if (filter.month)
            q.installment = filter.month;
        if (filter.isActive === 'true')
            q.isActive = true;
        else if (filter.isActive === 'false')
            q.isActive = false;
        if (filter.status) {
            if (filter.status === 'overdue') {
                q.status = 'pending';
                q.dueDate = { $lt: new Date() };
            }
            else if (filter.status === 'pending') {
                q.status = 'pending';
                q.dueDate = { $gte: new Date() };
            }
            else {
                q.status = filter.status;
            }
        }
        const assignments = await this.assignmentModel.find(q).sort({ dueDate: 1 }).limit(5000).lean();
        const withFines = await this.recalculateFinesWithRoutes(assignments);
        const studentIds = [...new Set(withFines.map((a) => a.studentId))];
        if (studentIds.length > 0) {
            const students = await this.studentModel
                .find({ _id: { $in: studentIds } }, { name: 1, uniqueId: 1, rollNumber: 1 })
                .lean();
            const studentMap = new Map(students.map((s) => [String(s._id), s]));
            return withFines.map((a) => {
                const stu = studentMap.get(a.studentId);
                return { ...a, studentName: stu?.name, studentUniqueId: stu?.uniqueId, studentRollNumber: stu?.rollNumber };
            });
        }
        return withFines;
    }
    async syncAssignments(routeId) {
        const query = { isActive: true };
        if (routeId)
            query._id = routeId;
        const routes = await this.routeModel.find(query).lean();
        let created = 0;
        for (const route of routes) {
            const rid = String(route._id);
            const assignedStudentIds = await this.assignmentModel.distinct('studentId', { routeId: rid });
            if (assignedStudentIds.length === 0)
                continue;
            const existing = await this.assignmentModel.find({ routeId: rid }, { studentId: 1, installment: 1 }).lean();
            const existingSet = new Set(existing.map((a) => `${a.studentId}_${a.installment}`));
            const ay = await this.academicYearModel.findById(route.academicYearId).lean();
            if (!ay)
                continue;
            const ayStart = new Date(ay.startDate);
            const makeDueDate = (month) => {
                const year = (0, fees_installment_dates_1.yearForMonthInAY)(ayStart, month);
                const day = (0, fees_installment_dates_1.clampDayToMonth)(year, month, route.dueDay);
                return new Date(Date.UTC(year, month - 1, day));
            };
            const docs = [];
            for (const studentId of assignedStudentIds) {
                for (let month = 1; month <= 12; month++) {
                    const key = `${studentId}_${MONTHS[month]}`;
                    if (existingSet.has(key))
                        continue;
                    docs.push({
                        studentId,
                        routeId: rid,
                        academicYearId: route.academicYearId,
                        routeName: route.name,
                        installment: MONTHS[month],
                        amount: route.monthlyFee,
                        fineAmount: 0,
                        dueDate: makeDueDate(month),
                        status: 'pending',
                    });
                }
            }
            if (docs.length > 0) {
                for (let i = 0; i < docs.length; i += 5000) {
                    const batch = docs.slice(i, i + 5000);
                    const result = await this.assignmentModel.insertMany(batch, { ordered: false }).catch((err) => {
                        if (err?.code === 11000 || err?.writeErrors) {
                            return { insertedCount: batch.length - (err.writeErrors?.length ?? 0) };
                        }
                        throw err;
                    });
                    created += result.insertedCount ?? result.length ?? 0;
                }
            }
        }
        return { synced: created };
    }
    async toggleTransport(studentId, dto, ctx) {
        const student = await this.studentModel.findById(studentId, { _id: 1 }).lean();
        if (!student)
            throw new common_1.NotFoundException('Student not found');
        const result = await this.assignmentModel.updateMany({ studentId, routeId: dto.routeId, academicYearId: dto.academicYearId, status: { $ne: 'paid' } }, { $set: { isActive: dto.isActive } });
        const paidResult = await this.assignmentModel.updateMany({ studentId, routeId: dto.routeId, academicYearId: dto.academicYearId, status: 'paid' }, { $set: { isActive: dto.isActive } });
        await this.auditService.log({
            entity: 'TransportAssignment', entityId: `${studentId}_${dto.routeId}`,
            action: 'update', after: { studentId, routeId: dto.routeId, isActive: dto.isActive }, ctx,
        });
        return { updated: result.matchedCount + paidResult.matchedCount, isActive: dto.isActive };
    }
    async removeTransport(studentId, routeId, academicYearId, ctx) {
        const student = await this.studentModel.findById(studentId, { _id: 1 }).lean();
        if (!student)
            throw new common_1.NotFoundException('Student not found');
        const deleteResult = await this.assignmentModel.deleteMany({
            studentId, routeId, academicYearId, status: { $ne: 'paid' },
        });
        const kept = await this.assignmentModel.countDocuments({
            studentId, routeId, academicYearId, status: 'paid',
        });
        await this.auditService.log({
            entity: 'TransportAssignment', entityId: `${studentId}_${routeId}`,
            action: 'delete', before: { studentId, routeId, deleted: deleteResult.deletedCount, kept }, ctx,
        });
        return { deleted: deleteResult.deletedCount, kept };
    }
    async getStudentTransportStatus(studentId, academicYearId) {
        const student = await this.studentModel.findById(studentId, { academicYearId: 1 }).lean();
        if (!student)
            throw new common_1.NotFoundException('Student not found');
        if (academicYearId && !(0, mongoose_2.isValidObjectId)(academicYearId)) {
            throw new common_1.BadRequestException('Invalid academic year ID');
        }
        const ayId = academicYearId || student.academicYearId;
        if (!ayId)
            return [];
        const assignments = await this.assignmentModel.find({ studentId, academicYearId: ayId }, { routeId: 1, routeName: 1, isActive: 1, status: 1, pickupPoint: 1, amount: 1 }).lean();
        const routeMap = new Map();
        for (const a of assignments) {
            const rid = String(a.routeId);
            if (!routeMap.has(rid)) {
                routeMap.set(rid, {
                    routeId: rid,
                    routeName: a.routeName,
                    isActive: a.isActive !== false,
                    pendingCount: 0,
                    paidCount: 0,
                    pickupPoint: a.pickupPoint,
                    monthlyFee: a.amount,
                });
            }
            const entry = routeMap.get(rid);
            if (a.status === 'paid')
                entry.paidCount++;
            else {
                entry.pendingCount++;
                entry.isActive = a.isActive !== false;
            }
        }
        return Array.from(routeMap.values());
    }
    async getStudentDashboard(studentId, academicYearId) {
        const student = await this.studentModel.findById(studentId, {
            name: 1, classId: 1, academicYearId: 1, rollNumber: 1,
            sectionId: 1, profileImageUrl: 1, uniqueId: 1,
            guardianName: 1, father: 1, mother: 1, phone: 1, email: 1,
        }).lean();
        if (!student)
            throw new common_1.NotFoundException('Student not found');
        if (academicYearId && !(0, mongoose_2.isValidObjectId)(academicYearId)) {
            throw new common_1.BadRequestException('Invalid academic year ID');
        }
        const ayId = academicYearId || student.academicYearId;
        const raw = await this.assignmentModel
            .find({ studentId, academicYearId: ayId })
            .sort({ dueDate: 1 })
            .lean();
        const assignments = await this.recalculateFinesWithRoutes(raw);
        const activeAssignments = assignments.filter((a) => a.isActive !== false);
        const totalFee = activeAssignments.reduce((s, a) => s + a.amount + (a.fineAmount || 0), 0);
        const paidAmount = activeAssignments
            .filter((a) => a.status === 'paid')
            .reduce((s, a) => s + a.amount + (a.fineAmount || 0), 0);
        const dueAmount = totalFee - paidAmount;
        const unpaid = activeAssignments.filter((a) => a.status !== 'paid');
        const nextDueDate = unpaid.length > 0
            ? new Date(Math.min(...unpaid.map((a) => new Date(a.dueDate).getTime()))).toISOString()
            : null;
        return {
            student,
            summary: { totalFee, paidAmount, dueAmount, nextDueDate },
            assignments,
        };
    }
    async createRazorpayOrder(assignmentId, studentId) {
        const assignment = await this.assignmentModel.findOne({ _id: assignmentId, studentId });
        if (!assignment)
            throw new common_1.NotFoundException('Transport assignment not found');
        if (assignment.status === 'paid') {
            throw new common_1.BadRequestException('This fee is already paid.');
        }
        if (assignment.isActive === false) {
            throw new common_1.BadRequestException('Transport is currently inactive. Contact admin.');
        }
        const fine = await this.calculateFineFromRoute(assignment.toObject());
        const totalAmount = assignment.amount + fine;
        if (assignment.razorpayOrderId) {
            const existingTotal = assignment.amount + (assignment.fineAmount || 0);
            if (existingTotal === totalAmount) {
                return {
                    orderId: assignment.razorpayOrderId,
                    amount: Math.round(totalAmount * 100),
                    currency: 'INR',
                    keyId: this.razorpayKeyId,
                    assignmentId: String(assignment._id),
                };
            }
            assignment.razorpayOrderId = undefined;
        }
        const order = await this.getRazorpay().orders.create({
            amount: Math.round(totalAmount * 100),
            currency: 'INR',
            receipt: `transport_${assignmentId}_${Date.now()}`,
            notes: { assignmentId, studentId, type: 'transport' },
        });
        assignment.razorpayOrderId = order.id;
        assignment.fineAmount = fine;
        await assignment.save();
        return {
            orderId: order.id,
            amount: order.amount,
            currency: order.currency,
            keyId: this.razorpayKeyId,
            assignmentId: String(assignment._id),
        };
    }
    async verifyRazorpayPayment(assignmentId, studentId, body) {
        if (!this.razorpayKeySecret) {
            throw new common_1.BadRequestException('Payment verification is not configured.');
        }
        const sign = crypto
            .createHmac('sha256', this.razorpayKeySecret)
            .update(`${body.razorpay_order_id}|${body.razorpay_payment_id}`)
            .digest('hex');
        if (!(0, timing_safe_equal_1.timingSafeEqual)(sign, body.razorpay_signature)) {
            throw new common_1.BadRequestException('Payment signature verification failed');
        }
        const assignment = await this.assignmentModel.findOne({
            _id: assignmentId,
            studentId,
            razorpayOrderId: body.razorpay_order_id,
        }).lean();
        if (!assignment)
            throw new common_1.NotFoundException('Transport assignment not found');
        if (assignment.status === 'paid') {
            return { status: 'paid', receiptNumber: assignment.receiptNumber };
        }
        const fine = await this.calculateFineFromRoute(assignment);
        try {
            const payment = await this.getRazorpay().payments.fetch(body.razorpay_payment_id);
            const expectedPaise = Math.round((assignment.amount + fine) * 100);
            if (Number(payment.amount) !== expectedPaise) {
                throw new common_1.BadRequestException(`Payment amount mismatch: expected ${expectedPaise} paise, got ${payment.amount} paise`);
            }
        }
        catch (err) {
            if (err instanceof common_1.BadRequestException)
                throw err;
        }
        const receiptNumber = await this.generateReceiptNumber();
        const claimed = await this.assignmentModel.findOneAndUpdate({ _id: assignmentId, studentId, status: { $ne: 'paid' } }, {
            $set: {
                status: 'paid',
                paidAt: new Date(),
                paymentMethod: 'razorpay',
                razorpayPaymentId: body.razorpay_payment_id,
                razorpaySignature: body.razorpay_signature,
                fineAmount: fine,
                receiptNumber,
            },
        }, { new: true });
        if (!claimed) {
            const existing = await this.assignmentModel.findById(assignmentId).lean();
            return { status: 'paid', receiptNumber: existing?.receiptNumber };
        }
        return { status: 'paid', receiptNumber };
    }
    async recordOfflinePayment(assignmentId, dto, adminId) {
        const assignment = await this.assignmentModel.findById(assignmentId).lean();
        if (!assignment)
            throw new common_1.NotFoundException('Transport assignment not found');
        if (assignment.status === 'paid') {
            throw new common_1.BadRequestException('This fee is already paid.');
        }
        const fine = await this.calculateFineFromRoute(assignment);
        const receiptNumber = await this.generateReceiptNumber();
        const claimed = await this.assignmentModel.findOneAndUpdate({ _id: assignmentId, status: { $ne: 'paid' } }, {
            $set: {
                status: 'paid',
                paidAt: new Date(),
                paymentMethod: dto.paymentMethod,
                collectedBy: adminId,
                notes: dto.notes,
                fineAmount: fine,
                receiptNumber,
            },
        }, { new: true }).lean();
        if (!claimed) {
            throw new common_1.BadRequestException('This fee is already paid.');
        }
        return { ...claimed, receiptNumber };
    }
    async handleWebhook(signature, rawBody, jsonBody) {
        if (!this.razorpayWebhookSecret)
            throw new common_1.BadRequestException('Webhook not configured');
        const expected = crypto.createHmac('sha256', this.razorpayWebhookSecret).update(rawBody).digest('hex');
        if (!signature || !(0, timing_safe_equal_1.timingSafeEqual)(expected, signature)) {
            return { received: true };
        }
        const event = jsonBody?.event;
        const entity = jsonBody?.payload?.payment?.entity || jsonBody?.payload?.order?.entity;
        if (event === 'payment.captured' && entity) {
            const orderId = entity.order_id;
            const notes = entity.notes || {};
            if (notes.type !== 'transport')
                return { received: true };
            const assignment = await this.assignmentModel.findOne({ razorpayOrderId: orderId }).lean();
            if (assignment && assignment.status !== 'paid') {
                const fine = await this.calculateFineFromRoute(assignment);
                const expectedPaise = Math.round((assignment.amount + fine) * 100);
                if (Number(entity.amount) !== expectedPaise) {
                    return { received: true };
                }
                const receiptNumber = await this.generateReceiptNumber();
                await this.assignmentModel.findOneAndUpdate({ razorpayOrderId: orderId, status: { $ne: 'paid' } }, {
                    $set: {
                        status: 'paid',
                        paidAt: new Date(),
                        razorpayPaymentId: entity.id,
                        paymentMethod: 'razorpay',
                        fineAmount: fine,
                        receiptNumber,
                    },
                });
            }
        }
        return { received: true };
    }
    async verifyReceipt(receiptNumber) {
        const assignment = await this.assignmentModel.findOne({ receiptNumber }, { studentId: 1, routeName: 1, installment: 1, amount: 1, fineAmount: 1, status: 1, paidAt: 1, paymentMethod: 1, receiptNumber: 1, pickupPoint: 1 }).lean();
        if (!assignment)
            return null;
        const student = await this.studentModel.findById(assignment.studentId, { name: 1, uniqueId: 1, rollNumber: 1 }).lean();
        return {
            receiptNumber: assignment.receiptNumber,
            studentName: student?.name || 'Unknown',
            studentId: student?.uniqueId || '',
            rollNumber: student?.rollNumber || '',
            feeName: `Transport — ${assignment.routeName}`,
            installment: assignment.installment,
            amount: assignment.amount,
            fineAmount: assignment.fineAmount || 0,
            totalPaid: assignment.amount + (assignment.fineAmount || 0),
            status: assignment.status,
            paidAt: assignment.paidAt,
            paymentMethod: assignment.paymentMethod,
        };
    }
    computeFine(lateFine, daysLate, baseAmount) {
        if (!lateFine?.enabled || daysLate <= 0)
            return 0;
        const fineType = lateFine.type || 'per_day';
        switch (fineType) {
            case 'percentage':
                return Math.round(baseAmount * (lateFine.percentage || 0) / 100);
            case 'day_wise_slab': {
                const slabs = lateFine.slabs || [];
                for (const slab of slabs) {
                    if (daysLate >= slab.startDay && daysLate <= slab.endDay) {
                        return Math.round(slab.amount);
                    }
                }
                if (slabs.length > 0)
                    return Math.round(slabs[slabs.length - 1].amount);
                return 0;
            }
            case 'per_day':
            default:
                return Math.round(daysLate * (lateFine.amountPerDay || 0));
        }
    }
    async calculateFineFromRoute(assignment) {
        if (assignment.status === 'paid')
            return assignment.fineAmount || 0;
        if (assignment.isActive === false)
            return 0;
        const dueDate = new Date(assignment.dueDate);
        const now = new Date();
        if (now <= dueDate)
            return 0;
        if (assignment.createdAt && new Date(assignment.createdAt) > dueDate)
            return 0;
        const route = await this.routeModel.findById(assignment.routeId, { lateFine: 1 }).lean();
        if (!route?.lateFine?.enabled)
            return 0;
        const diffMs = now.getTime() - dueDate.getTime();
        const daysLate = Math.floor(diffMs / (1000 * 60 * 60 * 24));
        return this.computeFine(route.lateFine, daysLate, assignment.amount);
    }
    async recalculateFinesWithRoutes(assignments) {
        if (assignments.length === 0)
            return assignments;
        const routeIds = [...new Set(assignments.map((a) => a.routeId))];
        const routes = await this.routeModel.find({ _id: { $in: routeIds } }, { lateFine: 1 }).lean();
        const routeMap = new Map(routes.map((r) => [String(r._id), r]));
        const now = new Date();
        return assignments.map((a) => {
            if (a.status === 'paid')
                return a;
            if (a.isActive === false)
                return { ...a, fineAmount: 0, status: 'pending' };
            const dueDate = new Date(a.dueDate);
            if (now <= dueDate)
                return { ...a, fineAmount: 0, status: 'pending' };
            const createdAt = a.createdAt ? new Date(a.createdAt) : null;
            if (createdAt && createdAt > dueDate)
                return { ...a, fineAmount: 0, status: 'overdue' };
            const route = routeMap.get(String(a.routeId));
            const diffMs = now.getTime() - dueDate.getTime();
            const daysLate = Math.floor(diffMs / (1000 * 60 * 60 * 24));
            const fine = route?.lateFine?.enabled
                ? this.computeFine(route.lateFine, daysLate, a.amount)
                : 0;
            return { ...a, fineAmount: fine, status: 'overdue' };
        });
    }
    async generateReceiptNumber() {
        const year = new Date().getFullYear();
        const prefix = `TRCPT-${year}-`;
        for (let attempt = 0; attempt < 3; attempt++) {
            const count = await this.assignmentModel.countDocuments({
                receiptNumber: { $regex: `^${prefix}\\d+$` },
            });
            const seq = count + 1 + attempt;
            const receiptNumber = `${prefix}${String(seq).padStart(6, '0')}`;
            const exists = await this.assignmentModel.exists({ receiptNumber });
            if (!exists)
                return receiptNumber;
        }
        return `${prefix}${crypto.randomBytes(4).toString('hex').toUpperCase()}`;
    }
};
exports.TransportService = TransportService;
exports.TransportService = TransportService = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, mongoose_1.InjectModel)(transport_route_schema_1.TransportRoute.name)),
    __param(1, (0, mongoose_1.InjectModel)(transport_assignment_schema_1.TransportAssignment.name)),
    __param(2, (0, mongoose_1.InjectModel)(student_schema_1.Student.name)),
    __param(3, (0, mongoose_1.InjectModel)(academic_year_schema_1.AcademicYear.name)),
    __metadata("design:paramtypes", [mongoose_2.Model,
        mongoose_2.Model,
        mongoose_2.Model,
        mongoose_2.Model,
        audit_service_1.AuditService])
], TransportService);
//# sourceMappingURL=transport.service.js.map