"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 JobsService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JobNames = exports.JobsService = exports.QUEUES = exports.JOB_STATUS = void 0;
const common_1 = require("@nestjs/common");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const schedule_1 = require("@nestjs/schedule");
const uuid_1 = require("uuid");
const config_service_1 = require("../common/config/config.service");
const background_job_entity_1 = require("../database/entities/background-job.entity");
const failed_job_entity_1 = require("../database/entities/failed-job.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");
exports.JOB_STATUS = {
    PENDING: 'PENDING',
    ACTIVE: 'ACTIVE',
    COMPLETED: 'COMPLETED',
    FAILED: 'FAILED',
    RETRYING: 'RETRYING',
};
exports.QUEUES = {
    PAYMENT: 'PAYMENT_QUEUE',
    SESSION: 'SESSION_QUEUE',
    ROUTER: 'ROUTER_QUEUE',
    NOTIFICATION: 'NOTIFICATION_QUEUE',
    REPORT: 'REPORT_QUEUE',
    SYSTEM: 'SYSTEM_QUEUE',
};
const RETRY_DELAYS = [60_000, 300_000, 900_000];
const MAX_RETRIES = 3;
let JobsService = JobsService_1 = class JobsService {
    constructor(backgroundJobRepo, failedJobRepo, wifiSessionRepo, paymentRepo, routerRepo, auditLogRepo, configService) {
        this.backgroundJobRepo = backgroundJobRepo;
        this.failedJobRepo = failedJobRepo;
        this.wifiSessionRepo = wifiSessionRepo;
        this.paymentRepo = paymentRepo;
        this.routerRepo = routerRepo;
        this.auditLogRepo = auditLogRepo;
        this.configService = configService;
        this.logger = new common_1.Logger(JobsService_1.name);
        this.activeJobs = new Map();
    }
    async addJob(queue, name, payload, delayMs) {
        const jobId = (0, uuid_1.v4)();
        const job = this.backgroundJobRepo.create({
            jobId,
            queue,
            name,
            data: payload,
            status: 'PENDING',
            attempts: 0,
            maxAttempts: MAX_RETRIES,
        });
        const saved = await this.backgroundJobRepo.save(job);
        if (delayMs && delayMs > 0) {
            setTimeout(() => this.processJob(saved), delayMs);
        }
        else {
            setImmediate(() => this.processJob(saved));
        }
        return saved;
    }
    async processJob(job) {
        job.status = 'ACTIVE';
        job.startedAt = new Date();
        await this.backgroundJobRepo.save(job);
        try {
            switch (job.name) {
                case exports.JobNames.PAYMENT_RECONCILIATION:
                    await this.processPaymentReconciliation(job);
                    break;
                case exports.JobNames.SESSION_ACTIVATION:
                    await this.processSessionActivation(job);
                    break;
                case exports.JobNames.SESSION_DEACTIVATION:
                    await this.processSessionDeactivation(job);
                    break;
                case exports.JobNames.SESSION_CLEANUP:
                    await this.processSessionCleanup(job);
                    break;
                case exports.JobNames.ROUTER_HEALTH_CHECK:
                    await this.processRouterHealthCheck(job);
                    break;
                case exports.JobNames.SYSTEM_HEALTH_CHECK:
                    await this.processSystemHealthCheck(job);
                    break;
                default:
                    this.logger.warn(`Unknown job name: ${job.name}`);
            }
            job.status = 'COMPLETED';
            job.completedAt = new Date();
            await this.backgroundJobRepo.save(job);
        }
        catch (error) {
            this.logger.error(`Job ${job.jobId} (${job.name}) failed: ${error.message}`);
            job.attempts += 1;
            if (job.attempts < (job.maxAttempts || MAX_RETRIES)) {
                const delay = RETRY_DELAYS[Math.min(job.attempts - 1, RETRY_DELAYS.length - 1)];
                job.status = 'RETRYING';
                job.errorMessage = error.message;
                await this.backgroundJobRepo.save(job);
                setTimeout(() => this.processJob(job), delay);
            }
            else {
                job.status = 'FAILED';
                job.errorMessage = error.message;
                await this.backgroundJobRepo.save(job);
                const failedJob = this.failedJobRepo.create({
                    jobId: job.jobId,
                    queue: job.queue,
                    name: job.name,
                    data: job.data,
                    errorMessage: error.message,
                    errorStack: error.stack,
                });
                await this.failedJobRepo.save(failedJob);
            }
        }
    }
    async processPaymentReconciliation(job) {
        const data = job.data;
        if (!data?.paymentId)
            return;
        const payment = await this.paymentRepo.findOne({ where: { id: data.paymentId } });
        if (!payment)
            return;
        const PendingStatuses = ['PENDING', 'STK_SENT', 'PROCESSING'];
        if (!PendingStatuses.includes(payment.status)) {
            this.logger.log(`Payment ${data.paymentId} already finalized, skipping reconciliation`);
            return;
        }
        const timeoutMs = this.configService.getNumber('MPESA_STK_TIMEOUT', 30) * 1000 * 2;
        const elapsed = Date.now() - payment.createdAt.getTime();
        if (elapsed > timeoutMs) {
            payment.status = 'TIMED_OUT';
            payment.notes = 'Payment timed out - no callback received';
            await this.paymentRepo.save(payment);
            this.logger.log(`Payment ${data.paymentId} marked as TIMED_OUT`);
        }
    }
    async processSessionActivation(job) {
        const data = job.data;
        if (!data?.sessionId)
            return;
        const session = await this.wifiSessionRepo.findOne({ where: { id: data.sessionId } });
        if (!session)
            return;
        if (session.status !== 'PENDING_ACTIVATION') {
            return;
        }
        this.logger.log(`Session activation job for ${data.sessionId}`);
    }
    async processSessionDeactivation(job) {
        const data = job.data;
        if (!data?.sessionId)
            return;
        const session = await this.wifiSessionRepo.findOne({ where: { id: data.sessionId } });
        if (!session || session.status !== 'ACTIVE')
            return;
        session.status = 'DISCONNECTED';
        session.disconnectedAt = new Date();
        await this.wifiSessionRepo.save(session);
        this.logger.log(`Session ${data.sessionId} deactivated`);
    }
    async processSessionCleanup(job) {
        const now = new Date();
        const expiredSessions = await this.wifiSessionRepo.find({
            where: { status: 'ACTIVE', expiryTime: (0, typeorm_2.LessThan)(now) },
        });
        for (const session of expiredSessions) {
            session.status = 'EXPIRED';
            session.disconnectedAt = now;
            await this.wifiSessionRepo.save(session);
        }
        if (expiredSessions.length > 0) {
            this.logger.log(`Cleaned up ${expiredSessions.length} expired sessions`);
        }
    }
    async processRouterHealthCheck(job) {
        const routers = await this.routerRepo.find({ where: { isActive: true } });
        const now = new Date();
        const offlineThreshold = 5 * 60 * 1000;
        for (const activeRouter of routers) {
            if (activeRouter.lastSeenAt) {
                const timeSinceLastSeen = now.getTime() - activeRouter.lastSeenAt.getTime();
                if (timeSinceLastSeen > offlineThreshold && activeRouter.isOnline) {
                    activeRouter.isOnline = false;
                    await this.routerRepo.save(activeRouter);
                    this.logger.warn(`Router ${activeRouter.name} marked offline`);
                }
            }
        }
    }
    async processSystemHealthCheck(job) {
        try {
            await this.backgroundJobRepo.count();
            this.logger.log('System health check: Database OK');
        }
        catch (error) {
            this.logger.error(`System health check failed: ${error.message}`);
        }
        const failedCount = await this.failedJobRepo.count();
        if (failedCount > 50) {
            this.logger.warn(`High number of failed jobs: ${failedCount}`);
        }
    }
    async addPaymentReconciliationJob(paymentId, delayMs) {
        await this.addJob(exports.QUEUES.PAYMENT, exports.JobNames.PAYMENT_RECONCILIATION, { paymentId }, delayMs || 120_000);
    }
    async addSessionActivationJob(sessionId) {
        await this.addJob(exports.QUEUES.SESSION, exports.JobNames.SESSION_ACTIVATION, { sessionId });
    }
    async addSessionDeactivationJob(sessionId, delayMs) {
        await this.addJob(exports.QUEUES.SESSION, exports.JobNames.SESSION_DEACTIVATION, { sessionId }, delayMs);
    }
    async addRouterHealthCheckJob(routerId) {
        await this.addJob(exports.QUEUES.ROUTER, exports.JobNames.ROUTER_HEALTH_CHECK, { routerId });
    }
    async getQueueStats() {
        const stats = {};
        for (const queue of Object.values(exports.QUEUES)) {
            const waiting = await this.backgroundJobRepo.count({ where: { queue, status: 'PENDING' } });
            const active = await this.backgroundJobRepo.count({ where: { queue, status: 'ACTIVE' } });
            const completed = await this.backgroundJobRepo.count({ where: { queue, status: 'COMPLETED' } });
            const failed = await this.backgroundJobRepo.count({ where: { queue, status: 'FAILED' } });
            stats[queue] = { waiting, active, completed, failed };
        }
        return stats;
    }
    async getFailedJobs() {
        return this.failedJobRepo.find({ order: { failedAt: 'DESC' }, take: 100 });
    }
    async retryFailedJob(failedJobId) {
        const failedJob = await this.failedJobRepo.findOne({ where: { id: failedJobId } });
        if (!failedJob)
            return;
        await this.failedJobRepo.remove(failedJob);
        await this.addJob(failedJob.queue, failedJob.name, failedJob.data);
    }
    async cleanOldJobs(daysOld = 30) {
        const cutoff = new Date(Date.now() - daysOld * 24 * 60 * 60 * 1000);
        await this.backgroundJobRepo.delete({ completedAt: (0, typeorm_2.LessThan)(cutoff) });
    }
    async cleanOldFailedJobs(daysOld = 60) {
        const cutoff = new Date(Date.now() - daysOld * 24 * 60 * 60 * 1000);
        await this.failedJobRepo.delete({ failedAt: (0, typeorm_2.LessThan)(cutoff) });
    }
    async handleSessionCleanup() {
        await this.addJob(exports.QUEUES.SESSION, exports.JobNames.SESSION_CLEANUP, {});
    }
    async handleRouterHealthCheck() {
        await this.addJob(exports.QUEUES.ROUTER, exports.JobNames.ROUTER_HEALTH_CHECK, {});
    }
    async handleDailyCleanup() {
        await this.cleanOldJobs();
        await this.cleanOldFailedJobs();
        this.logger.log('Daily job cleanup completed');
    }
};
exports.JobsService = JobsService;
__decorate([
    (0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_5_MINUTES),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], JobsService.prototype, "handleSessionCleanup", null);
