"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);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var VouchersService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.VouchersService = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const voucher_entity_1 = require("../database/entities/voucher.entity");
const voucher_batch_entity_1 = require("../database/entities/voucher-batch.entity");
const package_entity_1 = require("../database/entities/package.entity");
const customer_entity_1 = require("../database/entities/customer.entity");
const customer_device_entity_1 = require("../database/entities/customer-device.entity");
const wifi_session_entity_1 = require("../database/entities/wifi-session.entity");
const payment_entity_1 = require("../database/entities/payment.entity");
const router_entity_1 = require("../database/entities/router.entity");
const audit_log_entity_1 = require("../database/entities/audit-log.entity");
const id_generator_util_1 = require("../common/utils/id-generator.util");
const sessions_service_1 = require("../sessions/sessions.service");
const sync_1 = require("csv-stringify/sync");
let VouchersService = VouchersService_1 = class VouchersService {
    constructor(voucherRepo, voucherBatchRepo, packageRepo, customerRepo, customerDeviceRepo, wifiSessionRepo, paymentRepo, routerRepo, auditLogRepo, sessionsService) {
        this.voucherRepo = voucherRepo;
        this.voucherBatchRepo = voucherBatchRepo;
        this.packageRepo = packageRepo;
        this.customerRepo = customerRepo;
        this.customerDeviceRepo = customerDeviceRepo;
        this.wifiSessionRepo = wifiSessionRepo;
        this.paymentRepo = paymentRepo;
        this.routerRepo = routerRepo;
        this.auditLogRepo = auditLogRepo;
        this.sessionsService = sessionsService;
        this.logger = new common_1.Logger(VouchersService_1.name);
    }
    async generateSingle(dto, businessId) {
        const pkg = await this.packageRepo.findOne({
            where: { id: dto.packageId, businessId },
        });
        if (!pkg) {
            throw new common_1.NotFoundException(`Package ${dto.packageId} not found`);
        }
        const code = (0, id_generator_util_1.generateVoucherCode)(dto.prefix);
        const voucher = this.voucherRepo.create({
            businessId,
            code,
            packageId: dto.packageId,
            status: voucher_entity_1.VoucherStatus.UNUSED,
            expiryDate: dto.expiryDate ? new Date(dto.expiryDate) : undefined,
        });
        const saved = await this.voucherRepo.save(voucher);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId,
            action: 'VOUCHER_CREATED',
            entityType: 'Voucher',
            entityId: saved.id,
            newValue: { code, packageId: dto.packageId },
        }));
        this.logger.log(`Voucher ${code} created for business ${businessId}`);
        return saved;
    }
    async generateBulk(dto, businessId) {
        const pkg = await this.packageRepo.findOne({
            where: { id: dto.packageId, businessId },
        });
        if (!pkg) {
            throw new common_1.NotFoundException(`Package ${dto.packageId} not found`);
        }
        const batch = this.voucherBatchRepo.create({
            businessId,
            prefix: dto.prefix,
            count: dto.count,
            packageId: dto.packageId,
            expiryDate: dto.expiryDate ? new Date(dto.expiryDate) : undefined,
            notes: dto.description || undefined,
            generatedBy: businessId,
        });
        const savedBatch = await this.voucherBatchRepo.save(batch);
        const vouchers = [];
        const usedCodes = new Set();
        for (let i = 0; i < dto.count; i++) {
            let code;
            let attempts = 0;
            do {
                code = (0, id_generator_util_1.generateVoucherCode)(dto.prefix);
                attempts++;
                if (attempts > 100) {
                    throw new common_1.BadRequestException('Failed to generate unique voucher code after 100 attempts');
                }
            } while (usedCodes.has(code));
            usedCodes.add(code);
            vouchers.push(this.voucherRepo.create({
                businessId,
                batchId: savedBatch.id,
                code,
                packageId: dto.packageId,
                status: voucher_entity_1.VoucherStatus.UNUSED,
                expiryDate: dto.expiryDate ? new Date(dto.expiryDate) : undefined,
            }));
        }
        const savedVouchers = await this.voucherRepo.save(vouchers);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId,
            action: 'VOUCHER_BULK_CREATED',
            entityType: 'VoucherBatch',
            entityId: savedBatch.id,
            newValue: {
                batchId: savedBatch.id,
                count: dto.count,
                prefix: dto.prefix,
                packageId: dto.packageId,
            },
        }));
        this.logger.log(`${dto.count} vouchers created in batch ${savedBatch.id} for business ${businessId}`);
        return { batch: savedBatch, vouchers: savedVouchers };
    }
    async redeem(dto, businessId) {
        const voucher = await this.voucherRepo.findOne({
            where: { code: dto.code },
            relations: ['package'],
        });
        if (!voucher) {
            throw new common_1.NotFoundException(`Voucher with code ${dto.code} not found`);
        }
        if (businessId && voucher.businessId !== businessId) {
            throw new common_1.BadRequestException('Voucher does not belong to this business');
        }
        if (voucher.status === voucher_entity_1.VoucherStatus.USED) {
            throw new common_1.BadRequestException('Voucher has already been used');
        }
        if (voucher.status === voucher_entity_1.VoucherStatus.DISABLED) {
            throw new common_1.BadRequestException('Voucher has been disabled');
        }
        if (voucher.expiryDate && new Date() > voucher.expiryDate) {
            voucher.status = voucher_entity_1.VoucherStatus.EXPIRED;
            await this.voucherRepo.save(voucher);
            throw new common_1.BadRequestException('Voucher has expired');
        }
        const effectiveBusinessId = businessId || voucher.businessId;
        const customer = await this.findOrCreateCustomer(dto.phoneNumber || 'unknown', effectiveBusinessId);
        let device = null;
        if (dto.macAddress) {
            device = await this.findOrCreateDevice(customer.id, dto);
        }
        const router = await this.routerRepo.findOne({
            where: { businessId: effectiveBusinessId, isOnline: true, isActive: true },
            order: { activeSessions: 'ASC' },
        });
        if (!router) {
            throw new common_1.BadRequestException('No online router available for session activation');
        }
        voucher.status = voucher_entity_1.VoucherStatus.USED;
        voucher.usedByCustomerId = customer.id;
        voucher.usedAt = new Date();
        await this.voucherRepo.save(voucher);
        const internalReference = (0, id_generator_util_1.generatePaymentReference)();
        const pkg = await this.packageRepo.findOne({
            where: { id: voucher.packageId },
        });
        const payment = this.paymentRepo.create({
            businessId: effectiveBusinessId,
            customerId: customer.id,
            packageId: voucher.packageId,
            routerId: router.id,
            internalReference,
            phoneNumber: customer.phoneNumber,
            amount: pkg ? Number(pkg.priceKes) : 0,
            status: payment_entity_1.PaymentStatus.SUCCESSFUL,
            notes: `Voucher redemption: ${voucher.code}`,
        });
        const savedPayment = await this.paymentRepo.save(payment);
        let session;
        try {
            session = await this.sessionsService.activateSession(savedPayment, customer);
        }
        catch (error) {
            this.logger.error(`Session activation failed for voucher ${voucher.code}: ${error.message}`);
            session = this.wifiSessionRepo.create({
                customerId: customer.id,
                packageId: voucher.packageId,
                routerId: router.id,
                businessId: effectiveBusinessId,
                deviceId: device?.id || undefined,
                macAddress: dto.macAddress || undefined,
                ipAddress: dto.ipAddress || undefined,
                status: wifi_session_entity_1.WifiSessionStatus.ACTIVATION_FAILED,
                lastActivationError: error.message,
            });
            session = await this.wifiSessionRepo.save(session);
        }
        customer.totalSessions += 1;
        customer.lastConnectionAt = new Date();
        await this.customerRepo.save(customer);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId: effectiveBusinessId,
            action: 'VOUCHER_REDEEMED',
            entityType: 'Voucher',
            entityId: voucher.id,
            newValue: {
                voucherCode: voucher.code,
                customerId: customer.id,
                paymentId: savedPayment.id,
                sessionId: session.id,
            },
        }));
        this.logger.log(`Voucher ${voucher.code} redeemed by customer ${customer.id}`);
        return { session, voucher };
    }
    async disable(id, businessId) {
        const voucher = await this.voucherRepo.findOne({
            where: { id, businessId },
        });
        if (!voucher) {
            throw new common_1.NotFoundException(`Voucher ${id} not found`);
        }
        if (voucher.status === voucher_entity_1.VoucherStatus.USED) {
            throw new common_1.BadRequestException('Cannot disable a used voucher');
        }
        voucher.status = voucher_entity_1.VoucherStatus.DISABLED;
        const updated = await this.voucherRepo.save(voucher);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId,
            action: 'VOUCHER_DISABLED',
            entityType: 'Voucher',
            entityId: voucher.id,
            newValue: { status: voucher_entity_1.VoucherStatus.DISABLED },
        }));
        this.logger.log(`Voucher ${id} disabled`);
        return updated;
    }
    async getBatchVouchers(batchId, businessId) {
        const batch = await this.voucherBatchRepo.findOne({
            where: { id: batchId, businessId },
        });
        if (!batch) {
            throw new common_1.NotFoundException(`Batch ${batchId} not found`);
        }
        return this.voucherRepo.find({
            where: { batchId, businessId },
            relations: ['package'],
            order: { createdAt: 'DESC' },
        });
    }
    async getAll(businessId, filters) {
        const page = filters.page ?? 1;
        const limit = filters.limit ?? 20;
        const queryBuilder = this.voucherRepo
            .createQueryBuilder('voucher')
            .leftJoinAndSelect('voucher.package', 'package')
            .where('voucher.businessId = :businessId', { businessId });
        if (filters.status) {
            queryBuilder.andWhere('voucher.status = :status', { status: filters.status });
        }
        if (filters.batchId) {
            queryBuilder.andWhere('voucher.batchId = :batchId', {
                batchId: filters.batchId,
            });
        }
        queryBuilder
            .orderBy('voucher.createdAt', 'DESC')
            .skip((page - 1) * limit)
            .take(limit);
        const [data, total] = await queryBuilder.getManyAndCount();
        const totalPages = Math.ceil(total / limit);
        return { data, total, page, limit, totalPages };
    }
    async exportVouchers(businessId, batchId) {
        const where = { businessId };
        if (batchId) {
            where.batchId = batchId;
        }
        const vouchers = await this.voucherRepo.find({
            where,
            relations: ['package'],
            order: { createdAt: 'DESC' },
        });
        const rows = vouchers.map((v) => ({
            Code: v.code,
            Status: v.status,
            Package: v.package?.name || 'N/A',
            'Created At': v.createdAt?.toISOString() || '',
            'Expiry Date': v.expiryDate?.toISOString() || '',
            'Used At': v.usedAt?.toISOString() || '',
            'Batch ID': v.batchId || '',
        }));
        const csv = (0, sync_1.stringify)(rows, {
            header: true,
            columns: [
                'Code',
                'Status',
                'Package',
                'Created At',
                'Expiry Date',
                'Used At',
                'Batch ID',
            ],
        });
        return csv;
    }
    async getBatch(id, businessId) {
        const batch = await this.voucherBatchRepo.findOne({
            where: { id, businessId },
            relations: ['package'],
        });
        if (!batch) {
            throw new common_1.NotFoundException(`Batch ${id} not found`);
        }
        const vouchers = await this.voucherRepo.find({
            where: { batchId: id, businessId },
            relations: ['package'],
            order: { createdAt: 'DESC' },
        });
        return { batch, vouchers };
    }
    async getAllBatches(businessId) {
        return this.voucherBatchRepo.find({
            where: { businessId },
            relations: ['package'],
            order: { createdAt: 'DESC' },
        });
    }
    async getVoucherStats(businessId) {
        const todayStart = new Date();
        todayStart.setHours(0, 0, 0, 0);
        const [total, unused, used, disabled, expired, generatedToday] = await Promise.all([
            this.voucherRepo.count({ where: { businessId } }),
            this.voucherRepo.count({
                where: { businessId, status: voucher_entity_1.VoucherStatus.UNUSED },
            }),
            this.voucherRepo.count({
                where: { businessId, status: voucher_entity_1.VoucherStatus.USED },
            }),
            this.voucherRepo.count({
                where: { businessId, status: voucher_entity_1.VoucherStatus.DISABLED },
            }),
            this.voucherRepo.count({
                where: { businessId, status: voucher_entity_1.VoucherStatus.EXPIRED },
            }),
            this.voucherRepo.count({
                where: {
                    businessId,
                    createdAt: (0, typeorm_2.MoreThanOrEqual)(todayStart),
                },
            }),
        ]);
        return { total, unused, used, disabled, expired, generatedToday };
    }
    async findOrCreateCustomer(phoneNumber, businessId) {
        let customer = await this.customerRepo.findOne({
            where: { phoneNumber, businessId },
        });
        if (!customer) {
            customer = this.customerRepo.create({
                phoneNumber,
                businessId,
            });
            customer = await this.customerRepo.save(customer);
            this.logger.log(`Created new customer: ${customer.id} (${phoneNumber})`);
        }
        return customer;
    }
    async findOrCreateDevice(customerId, deviceInfo) {
        let device = await this.customerDeviceRepo.findOne({
            where: { customerId, macAddress: deviceInfo.macAddress },
        });
        if (!device) {
            device = this.customerDeviceRepo.create({
                customerId,
                macAddress: deviceInfo.macAddress,
                ipAddress: deviceInfo.ipAddress || undefined,
                deviceName: deviceInfo.deviceName || undefined,
                deviceType: deviceInfo.deviceType || undefined,
                browserInfo: deviceInfo.browserInfo || undefined,
                firstSeenAt: new Date(),
                lastSeenAt: new Date(),
            });
            device = await this.customerDeviceRepo.save(device);
        }
        else {
            device.lastSeenAt = new Date();
            if (deviceInfo.ipAddress)
                device.ipAddress = deviceInfo.ipAddress;
            await this.customerDeviceRepo.save(device);
        }
        return device;
    }
};
exports.VouchersService = VouchersService;
exports.VouchersService = VouchersService = VouchersService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(voucher_entity_1.Voucher)),
    __param(1, (0, typeorm_1.InjectRepository)(voucher_batch_entity_1.VoucherBatch)),
    __param(2, (0, typeorm_1.InjectRepository)(package_entity_1.Package)),
    __param(3, (0, typeorm_1.InjectRepository)(customer_entity_1.Customer)),
    __param(4, (0, typeorm_1.InjectRepository)(customer_device_entity_1.CustomerDevice)),
    __param(5, (0, typeorm_1.InjectRepository)(wifi_session_entity_1.WifiSession)),
    __param(6, (0, typeorm_1.InjectRepository)(payment_entity_1.Payment)),
    __param(7, (0, typeorm_1.InjectRepository)(router_entity_1.Router)),
    __param(8, (0, typeorm_1.InjectRepository)(audit_log_entity_1.AuditLog)),
    __param(9, (0, common_1.Inject)((0, common_1.forwardRef)(() => sessions_service_1.SessionsService))),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        sessions_service_1.SessionsService])
], VouchersService);
