"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateTotalCallCost = calculateTotalCallCost;
exports.aggregateTokenUsage = aggregateTokenUsage;
exports.logCallBilling = logCallBilling;
exports.createEmptyBillingUsage = createEmptyBillingUsage;
exports.getOrCreateBillingUsage = getOrCreateBillingUsage;
exports.trackOpenAIRealtimeUsage = trackOpenAIRealtimeUsage;
exports.trackOpenAIChatUsage = trackOpenAIChatUsage;
exports.trackOpenAITTSUsage = trackOpenAITTSUsage;
exports.trackTwilioCallDuration = trackTwilioCallDuration;
exports.logFinalCallBilling = logFinalCallBilling;
const billing_logger_1 = __importDefault(require("../../server/services/billing/billing-logger"));
const call_data_storage_1 = require("./call-data-storage");
const constants_1 = require("./constants");
const logger_1 = __importDefault(require("../../logger"));
const redisClient_1 = require("../../util/redisClient");
/**
 * Calculate cost for OpenAI Realtime API usage
 * Uses pricing constants from constants.ts
 */
function calculateOpenAIRealtimeCost(usage) {
    const inputTokens = usage.inputTokens || 0;
    const outputTokens = usage.outputTokens || 0;
    const inputAudioMinutes = (usage.inputAudioSeconds || 0) / 60;
    const outputAudioMinutes = (usage.outputAudioSeconds || 0) / 60;
    const inputTokenCost = (inputTokens / 1000) * constants_1.OPENAI_REALTIME_INPUT_TOKEN_COST_PER_1K;
    const outputTokenCost = (outputTokens / 1000) * constants_1.OPENAI_REALTIME_OUTPUT_TOKEN_COST_PER_1K;
    const inputAudioCost = inputAudioMinutes * constants_1.OPENAI_REALTIME_INPUT_AUDIO_COST_PER_MINUTE;
    const outputAudioCost = outputAudioMinutes * constants_1.OPENAI_REALTIME_OUTPUT_AUDIO_COST_PER_MINUTE;
    return inputTokenCost + outputTokenCost + inputAudioCost + outputAudioCost;
}
/**
 * Calculate cost for OpenAI Chat Completions
 * Uses pricing constants from constants.ts (gpt-4o-mini)
 */
function calculateOpenAIChatCost(usage) {
    const inputTokens = usage.inputTokens || 0;
    const outputTokens = usage.outputTokens || 0;
    const inputTokenCost = (inputTokens / 1000000) * constants_1.OPENAI_CHAT_INPUT_TOKEN_COST_PER_1M;
    const outputTokenCost = (outputTokens / 1000000) * constants_1.OPENAI_CHAT_OUTPUT_TOKEN_COST_PER_1M;
    return inputTokenCost + outputTokenCost;
}
/**
 * Calculate cost for OpenAI Text-to-Speech
 * Uses pricing constants from constants.ts (tts-1)
 */
function calculateOpenAITTSCost(usage) {
    const characters = usage.characters || 0;
    return (characters / 1000000) * constants_1.OPENAI_TTS_COST_PER_1M_CHARACTERS;
}
/**
 * Calculate cost for Twilio Call
 * Uses standard cost per minute from constants
 */
function calculateTwilioCallCost(usage) {
    const callDurationMinutes = (usage.callDurationSeconds || 0) / 60;
    return callDurationMinutes * constants_1.TWILIO_CALL_COST_PER_MINUTE;
}
/**
 * Calculate total cost for all services used in a call
 */
function calculateTotalCallCost(usage) {
    const realtimeCost = calculateOpenAIRealtimeCost(usage.openaiRealtime);
    const chatCost = calculateOpenAIChatCost(usage.openaiChat);
    const ttsCost = calculateOpenAITTSCost(usage.openaiTTS);
    const twilioCost = calculateTwilioCallCost(usage.twilio);
    return realtimeCost + chatCost + ttsCost + twilioCost;
}
/**
 * Aggregate token usage for billing log
 */
function aggregateTokenUsage(usage) {
    return {
        // OpenAI Realtime
        openai_realtime_input_tokens: usage.openaiRealtime.inputTokens,
        openai_realtime_output_tokens: usage.openaiRealtime.outputTokens,
        openai_realtime_input_audio_seconds: usage.openaiRealtime.inputAudioSeconds,
        openai_realtime_output_audio_seconds: usage.openaiRealtime.outputAudioSeconds,
        // OpenAI Chat
        openai_chat_input_tokens: usage.openaiChat.inputTokens,
        openai_chat_output_tokens: usage.openaiChat.outputTokens,
        openai_chat_requests: usage.openaiChat.requestCount,
        // OpenAI TTS
        openai_tts_characters: usage.openaiTTS.characters,
        openai_tts_requests: usage.openaiTTS.requestCount,
        // Twilio
        twilio_call_duration_seconds: usage.twilio.callDurationSeconds,
    };
}
/**
 * Log billing for a call
 * Creates 4 separate billing log entries: openai_realtime, openai_chat, openai_tts, and twilio
 */