__decorate([
    (0, schedule_1.Cron)('*/15 * * * *'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], JobsService.prototype, "handleRouterHealthCheck", null);
__decorate([
    (0, schedule_1.Cron)('0 0 * * *'),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], JobsService.prototype, "handleDailyCleanup", null);
exports.JobsService = JobsService = JobsService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(background_job_entity_1.BackgroundJob)),
    __param(1, (0, typeorm_1.InjectRepository)(failed_job_entity_1.FailedJob)),
    __param(2, (0, typeorm_1.InjectRepository)(wifi_session_entity_1.WifiSession)),
    __param(3, (0, typeorm_1.InjectRepository)(payment_entity_1.Payment)),
    __param(4, (0, typeorm_1.InjectRepository)(router_entity_1.Router)),
    __param(5, (0, typeorm_1.InjectRepository)(audit_log_entity_1.AuditLog)),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        config_service_1.ConfigService])
], JobsService);
exports.JobNames = {
    PAYMENT_RECONCILIATION: 'payment_reconciliation',
    SESSION_ACTIVATION: 'session_activation',
    SESSION_DEACTIVATION: 'session_deactivation',
    SESSION_CLEANUP: 'session_cleanup',
    ROUTER_HEALTH_CHECK: 'router_health_check',
    SYSTEM_HEALTH_CHECK: 'system_health_check',
    NOTIFICATION_SEND: 'notification_send',
    REPORT_GENERATE: 'report_generate',
};
