import { Injectable, Inject } from "@nestjs/common"; import { Logger } from "nestjs-pino"; import { SimDetailsService } from "./sim-details.service"; import { SimUsageService } from "./sim-usage.service"; import { SimTopUpService } from "./sim-topup.service"; import { SimPlanService } from "./sim-plan.service"; import { SimCancellationService } from "./sim-cancellation.service"; import { EsimManagementService } from "./esim-management.service"; import { SimValidationService } from "./sim-validation.service"; import { getErrorMessage } from "@bff/core/utils/error.util"; import type { SimDetails, SimUsage, SimTopUpHistory, } from "@bff/integrations/freebit/interfaces/freebit.types"; import type { SimTopUpRequest, SimPlanChangeRequest, SimCancelRequest, SimTopUpHistoryRequest, SimFeaturesUpdateRequest, } from "../types/sim-requests.types"; @Injectable() export class SimOrchestratorService { constructor( private readonly simDetails: SimDetailsService, private readonly simUsage: SimUsageService, private readonly simTopUp: SimTopUpService, private readonly simPlan: SimPlanService, private readonly simCancellation: SimCancellationService, private readonly esimManagement: EsimManagementService, private readonly simValidation: SimValidationService, @Inject(Logger) private readonly logger: Logger ) {} /** * Get SIM details for a subscription */ async getSimDetails(userId: string, subscriptionId: number): Promise { return this.simDetails.getSimDetails(userId, subscriptionId); } /** * Get SIM data usage for a subscription */ async getSimUsage(userId: string, subscriptionId: number): Promise { return this.simUsage.getSimUsage(userId, subscriptionId); } /** * Top up SIM data quota with payment processing */ async topUpSim(userId: string, subscriptionId: number, request: SimTopUpRequest): Promise { return this.simTopUp.topUpSim(userId, subscriptionId, request); } /** * Get SIM top-up history */ async getSimTopUpHistory( userId: string, subscriptionId: number, request: SimTopUpHistoryRequest ): Promise { return this.simUsage.getSimTopUpHistory(userId, subscriptionId, request); } /** * Change SIM plan */ async changeSimPlan( userId: string, subscriptionId: number, request: SimPlanChangeRequest ): Promise<{ ipv4?: string; ipv6?: string }> { return this.simPlan.changeSimPlan(userId, subscriptionId, request); } /** * Update SIM features (voicemail, call waiting, roaming, network type) */ async updateSimFeatures( userId: string, subscriptionId: number, request: SimFeaturesUpdateRequest ): Promise { return this.simPlan.updateSimFeatures(userId, subscriptionId, request); } /** * Cancel SIM service */ async cancelSim( userId: string, subscriptionId: number, request: SimCancelRequest = {} ): Promise { return this.simCancellation.cancelSim(userId, subscriptionId, request); } /** * Reissue eSIM profile */ async reissueEsimProfile(userId: string, subscriptionId: number, newEid?: string): Promise { return this.esimManagement.reissueEsimProfile(userId, subscriptionId, newEid); } /** * Get comprehensive SIM information (details + usage combined) */ async getSimInfo( userId: string, subscriptionId: number ): Promise<{ details: SimDetails; usage: SimUsage; }> { try { const [details, usage] = await Promise.all([ this.getSimDetails(userId, subscriptionId), this.getSimUsage(userId, subscriptionId), ]); // If Freebit doesn't return remaining quota, derive it from plan code (e.g., PASI_50G) // by subtracting measured usage (today + recentDays) from the plan cap. const normalizeNumber = (n: number) => (isFinite(n) && n > 0 ? n : 0); const usedMb = normalizeNumber(usage.todayUsageMb) + (usage.recentDaysUsage || []).reduce((sum, d) => sum + normalizeNumber(d.usageMb), 0); const planCapMatch = (details.planCode || "").match(/(\d+)\s*G/i); if ((details.remainingQuotaMb === 0 || details.remainingQuotaMb == null) && planCapMatch) { const capGb = parseInt(planCapMatch[1], 10); if (!isNaN(capGb) && capGb > 0) { const capMb = capGb * 1000; const remainingMb = Math.max(capMb - usedMb, 0); details.remainingQuotaMb = Math.round(remainingMb * 100) / 100; details.remainingQuotaKb = Math.round(details.remainingQuotaMb * 1000); } } return { details, usage }; } catch (error) { const sanitizedError = getErrorMessage(error); this.logger.error(`Failed to get comprehensive SIM info for subscription ${subscriptionId}`, { error: sanitizedError, userId, subscriptionId, }); throw error; } } /** * Debug method to check subscription data for SIM services */ async debugSimSubscription( userId: string, subscriptionId: number ): Promise> { return this.simValidation.debugSimSubscription(userId, subscriptionId); } /** * Debug method to query Freebit directly for any account's details * Bypasses subscription validation and queries Freebit directly */ async getSimDetailsDirectly(account: string): Promise { return this.simDetails.getSimDetailsDirectly(account); } }