async function logCallBilling(callSid, usage) {
    try {
        // Get call data to retrieve workspaceId and projectId
        const callData = await (0, call_data_storage_1.getCallData)(callSid);
        if (!callData || !callData.workspaceId || !callData.projectId) {
            console.warn(`[Billing] No callData or missing workspaceId/projectId for callSid: ${callSid}`);
            return;
        }
        // Calculate individual service costs
        const realtimeCost = calculateOpenAIRealtimeCost(usage.openaiRealtime);
        const chatCost = calculateOpenAIChatCost(usage.openaiChat);
        const ttsCost = calculateOpenAITTSCost(usage.openaiTTS);
        const twilioCost = calculateTwilioCallCost(usage.twilio);
        // Log OpenAI Realtime usage (tokenUsage only, no cost)
        if (usage.openaiRealtime.inputTokens > 0 || usage.openaiRealtime.outputTokens > 0 ||
            usage.openaiRealtime.inputAudioSeconds > 0 || usage.openaiRealtime.outputAudioSeconds > 0) {
            await billing_logger_1.default.logCallUsage({
                cost: 0,
                tokenUsage: {
                    prompt: usage.openaiRealtime.inputTokens,
                    completion: usage.openaiRealtime.outputTokens,
                    cached: 0,
                },
                workspaceId: callData.workspaceId,
                projectId: callData.projectId,
                typeId: callSid,
                metadata: {
                    callSid,
                    service: 'openai_realtime',
                    usage: usage.openaiRealtime,
                    inputAudioSeconds: usage.openaiRealtime.inputAudioSeconds,
                    outputAudioSeconds: usage.openaiRealtime.outputAudioSeconds,
                },
                provider: 'trusys',
                model: process.env.OPENAI_REALTIME_MODEL || 'gpt-4o-realtime-preview-2024-12-17',
            });
        }
        console.log('🧪 Realtime cost:', realtimeCost);
        console.log('🧪 Chat cost:', chatCost);
        console.log('🧪 TTS cost:', ttsCost);
        console.log('🧪 Twilio cost:', twilioCost);
        console.log('🧪 Total cost:', realtimeCost + chatCost + ttsCost + twilioCost);
        // Log OpenAI Chat usage (tokenUsage only, no cost)
        if (usage.openaiChat.inputTokens > 0 || usage.openaiChat.outputTokens > 0) {
            await billing_logger_1.default.logCallUsage({
                cost: 0,
                tokenUsage: {
                    prompt: usage.openaiChat.inputTokens,
                    completion: usage.openaiChat.outputTokens,
                    cached: 0,
                },
                workspaceId: callData.workspaceId,
                projectId: callData.projectId,
                typeId: callSid,
                metadata: {
                    callSid,
                    service: 'openai_chat',
                    usage: usage.openaiChat,
                    requestCount: usage.openaiChat.requestCount,
                },
                provider: 'trusys',
                model: 'gpt-4.1',
            });
        }
        // Log OpenAI TTS usage (tokenUsage only, no cost)
        if (usage.openaiTTS.characters > 0) {
            await billing_logger_1.default.logCallUsage({
                cost: 0,
                tokenUsage: {
                    prompt: 0,
                    completion: usage.openaiTTS.characters,
                    cached: 0,
                },
                workspaceId: callData.workspaceId,
                projectId: callData.projectId,
                typeId: callSid,
                metadata: {
                    callSid,
                    service: 'openai_tts',
                    usage: usage.openaiTTS,
                    characters: 0,
                    outputCharacters: usage.openaiTTS.characters,
                    requestCount: usage.openaiTTS.requestCount,
                },
                provider: 'trusys',
                model: 'default',
            });
        }
        // Log Twilio call usage (cost only, no tokenUsage)
        if (usage.twilio.callDurationSeconds > 0) {
            await billing_logger_1.default.logCallUsage({
                cost: twilioCost,
                tokenUsage: {
                    duration: usage.twilio.callDurationSeconds,
                },
                workspaceId: callData.workspaceId,
                projectId: callData.projectId,
                typeId: callSid,
                metadata: {
                    callSid,
                    service: 'twilio',
                    usage: usage.twilio,
                    callDurationSeconds: usage.twilio.callDurationSeconds,
                },
                provider: 'twilio',
                model: 'twilio-call',
            });
        }
        const totalCost = realtimeCost + chatCost + ttsCost + twilioCost;
        console.log(`[Billing] Logged separate billing entries for call ${callSid}: Total=$${totalCost.toFixed(4)}`);
        console.log(`[Billing] Breakdown: Realtime=$${realtimeCost.toFixed(4)}, Chat=$${chatCost.toFixed(4)}, TTS=$${ttsCost.toFixed(4)}, Twilio=$${twilioCost.toFixed(4)}`);
    }
    catch (error) {
        const err = error;
        console.error(`[Billing] Error logging call billing: ${err.message}`);
        // Don't throw - billing errors shouldn't break the call
    }
}
/**
 * Initialize empty billing usage tracker
 */
