"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 RouterService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RouterService = void 0;
const common_1 = require("@nestjs/common");
const crypto = require("crypto");
const typeorm_1 = require("@nestjs/typeorm");
const typeorm_2 = require("typeorm");
const schedule_1 = require("@nestjs/schedule");
const config_service_1 = require("../common/config/config.service");
const encryption_util_1 = require("../common/utils/encryption.util");
const router_entity_1 = require("../database/entities/router.entity");
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 radius_account_entity_1 = require("../database/entities/radius-account.entity");
const location_entity_1 = require("../database/entities/location.entity");
const audit_log_entity_1 = require("../database/entities/audit-log.entity");
const package_entity_1 = require("../database/entities/package.entity");
const state_machines_1 = require("../common/constants/state-machines");
const provider_factory_1 = require("./providers/provider.factory");
let RouterService = RouterService_1 = class RouterService {
    constructor(routerRepo, wifiSessionRepo, customerRepo, customerDeviceRepo, radiusAccountRepo, locationRepo, auditLogRepo, packageRepo, providerFactory, configService) {
        this.routerRepo = routerRepo;
        this.wifiSessionRepo = wifiSessionRepo;
        this.customerRepo = customerRepo;
        this.customerDeviceRepo = customerDeviceRepo;
        this.radiusAccountRepo = radiusAccountRepo;
        this.locationRepo = locationRepo;
        this.auditLogRepo = auditLogRepo;
        this.packageRepo = packageRepo;
        this.providerFactory = providerFactory;
        this.configService = configService;
        this.logger = new common_1.Logger(RouterService_1.name);
    }
    async testConnection(routerId) {
        const router = await this.routerRepo.findOne({ where: { id: routerId } });
        if (!router) {
            throw new common_1.NotFoundException(`Router ${routerId} not found`);
        }
        const provider = this.providerFactory.getProvider(router.platform);
        const result = await provider.testConnection(router);
        router.isOnline = result.success;
        if (result.success) {
            router.lastSeenAt = new Date();
        }
        await this.routerRepo.save(router);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId: router.businessId,
            action: 'ROUTER_CONNECTION_TEST',
            entityType: 'Router',
            entityId: router.id,
            newValue: {
                success: result.success,
                message: result.message,
            },
        }));
        return result;
    }
    async activateSession(session) {
        const [router, pkg, customer, device] = await Promise.all([
            this.routerRepo.findOne({ where: { id: session.routerId } }),
            this.packageRepo.findOne({ where: { id: session.packageId } }),
            this.customerRepo.findOne({ where: { id: session.customerId } }),
            session.deviceId
                ? this.customerDeviceRepo.findOne({ where: { id: session.deviceId } })
                : Promise.resolve(null),
        ]);
        if (!router) {
            throw new common_1.NotFoundException(`Router ${session.routerId} not found`);
        }
        if (!pkg) {
            throw new common_1.NotFoundException(`Package ${session.packageId} not found`);
        }
        if (!customer) {
            throw new common_1.NotFoundException(`Customer ${session.customerId} not found`);
        }
        const provider = this.providerFactory.getProvider(router.platform);
        const username = `${customer.phoneNumber}-${Math.random().toString(36).slice(2, 8)}`;
        const password = this.generatePassword();
        const expiryTime = new Date();
        if (pkg.durationMinutes) {
            expiryTime.setMinutes(expiryTime.getMinutes() + pkg.durationMinutes);
        }
        else {
            expiryTime.setHours(expiryTime.getHours() + 1);
        }
        try {
            const result = await provider.createHotspotUser(router, {
                username,
                password,
                macAddress: device?.macAddress || session.macAddress || undefined,
                ipAddress: device?.ipAddress || undefined,
                uploadKbps: pkg.uploadSpeedKbps || undefined,
                downloadKbps: pkg.downloadSpeedKbps || undefined,
                uptimeLimit: pkg.durationMinutes ? pkg.durationMinutes * 60 : undefined,
                dataLimitMb: pkg.dataAllowanceMb
                    ? Number(pkg.dataAllowanceMb)
                    : undefined,
                profile: pkg.routerProfile || undefined,
                comment: `HBS Session: ${session.id}`,
            });
            session.routerSessionId = result.sessionId;
            session.status = wifi_session_entity_1.WifiSessionStatus.ACTIVE;
            session.startTime = new Date();
            session.expiryTime = expiryTime;
            session.sessionKey = password;
            session.activationAttempts = (session.activationAttempts || 0) + 1;
            if (pkg.dataAllowanceMb) {
                session.remainingDataMb = Number(pkg.dataAllowanceMb);
            }
            await this.wifiSessionRepo.save(session);
            if (router.platform === state_machines_1.ROUTER_PLATFORMS.RADIUS ||
                router.platform === state_machines_1.ROUTER_PLATFORMS.FREERADIUS) {
                const radiusAccount = this.radiusAccountRepo.create({
                    businessId: session.businessId,
                    customerId: session.customerId,
                    username,
                    password,
                    callingStationId: device?.macAddress || undefined,
                    framedIpAddress: device?.ipAddress || undefined,
                    nasIpAddress: router.host,
                    sessionTimeout: pkg.durationMinutes ? pkg.durationMinutes * 60 : undefined,
                    idleTimeout: 600,
                });
                await this.radiusAccountRepo.save(radiusAccount);
            }
            await this.auditLogRepo.save(this.auditLogRepo.create({
                businessId: session.businessId,
                action: 'SESSION_ACTIVATED',
                entityType: 'WifiSession',
                entityId: session.id,
                newValue: {
                    routerId: router.id,
                    username,
                    packageId: pkg.id,
                    status: wifi_session_entity_1.WifiSessionStatus.ACTIVE,
                },
            }));
            this.logger.log(`Session ${session.id} activated on router ${router.name} (${router.platform})`);
            return session;
        }
        catch (error) {
            session.activationAttempts = (session.activationAttempts || 0) + 1;
            session.lastActivationError = error.message;
            session.status = wifi_session_entity_1.WifiSessionStatus.ACTIVATION_FAILED;
            await this.wifiSessionRepo.save(session);
            await this.auditLogRepo.save(this.auditLogRepo.create({
                businessId: session.businessId,
                action: 'SESSION_ACTIVATION_FAILED',
                entityType: 'WifiSession',
                entityId: session.id,
                newValue: {
                    error: error.message,
                    attempts: session.activationAttempts,
                },
            }));
            this.logger.error(`Failed to activate session ${session.id} on router ${router.id}: ${error.message}`);
            throw new common_1.BadRequestException(`Failed to activate session: ${error.message}`);
        }
    }
    async deactivateSession(wifiSessionId) {
        const session = await this.wifiSessionRepo.findOne({
            where: { id: wifiSessionId },
            relations: ['router'],
        });
        if (!session) {
            throw new common_1.NotFoundException(`WiFi session ${wifiSessionId} 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 ${wifiSessionId} is already ${session.status}`);
            return;
        }
        const router = await this.routerRepo.findOne({
            where: { id: session.routerId },
        });
        if (!router) {
            session.status = wifi_session_entity_1.WifiSessionStatus.DISCONNECTED;
            session.disconnectedAt = new Date();
            await this.wifiSessionRepo.save(session);
            return;
        }
        const provider = this.providerFactory.getProvider(router.platform);
        try {
            if (session.routerSessionId) {
                await provider.disconnectUser(router, session.routerSessionId);
            }
            const username = this.extractUsernameFromSession(session);
            if (username) {
                try {
                    await provider.removeHotspotUser(router, username);
                }
                catch (err) {
                    this.logger.warn(`Could not remove hotspot user for session ${wifiSessionId}: ${err.message}`);
                }
            }
            session.status = wifi_session_entity_1.WifiSessionStatus.DISCONNECTED;
            session.disconnectedAt = 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: {
                    status: wifi_session_entity_1.WifiSessionStatus.DISCONNECTED,
                    duration: session.startTime
                        ? Math.round((new Date().getTime() - session.startTime.getTime()) / 1000)
                        : 0,
                },
            }));
            this.logger.log(`Session ${wifiSessionId} deactivated`);
        }
        catch (error) {
            this.logger.error(`Failed to deactivate session ${wifiSessionId}: ${error.message}`);
            session.status = wifi_session_entity_1.WifiSessionStatus.DISCONNECTED;
            session.disconnectedAt = new Date();
            session.lastActivationError = `Deactivation error: ${error.message}`;
            await this.wifiSessionRepo.save(session);
        }
    }
    async checkRouterHealth(routerId) {
        const router = await this.routerRepo.findOne({ where: { id: routerId } });
        if (!router) {
            throw new common_1.NotFoundException(`Router ${routerId} not found`);
        }
        const provider = this.providerFactory.getProvider(router.platform);
        const status = await provider.getRouterStatus(router);
        router.isOnline = status.isOnline;
        if (status.isOnline) {
            router.lastSeenAt = new Date();
            router.cpuUsage = status.cpuUsage;
            router.memoryUsage = status.memoryUsage;
            router.uptime = status.uptime;
            router.activeSessions = status.activeSessions;
        }
        await this.routerRepo.save(router);
        return {
            isOnline: status.isOnline,
            cpuUsage: status.cpuUsage,
            memoryUsage: status.memoryUsage,
            uptime: status.uptime,
            activeSessions: status.activeSessions,
        };
    }
    async getRouterSessions(routerId) {
        const router = await this.routerRepo.findOne({ where: { id: routerId } });
        if (!router) {
            throw new common_1.NotFoundException(`Router ${routerId} not found`);
        }
        const provider = this.providerFactory.getProvider(router.platform);
        return provider.getActiveSessions(router);
    }
    async addRouter(dto, businessId) {
        const encryptionKey = this.configService.getEncryptionKey();
        const encryptedPassword = (0, encryption_util_1.encrypt)(dto.apiPassword, encryptionKey);
        const router = this.routerRepo.create({
            businessId,
            name: dto.name,
            platform: dto.platform,
            host: dto.host,
            apiPort: dto.apiPort ?? 8728,
            apiUsername: dto.apiUsername,
            apiPassword: encryptedPassword,
            locationId: dto.locationId || undefined,
            useSsl: dto.useSsl ?? false,
            isActive: true,
            isOnline: false,
        });
        const saved = await this.routerRepo.save(router);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId,
            action: 'ROUTER_CREATED',
            entityType: 'Router',
            entityId: saved.id,
            newValue: {
                name: saved.name,
                platform: saved.platform,
                host: saved.host,
            },
        }));
        this.logger.log(`Router ${saved.id} (${saved.name}) created for business ${businessId}`);
        return saved;
    }
    async updateRouter(routerId, dto) {
        const router = await this.routerRepo.findOne({ where: { id: routerId } });
        if (!router) {
            throw new common_1.NotFoundException(`Router ${routerId} not found`);
        }
        const encryptionKey = this.configService.getEncryptionKey();
        if (dto.name !== undefined)
            router.name = dto.name;
        if (dto.platform !== undefined)
            router.platform = dto.platform;
        if (dto.host !== undefined)
            router.host = dto.host;
        if (dto.apiPort !== undefined)
            router.apiPort = dto.apiPort;
        if (dto.apiUsername !== undefined)
            router.apiUsername = dto.apiUsername;
        if (dto.apiPassword !== undefined) {
            router.apiPassword = (0, encryption_util_1.encrypt)(dto.apiPassword, encryptionKey);
        }
        if (dto.locationId !== undefined) {
            router.locationId = dto.locationId || null;
        }
        if (dto.useSsl !== undefined)
            router.useSsl = dto.useSsl;
        if (dto.isActive !== undefined)
            router.isActive = dto.isActive;
        const saved = await this.routerRepo.save(router);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId: router.businessId,
            action: 'ROUTER_UPDATED',
            entityType: 'Router',
            entityId: router.id,
            newValue: dto,
        }));
        this.logger.log(`Router ${routerId} updated`);
        return saved;
    }
    async deleteRouter(routerId) {
        const router = await this.routerRepo.findOne({ where: { id: routerId } });
        if (!router) {
            throw new common_1.NotFoundException(`Router ${routerId} not found`);
        }
        router.isActive = false;
        await this.routerRepo.save(router);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId: router.businessId,
            action: 'ROUTER_DELETED',
            entityType: 'Router',
            entityId: router.id,
            previousValue: { name: router.name, host: router.host },
            newValue: { isActive: false },
        }));
        this.logger.log(`Router ${routerId} soft-deleted`);
    }
    async getBusinessRouters(businessId) {
        return this.routerRepo.find({
            where: { businessId },
            relations: ['location'],
            order: { createdAt: 'DESC' },
        });
    }
    async getRouterById(routerId) {
        const router = await this.routerRepo.findOne({
            where: { id: routerId },
            relations: ['location'],
        });
        if (!router) {
            throw new common_1.NotFoundException(`Router ${routerId} not found`);
        }
        return router;
    }
    async getRouterStatus(routerId) {
        const router = await this.routerRepo.findOne({ where: { id: routerId } });
        if (!router) {
            throw new common_1.NotFoundException(`Router ${routerId} not found`);
        }
        const provider = this.providerFactory.getProvider(router.platform);
        const status = await provider.getRouterStatus(router);
        router.isOnline = status.isOnline;
        if (status.isOnline) {
            router.lastSeenAt = new Date();
            router.cpuUsage = status.cpuUsage;
            router.memoryUsage = status.memoryUsage;
            router.uptime = status.uptime;
            router.activeSessions = status.activeSessions;
        }
        await this.routerRepo.save(router);
        return {
            router: {
                id: router.id,
                name: router.name,
                platform: router.platform,
                host: router.host,
                isActive: router.isActive,
                isOnline: router.isOnline,
                lastSeenAt: router.lastSeenAt,
            },
            status,
        };
    }
    async monitorAllRouters() {
        this.logger.debug('Starting router health monitoring...');
        const routers = await this.routerRepo.find({
            where: { isActive: true },
        });
        let checked = 0;
        let online = 0;
        for (const router of routers) {
            try {
                const provider = this.providerFactory.getProvider(router.platform);
                const status = await provider.getRouterStatus(router);
                router.isOnline = status.isOnline;
                if (status.isOnline) {
                    router.lastSeenAt = new Date();
                    router.cpuUsage = status.cpuUsage;
                    router.memoryUsage = status.memoryUsage;
                    router.uptime = status.uptime;
                    router.activeSessions = status.activeSessions;
                    online++;
                }
                await this.routerRepo.save(router);
                checked++;
            }
            catch (error) {
                this.logger.error(`Health check failed for router ${router.id}: ${error.message}`);
                router.isOnline = false;
                await this.routerRepo.save(router);
                checked++;
            }
        }
        this.logger.log(`Router health check complete: ${online}/${checked} routers online`);
    }
    async cleanupExpiredSessions() {
        this.logger.debug('Checking for expired sessions...');
        const expiredSessions = await this.wifiSessionRepo.find({
            where: [
                {
                    status: wifi_session_entity_1.WifiSessionStatus.ACTIVE,
                    expiryTime: (0, typeorm_2.In)([]),
                },
            ],
        });
        const now = new Date();
        const expired = expiredSessions.filter((s) => s.expiryTime && s.expiryTime <= now);
        let cleaned = 0;
        for (const session of expired) {
            try {
                await this.deactivateSession(session.id);
                session.status = wifi_session_entity_1.WifiSessionStatus.EXPIRED;
                await this.wifiSessionRepo.save(session);
                cleaned++;
            }
            catch (error) {
                this.logger.error(`Failed to clean up expired session ${session.id}: ${error.message}`);
            }
        }
        if (cleaned > 0) {
            this.logger.log(`Cleaned up ${cleaned} expired sessions`);
        }
        const dataExhaustedSessions = await this.wifiSessionRepo.find({
            where: {
                status: wifi_session_entity_1.WifiSessionStatus.ACTIVE,
            },
        });
        const exhausted = dataExhaustedSessions.filter((s) => s.remainingDataMb !== null && s.remainingDataMb !== undefined && s.remainingDataMb <= BigInt(0));
        for (const session of exhausted) {
            try {
                await this.deactivateSession(session.id);
                session.status = wifi_session_entity_1.WifiSessionStatus.DATA_EXHAUSTED;
                await this.wifiSessionRepo.save(session);
            }
            catch (error) {
                this.logger.error(`Failed to clean up data-exhausted session ${session.id}: ${error.message}`);
            }
        }
    }
    async reconnectRouter(routerId) {
        const router = await this.routerRepo.findOne({ where: { id: routerId } });
        if (!router) {
            throw new common_1.NotFoundException(`Router ${routerId} not found`);
        }
        const provider = this.providerFactory.getProvider(router.platform);
        const connected = await provider.connect(router);
        router.isOnline = connected;
        if (connected) {
            router.lastSeenAt = new Date();
        }
        await this.routerRepo.save(router);
        await this.auditLogRepo.save(this.auditLogRepo.create({
            businessId: router.businessId,
            action: connected ? 'ROUTER_RECONNECTED' : 'ROUTER_RECONNECT_FAILED',
            entityType: 'Router',
            entityId: router.id,
            newValue: { connected },
        }));
        return {
            success: connected,
            message: connected
                ? 'Successfully reconnected to router'
                : 'Failed to reconnect to router',
        };
    }
    generatePassword(length = 12) {
        const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
        let result = '';
        const bytes = new Uint8Array(length);
        crypto.randomFillSync(bytes);
        for (let i = 0; i < length; i++) {
            result += chars[bytes[i] % chars.length];
        }
        return result;
    }
    extractUsernameFromSession(session) {
        if (session.sessionKey) {
            const active = this.wifiSessionRepo.find({
                where: { routerSessionId: session.routerSessionId },
            });
            return null;
        }
        if (session.customer?.phoneNumber) {
            return session.customer.phoneNumber;
        }
        return null;
    }
};
exports.RouterService = RouterService;
__decorate([
    (0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_5_MINUTES),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], RouterService.prototype, "monitorAllRouters", null);
__decorate([
    (0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_MINUTE),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], RouterService.prototype, "cleanupExpiredSessions", null);
exports.RouterService = RouterService = RouterService_1 = __decorate([
    (0, common_1.Injectable)(),
    __param(0, (0, typeorm_1.InjectRepository)(router_entity_1.Router)),
    __param(1, (0, typeorm_1.InjectRepository)(wifi_session_entity_1.WifiSession)),
    __param(2, (0, typeorm_1.InjectRepository)(customer_entity_1.Customer)),
    __param(3, (0, typeorm_1.InjectRepository)(customer_device_entity_1.CustomerDevice)),
    __param(4, (0, typeorm_1.InjectRepository)(radius_account_entity_1.RadiusAccount)),
    __param(5, (0, typeorm_1.InjectRepository)(location_entity_1.Location)),
    __param(6, (0, typeorm_1.InjectRepository)(audit_log_entity_1.AuditLog)),
    __param(7, (0, typeorm_1.InjectRepository)(package_entity_1.Package)),
    __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,
        provider_factory_1.ProviderFactory,
        config_service_1.ConfigService])
], RouterService);
