"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 SessionsService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionsService = 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 wifi_session_entity_1 = require("../database/entities/wifi-session.entity");
const customer_entity_1 = require("../database/entities/customer.entity");
const customer_device_entity_1 = require("../database/entities/customer-device.entity");
const package_entity_1 = require("../database/entities/package.entity");
const router_entity_1 = require("../database/entities/router.entity");
const payment_entity_1 = require("../database/entities/payment.entity");
const audit_log_entity_1 = require("../database/entities/audit-log.entity");
const router_service_1 = require("../router/router.service");
const SESSION_STATE_TRANSITIONS = {
    [wifi_session_entity_1.WifiSessionStatus.PENDING_ACTIVATION]: [
        wifi_session_entity_1.WifiSessionStatus.ACTIVE,
        wifi_session_entity_1.WifiSessionStatus.ACTIVATION_FAILED,
    ],
    [wifi_session_entity_1.WifiSessionStatus.ACTIVE]: [
        wifi_session_entity_1.WifiSessionStatus.PAUSED,
        wifi_session_entity_1.WifiSessionStatus.EXPIRED,
        wifi_session_entity_1.WifiSessionStatus.DATA_EXHAUSTED,
        wifi_session_entity_1.WifiSessionStatus.DISCONNECTED,
        wifi_session_entity_1.WifiSessionStatus.CANCELLED,
    ],
    [wifi_session_entity_1.WifiSessionStatus.PAUSED]: [
        wifi_session_entity_1.WifiSessionStatus.ACTIVE,
        wifi_session_entity_1.WifiSessionStatus.DISCONNECTED,
        wifi_session_entity_1.WifiSessionStatus.CANCELLED,
    ],
    [wifi_session_entity_1.WifiSessionStatus.ACTIVATION_FAILED]: [
        wifi_session_entity_1.WifiSessionStatus.PENDING_ACTIVATION,
        wifi_session_entity_1.WifiSessionStatus.CANCELLED,
    ],
    [wifi_session_entity_1.WifiSessionStatus.EXPIRED]: [],
    [wifi_session_entity_1.WifiSessionStatus.DATA_EXHAUSTED]: [],
    [wifi_session_entity_1.WifiSessionStatus.DISCONNECTED]: [],
    [wifi_session_entity_1.WifiSessionStatus.CANCELLED]: [],
};
let SessionsService = SessionsService_1 = class SessionsService {
    constructor(wifiSessionRepo, customerRepo, customerDeviceRepo, packageRepo, routerRepo, paymentRepo, auditLogRepo, routerService) {
        this.wifiSessionRepo = wifiSessionRepo;
        this.customerRepo = customerRepo;
        this.customerDeviceRepo = customerDeviceRepo;
        this.packageRepo = packageRepo;
        this.routerRepo = routerRepo;
        this.paymentRepo = paymentRepo;
        this.auditLogRepo = auditLogRepo;
        this.routerService = routerService;
        this.logger = new common_1.Logger(SessionsService_1.name);
    }
    async activateSession(payment, customer) {
        const pkg = await this.packageRepo.findOne({ where: { id: payment.packageId } });
        if (!pkg) {
            throw new common_1.NotFoundException(`Package ${payment.packageId} not found`);
        }
        const router = await this.routerRepo.findOne({ where: { id: payment.routerId || '' } });
        if (!router) {
            throw new common_1.NotFoundException(`Router ${payment.routerId} not found`);
        }
        let cust;
        if (customer) {
            cust = customer;
        }
        else if (payment.phoneNumber) {
            cust = await this.findOrCreateCustomer(payment.phoneNumber, payment.businessId);
        }
        else if (payment.customerId) {
            const existing = await this.customerRepo.findOne({
                where: { id: payment.customerId, businessId: payment.businessId },
            });
            if (!existing) {
                throw new common_1.NotFoundException(`Customer ${payment.customerId} not found`);
            }
            cust = existing;
        }
        else {
            throw new common_1.BadRequestException('No customer or phone number provided for activation');
        }
        let device = null;
        const deviceMac = payment.callbackPayload?.macAddress;
        const deviceIp = payment.callbackPayload?.ipAddress;
        if (deviceMac) {
            let existingDevice = await this.customerDeviceRepo.findOne({
                where: { customerId: cust.id, macAddress: deviceMac },
            });
            if (!existingDevice) {
                existingDevice = this.customerDeviceRepo.create({
                    customerId: cust.id,
                    macAddress: deviceMac,
                    ipAddress: deviceIp || undefined,
                    deviceType: payment.callbackPayload?.deviceType || undefined,
                    browserInfo: payment.callbackPayload?.userAgent || undefined,
                    lastSeenAt: new Date(),
                });
                existingDevice = await this.customerDeviceRepo.save(existingDevice);
            }
            else {
                existingDevice.lastSeenAt = new Date();
                if (deviceIp)
                    existingDevice.ipAddress = deviceIp;
                await this.customerDeviceRepo.save(existingDevice);
            }
            device = existingDevice;
        }
        cust.lastConnectionAt = new Date();
        await this.customerRepo.save(cust);
        const session = this.wifiSessionRepo.create({
            customerId: cust.id,
            packageId: pkg.id,
            routerId: router.id,
            businessId: payment.businessId,
            deviceId: device?.id || undefined,
            macAddress: device?.macAddress || undefined,
            ipAddress: device?.ipAddress || undefined,
            status: wifi_session_entity_1.WifiSessionStatus.PENDING_ACTIVATION,
        });
        const savedSession = await this.wifiSessionRepo.save(session);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId: payment.businessId,
            action: 'SESSION_CREATED',
            entityType: 'WifiSession',
            entityId: savedSession.id,
            newValue: {
                customerId: cust.id,
                packageId: pkg.id,
                routerId: router.id,
                status: wifi_session_entity_1.WifiSessionStatus.PENDING_ACTIVATION,
            },
        }));
        try {
            const activated = await this.routerService.activateSession(savedSession);
            await this.auditLogRepo.save(this.auditLogRepo.create({
                businessId: payment.businessId,
                action: 'SESSION_ACTIVATED',
                entityType: 'WifiSession',
                entityId: activated.id,
                newValue: {
                    status: wifi_session_entity_1.WifiSessionStatus.ACTIVE,
                    startTime: activated.startTime,
                    expiryTime: activated.expiryTime,
                },
            }));
            this.logger.log(`Session ${activated.id} activated for customer ${cust.id}`);
            return activated;
        }
        catch (error) {
            const failedSession = await this.wifiSessionRepo.findOne({
                where: { id: savedSession.id },
            });
            if (!failedSession) {
                throw error;
            }
            await this.createRetryJob(failedSession.id, payment.businessId, error.message);
            await this.auditLogRepo.save(this.auditLogRepo.create({
                businessId: payment.businessId,
                action: 'SESSION_ACTIVATION_FAILED',
                entityType: 'WifiSession',
                entityId: failedSession.id,
                newValue: {
                    error: error.message,
                    attempts: failedSession.activationAttempts,
                },
            }));
            this.logger.error(`Session ${failedSession.id} activation failed: ${error.message}`);
            return failedSession;
        }
    }
    async deactivateSession(sessionId, reason) {
        const session = await this.wifiSessionRepo.findOne({
            where: { id: sessionId },
            relations: ['customer', 'package', 'router'],
        });
        if (!session) {
            throw new common_1.NotFoundException(`Session ${sessionId} not found`);
        }
        if (session.status === wifi_session_entity_1.WifiSessionStatus.DISCONNECTED ||
            session.status === wifi_session_entity_1.WifiSessionStatus.EXPIRED ||
            session.status === wifi_session_entity_1.WifiSessionStatus.CANCELLED) {
            this.logger.warn(`Session ${sessionId} already in terminal state: ${session.status}`);
            return session;
        }
        try {
            await this.routerService.deactivateSession(sessionId);
        }
        catch (error) {
            this.logger.error(`Router deactivation failed for session ${sessionId}: ${error.message}`);
            session.status = wifi_session_entity_1.WifiSessionStatus.DISCONNECTED;
            session.disconnectedAt = new Date();
            await this.wifiSessionRepo.save(session);
        }
        session.status = wifi_session_entity_1.WifiSessionStatus.DISCONNECTED;
        session.disconnectedAt = new Date();
        if (!session.endTime) {
            session.endTime = new Date();
        }
        await this.wifiSessionRepo.save(session);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId: session.businessId,
            action: 'SESSION_DEACTIVATED',
            entityType: 'WifiSession',
            entityId: session.id,
            newValue: { reason, status: wifi_session_entity_1.WifiSessionStatus.DISCONNECTED },
        }));
        this.logger.log(`Session ${sessionId} deactivated. Reason: ${reason}`);
        return session;
    }
    async disconnectExpiredSessions() {
        const now = new Date();
        const sessions = await this.wifiSessionRepo.find({
            where: { status: wifi_session_entity_1.WifiSessionStatus.ACTIVE },
        });
        const expired = sessions.filter((s) => s.expiryTime && s.expiryTime <= now);
        let disconnected = 0;
        for (const session of expired) {
            try {
                await this.routerService.deactivateSession(session.id);
                session.status = wifi_session_entity_1.WifiSessionStatus.EXPIRED;
                session.disconnectedAt = now;
                session.endTime = now;
                await this.wifiSessionRepo.save(session);
                disconnected++;
                await this.auditLogRepo.save(this.auditLogRepo.create({
                    businessId: session.businessId,
                    action: 'SESSION_EXPIRED',
                    entityType: 'WifiSession',
                    entityId: session.id,
                    newValue: { status: wifi_session_entity_1.WifiSessionStatus.EXPIRED },
                }));
            }
            catch (error) {
                this.logger.error(`Failed to disconnect expired session ${session.id}: ${error.message}`);
            }
        }
        if (disconnected > 0) {
            this.logger.log(`Disconnected ${disconnected} expired sessions`);
        }
        return disconnected;
    }
    async disconnectDataExhausted() {
        const sessions = await this.wifiSessionRepo.find({
            where: { status: wifi_session_entity_1.WifiSessionStatus.ACTIVE },
        });
        let disconnected = 0;
        for (const session of sessions) {
            const pkg = await this.packageRepo.findOne({ where: { id: session.packageId } });
            if (pkg?.dataAllowanceMb &&
                pkg.dataAllowanceMb > 0 &&
                Number(session.dataUsedMb) >= Number(pkg.dataAllowanceMb)) {
                try {
                    await this.routerService.deactivateSession(session.id);
                    session.status = wifi_session_entity_1.WifiSessionStatus.DATA_EXHAUSTED;
                    session.disconnectedAt = new Date();
                    session.endTime = new Date();
                    await this.wifiSessionRepo.save(session);
                    disconnected++;
                    await this.auditLogRepo.save(this.auditLogRepo.create({
                        businessId: session.businessId,
                        action: 'SESSION_DATA_EXHAUSTED',
                        entityType: 'WifiSession',
                        entityId: session.id,
                        newValue: {
                            status: wifi_session_entity_1.WifiSessionStatus.DATA_EXHAUSTED,
                            dataUsedMb: Number(session.dataUsedMb),
                        },
                    }));
                }
                catch (error) {
                    this.logger.error(`Failed to disconnect data-exhausted session ${session.id}: ${error.message}`);
                }
            }
        }
        if (disconnected > 0) {
            this.logger.log(`Disconnected ${disconnected} data-exhausted sessions`);
        }
        return disconnected;
    }
    async updateDataUsage(sessionId, bytesUsed) {
        const session = await this.wifiSessionRepo.findOne({ where: { id: sessionId } });
        if (!session) {
            throw new common_1.NotFoundException(`Session ${sessionId} not found`);
        }
        const mbUsed = bytesUsed / (1024 * 1024);
        session.dataUsedMb = Number(session.dataUsedMb) + mbUsed;
        const pkg = await this.packageRepo.findOne({ where: { id: session.packageId } });
        if (pkg?.dataAllowanceMb && Number(pkg.dataAllowanceMb) > 0) {
            session.remainingDataMb =
                Math.max(0, Number(pkg.dataAllowanceMb) - Number(session.dataUsedMb));
        }
        return this.wifiSessionRepo.save(session);
    }
    async extendSession(sessionId, additionalMinutes) {
        const session = await this.wifiSessionRepo.findOne({ where: { id: sessionId } });
        if (!session) {
            throw new common_1.NotFoundException(`Session ${sessionId} not found`);
        }
        if (session.status !== wifi_session_entity_1.WifiSessionStatus.ACTIVE) {
            throw new common_1.BadRequestException(`Cannot extend session with status ${session.status}`);
        }
        if (session.expiryTime) {
            session.expiryTime = new Date(session.expiryTime.getTime() + additionalMinutes * 60 * 1000);
        }
        else {
            session.expiryTime = new Date(Date.now() + additionalMinutes * 60 * 1000);
        }
        if (session.endTime) {
            session.endTime = new Date(session.endTime.getTime() + additionalMinutes * 60 * 1000);
        }
        await this.wifiSessionRepo.save(session);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId: session.businessId,
            action: 'SESSION_EXTENDED',
            entityType: 'WifiSession',
            entityId: session.id,
            newValue: {
                additionalMinutes,
                newExpiryTime: session.expiryTime,
            },
        }));
        this.logger.log(`Session ${sessionId} extended by ${additionalMinutes} minutes`);
        return session;
    }
    async updateSessionStatus(sessionId, status) {
        const session = await this.wifiSessionRepo.findOne({ where: { id: sessionId } });
        if (!session) {
            throw new common_1.NotFoundException(`Session ${sessionId} not found`);
        }
        this.validateStateTransition(session.status, status);
        session.status = status;
        if (status === wifi_session_entity_1.WifiSessionStatus.DISCONNECTED ||
            status === wifi_session_entity_1.WifiSessionStatus.EXPIRED ||
            status === wifi_session_entity_1.WifiSessionStatus.DATA_EXHAUSTED ||
            status === wifi_session_entity_1.WifiSessionStatus.CANCELLED) {
            session.disconnectedAt = new Date();
            if (!session.endTime) {
                session.endTime = new Date();
            }
        }
        const updated = await this.wifiSessionRepo.save(session);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId: session.businessId,
            action: 'SESSION_STATUS_CHANGED',
            entityType: 'WifiSession',
            entityId: session.id,
            newValue: { status },
        }));
        this.logger.log(`Session ${sessionId} status updated to ${status}`);
        return updated;
    }
    async getSessionById(id, businessId) {
        const session = await this.wifiSessionRepo.findOne({
            where: { id, businessId },
            relations: ['customer', 'package', 'router', 'device'],
        });
        if (!session) {
            throw new common_1.NotFoundException(`Session ${id} not found`);
        }
        return session;
    }
    async getActiveSessions(businessId) {
        return this.wifiSessionRepo.find({
            where: {
                businessId,
                status: (0, typeorm_2.In)([wifi_session_entity_1.WifiSessionStatus.ACTIVE, wifi_session_entity_1.WifiSessionStatus.PAUSED]),
            },
            relations: ['customer', 'package', 'router', 'device'],
            order: { startTime: 'DESC' },
        });
    }
    async getCustomerSessions(customerId) {
        return this.wifiSessionRepo.find({
            where: { customerId },
            relations: ['package', 'router', 'device'],
            order: { createdAt: 'DESC' },
        });
    }
    async getActiveSessionsCount(businessId) {
        return this.wifiSessionRepo.count({
            where: {
                businessId,
                status: (0, typeorm_2.In)([wifi_session_entity_1.WifiSessionStatus.ACTIVE, wifi_session_entity_1.WifiSessionStatus.PAUSED]),
            },
        });
    }
    async getSessionStats(businessId) {
        const todayStart = new Date();
        todayStart.setHours(0, 0, 0, 0);
        const activeCount = await this.wifiSessionRepo.count({
            where: {
                businessId,
                status: (0, typeorm_2.In)([wifi_session_entity_1.WifiSessionStatus.ACTIVE, wifi_session_entity_1.WifiSessionStatus.PAUSED]),
            },
        });
        const expiredToday = await this.wifiSessionRepo.count({
            where: {
                businessId,
                status: wifi_session_entity_1.WifiSessionStatus.EXPIRED,
                disconnectedAt: (0, typeorm_2.MoreThanOrEqual)(todayStart),
            },
        });
        const allSessions = await this.wifiSessionRepo.find({
            where: { businessId, startTime: (0, typeorm_2.MoreThanOrEqual)(todayStart) },
        });
        const totalDataUsed = allSessions.reduce((sum, s) => sum + Number(s.dataUsedMb), 0);
        let averageDuration = 0;
        const sessionsWithDuration = allSessions.filter((s) => s.startTime && s.endTime);
        if (sessionsWithDuration.length > 0) {
            const totalMinutes = sessionsWithDuration.reduce((sum, s) => {
                const dur = (s.endTime.getTime() - s.startTime.getTime()) / (1000 * 60);
                return sum + dur;
            }, 0);
            averageDuration = totalMinutes / sessionsWithDuration.length;
        }
        return { activeCount, expiredToday, totalDataUsed, averageDuration };
    }
    async cleanupExpiredSessions(businessId) {
        this.logger.debug('Running session cleanup...');
        const expiredDisconnected = await this.disconnectExpiredSessions();
        const dataExhausted = await this.disconnectDataExhausted();
        this.logger.log(`Session cleanup complete: ${expiredDisconnected} expired, ${dataExhausted} data exhausted`);
        return { expiredDisconnected, dataExhausted };
    }
    async retryFailedActivation(sessionId) {
        const session = await this.wifiSessionRepo.findOne({
            where: { id: sessionId },
            relations: ['customer', 'package', 'router'],
        });
        if (!session) {
            throw new common_1.NotFoundException(`Session ${sessionId} not found`);
        }
        if (session.status !== wifi_session_entity_1.WifiSessionStatus.ACTIVATION_FAILED) {
            throw new common_1.BadRequestException(`Cannot retry activation for session with status ${session.status}`);
        }
        session.status = wifi_session_entity_1.WifiSessionStatus.PENDING_ACTIVATION;
        session.activationAttempts = (session.activationAttempts || 0) + 1;
        session.lastActivationError = null;
        await this.wifiSessionRepo.save(session);
        try {
            const activated = await this.routerService.activateSession(session);
            await this.auditLogRepo.save(this.auditLogRepo.create({
                businessId: session.businessId,
                action: 'SESSION_ACTIVATION_RETRY_SUCCESS',
                entityType: 'WifiSession',
                entityId: session.id,
                newValue: { status: wifi_session_entity_1.WifiSessionStatus.ACTIVE },
            }));
            this.logger.log(`Session ${sessionId} activation retry succeeded`);
            return activated;
        }
        catch (error) {
            const failedSession = await this.wifiSessionRepo.findOne({
                where: { id: sessionId },
            });
            if (!failedSession) {
                throw error;
            }
            await this.auditLogRepo.save(this.auditLogRepo.create({
                businessId: failedSession.businessId,
                action: 'SESSION_ACTIVATION_RETRY_FAILED',
                entityType: 'WifiSession',
                entityId: failedSession.id,
                newValue: {
                    error: error.message,
                    attempts: failedSession.activationAttempts,
                },
            }));
            this.logger.error(`Session ${sessionId} activation retry failed: ${error.message}`);
            throw error;
        }
    }
    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 for phone ${phoneNumber} in business ${businessId}`);
        }
        return customer;
    }
    async createRetryJob(sessionId, businessId, errorMessage) {
        this.logger.log(`Retry job scheduled for session ${sessionId}. Error: ${errorMessage}`);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId,
            action: 'RETRY_JOB_CREATED',
            entityType: 'WifiSession',
            entityId: sessionId,
            newValue: {
                jobType: 'SESSION_ACTIVATION_RETRY',
                lastError: errorMessage,
            },
        }));
    }
    validateStateTransition(currentStatus, targetStatus) {
        const allowedTransitions = SESSION_STATE_TRANSITIONS[currentStatus];
        if (!allowedTransitions) {
            throw new common_1.BadRequestException(`No transition rules defined for status: ${currentStatus}`);
        }
        if (!allowedTransitions.includes(targetStatus)) {
            throw new common_1.BadRequestException(`Invalid state transition from ${currentStatus} to ${targetStatus}. ` +
                `Allowed transitions: ${allowedTransitions.join(', ')}`);
        }
    }
};
exports.SessionsService = SessionsService;
__decorate([
    (0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_5_MINUTES),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Promise)
], SessionsService.prototype, "cleanupExpiredSessions", null);
exports.SessionsService = SessionsService = SessionsService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(wifi_session_entity_1.WifiSession)),
    __param(1, (0, typeorm_1.InjectRepository)(customer_entity_1.Customer)),
    __param(2, (0, typeorm_1.InjectRepository)(customer_device_entity_1.CustomerDevice)),
    __param(3, (0, typeorm_1.InjectRepository)(package_entity_1.Package)),
    __param(4, (0, typeorm_1.InjectRepository)(router_entity_1.Router)),
    __param(5, (0, typeorm_1.InjectRepository)(payment_entity_1.Payment)),
    __param(6, (0, typeorm_1.InjectRepository)(audit_log_entity_1.AuditLog)),
    __param(7, (0, common_1.Inject)((0, common_1.forwardRef)(() => router_service_1.RouterService))),
    __metadata("design:paramtypes", [typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        typeorm_2.Repository,
        router_service_1.RouterService])
], SessionsService);