function createEmptyBillingUsage() {
    return {
        openaiRealtime: {
            inputTokens: 0,
            outputTokens: 0,
            inputAudioSeconds: 0,
            outputAudioSeconds: 0,
        },
        openaiChat: {
            inputTokens: 0,
            outputTokens: 0,
            requestCount: 0,
        },
        openaiTTS: {
            characters: 0,
            requestCount: 0,
        },
        twilio: {
            callDurationSeconds: 0,
        },
    };
}
const REDIS_BILLING_PREFIX = 'call_billing_usage:';
const REDIS_BILLED_PREFIX = 'call_billed:';
const DEFAULT_TTL = 60 * 60 * 2; // 2 hours in seconds
// In-memory fallback storage when Redis is unavailable
const inMemoryCallBillingUsage = new Map();
const inMemoryBilledCalls = new Set();
// Track if we've warned about fallback
let hasWarnedAboutBillingFallback = false;
const warnAboutBillingFallback = () => {
    if (!hasWarnedAboutBillingFallback) {
        logger_1.default.debug('Using in-memory call billing storage (Redis unavailable)');
        hasWarnedAboutBillingFallback = true;
    }
};
/**
 * Get or create billing usage tracker for a call
 */
async function getOrCreateBillingUsage(callSid) {
    try {
        if (!(0, redisClient_1.isRedisAvailable)()) {
            warnAboutBillingFallback();
            if (!inMemoryCallBillingUsage.has(callSid)) {
                inMemoryCallBillingUsage.set(callSid, createEmptyBillingUsage());
            }
            return inMemoryCallBillingUsage.get(callSid);
        }
        const redis = (0, redisClient_1.getRedisClient)();
        const key = `${REDIS_BILLING_PREFIX}${callSid}`;
        const data = await redis.get(key);
        if (!data) {
            // Check in-memory fallback if not found in Redis
            if (inMemoryCallBillingUsage.has(callSid)) {
                return inMemoryCallBillingUsage.get(callSid);
            }
            // Create new empty usage
            const emptyUsage = createEmptyBillingUsage();
            await redis.setex(key, DEFAULT_TTL, JSON.stringify(emptyUsage));
            return emptyUsage;
        }
        return JSON.parse(data);
    }
    catch (error) {
        logger_1.default.warn(`Failed to get billing usage ${callSid} from Redis, checking memory: ${error}`);
        warnAboutBillingFallback();
        if (!inMemoryCallBillingUsage.has(callSid)) {
            inMemoryCallBillingUsage.set(callSid, createEmptyBillingUsage());
        }
        return inMemoryCallBillingUsage.get(callSid);
    }
}
/**
 * Update billing usage tracker for a call
 */
async function updateBillingUsage(callSid, usage) {
    try {
        if (!(0, redisClient_1.isRedisAvailable)()) {
            warnAboutBillingFallback();
            inMemoryCallBillingUsage.set(callSid, usage);
            return;
        }
        const redis = (0, redisClient_1.getRedisClient)();
        const key = `${REDIS_BILLING_PREFIX}${callSid}`;
        // Get remaining TTL
        const ttl = await redis.ttl(key);
        const newTtl = ttl > 0 ? ttl : DEFAULT_TTL;
        await redis.setex(key, newTtl, JSON.stringify(usage));
    }
    catch (error) {
        logger_1.default.warn(`Failed to update billing usage ${callSid} in Redis, using memory: ${error}`);
        warnAboutBillingFallback();
        inMemoryCallBillingUsage.set(callSid, usage);
    }
}
/**
 * Check if a call has been billed
 */
async function isCallBilled(callSid) {
    try {
        if (!(0, redisClient_1.isRedisAvailable)()) {
            return inMemoryBilledCalls.has(callSid);
        }
        const redis = (0, redisClient_1.getRedisClient)();
        const key = `${REDIS_BILLED_PREFIX}${callSid}`;
        const exists = await redis.exists(key);
        return exists === 1 || inMemoryBilledCalls.has(callSid);
    }
    catch (error) {
        logger_1.default.warn(`Failed to check if call ${callSid} is billed in Redis, checking memory: ${error}`);
        return inMemoryBilledCalls.has(callSid);
    }
}
/**
 * Mark a call as billed
 */
async function markCallAsBilled(callSid) {
    try {
        if (!(0, redisClient_1.isRedisAvailable)()) {
            inMemoryBilledCalls.add(callSid);
            return;
        }
        const redis = (0, redisClient_1.getRedisClient)();
        const key = `${REDIS_BILLED_PREFIX}${callSid}`;
        await redis.setex(key, DEFAULT_TTL, '1');
        // Also add to in-memory for quick lookup
        inMemoryBilledCalls.add(callSid);
    }
    catch (error) {
        logger_1.default.warn(`Failed to mark call ${callSid} as billed in Redis, using memory: ${error}`);
        inMemoryBilledCalls.add(callSid);
    }
}
/**
 * Delete billing usage for a call
 */
async function deleteBillingUsage(callSid) {
    try {
        if (!(0, redisClient_1.isRedisAvailable)()) {
            inMemoryCallBillingUsage.delete(callSid);
            return;
        }
        const redis = (0, redisClient_1.getRedisClient)();
        const key = `${REDIS_BILLING_PREFIX}${callSid}`;
        await redis.del(key);
        // Also remove from in-memory fallback
        inMemoryCallBillingUsage.delete(callSid);
    }
    catch (error) {
        logger_1.default.warn(`Failed to delete billing usage ${callSid} from Redis, trying memory: ${error}`);
        inMemoryCallBillingUsage.delete(callSid);
    }
}
/**
 * Track OpenAI Realtime API usage
 */
async function trackOpenAIRealtimeUsage(callSid, usage) {
    const billing = await getOrCreateBillingUsage(callSid);
    billing.openaiRealtime.inputTokens += usage.input_tokens || 0;
    billing.openaiRealtime.outputTokens += usage.output_tokens || 0;
    billing.openaiRealtime.inputAudioSeconds += usage.input_audio_seconds || 0;
    billing.openaiRealtime.outputAudioSeconds += usage.output_audio_seconds || 0;
    await updateBillingUsage(callSid, billing);
}
/**
 * Track OpenAI Chat Completions usage
 */
async function trackOpenAIChatUsage(callSid, usage) {
    const billing = await getOrCreateBillingUsage(callSid);
    billing.openaiChat.inputTokens += usage.input_tokens || 0;
    billing.openaiChat.outputTokens += usage.output_tokens || 0;
    billing.openaiChat.requestCount += 1;
    await updateBillingUsage(callSid, billing);
}
/**
 * Track OpenAI Text-to-Speech usage
 */
async function trackOpenAITTSUsage(callSid, characters) {
    const billing = await getOrCreateBillingUsage(callSid);
    billing.openaiTTS.characters += characters;
    billing.openaiTTS.requestCount += 1;
    await updateBillingUsage(callSid, billing);
}
/**
 * Track Twilio call duration
 */
async function trackTwilioCallDuration(callSid, durationSeconds) {
    const billing = await getOrCreateBillingUsage(callSid);
    billing.twilio.callDurationSeconds = Math.max(billing.twilio.callDurationSeconds, durationSeconds);
    await updateBillingUsage(callSid, billing);
}
/**
 * Log final aggregated billing for a call and clean up
 * Prevents duplicate billing by checking if call has already been billed
 */
async function logFinalCallBilling(callSid) {
    // Prevent duplicate billing
    if (await isCallBilled(callSid)) {
        console.log(`[Billing] Call ${callSid} has already been billed, skipping duplicate log`);
        return;
    }
    const billing = await getOrCreateBillingUsage(callSid);
    // Check if billing has any actual usage
    const hasUsage = billing.openaiRealtime.inputTokens > 0 ||
        billing.openaiRealtime.outputTokens > 0 ||
        billing.openaiRealtime.inputAudioSeconds > 0 ||
        billing.openaiRealtime.outputAudioSeconds > 0 ||
        billing.openaiChat.inputTokens > 0 ||
        billing.openaiChat.outputTokens > 0 ||
        billing.openaiTTS.characters > 0 ||
        billing.twilio.callDurationSeconds > 0;
    if (!hasUsage) {
        console.warn(`[Billing] No billing data found for callSid: ${callSid}`);
        return;
    }
    try {
        await logCallBilling(callSid, billing);
        // Mark as billed and clean up after logging
        await markCallAsBilled(callSid);
        await deleteBillingUsage(callSid);
    }
    catch (error) {
        const err = error;
        console.error(`[Billing] Error logging final billing for call ${callSid}: ${err.message}`);
        // Don't throw - billing errors shouldn't break the call
    }
}
//# sourceMappingURL=call-billing.js.map