/**
 * src/analytics/tracker.ts
 * تتبع الاستخدام والتكاليف — Usage/cost tracking
 */
import { supabase } from '../utils/supabase.js';
import { createChildLogger } from '../utils/logger.js';
const log = createChildLogger('analytics:tracker');
// ─── Track Message ──────────────────────────────────────────────────
export async function trackMessage(tenantId, direction, type, model, tokensIn, tokensOut, costUsd, responseTimeMs) {
    try {
        // Cost-only tracker: the actual `messages` row is inserted by the channel
        // route (web-chat, baileys, whatsapp handler) so we don't double-write.
        // Just bump the daily aggregate.
        void direction;
        void type;
        void model;
        void responseTimeMs;
        const today = new Date().toISOString().slice(0, 10);
        const { error: usageErr } = await supabase.rpc('upsert_daily_usage', {
            p_tenant_id: tenantId,
            p_date: today,
            p_messages: 1,
            p_tokens_in: tokensIn,
            p_tokens_out: tokensOut,
            p_cost: costUsd,
            p_new_customers: 0,
            p_active_customers: 0,
        });
        if (usageErr) {
            // Fallback: manual upsert into the `usage` table if the RPC is missing.
            log.debug({ error: usageErr.message }, 'RPC upsert_daily_usage failed, using fallback');
            await upsertDailyUsageFallback(tenantId, today, tokensIn, tokensOut, costUsd);
        }
    }
    catch (err) {
        log.error({ tenantId, err }, 'Failed to track message');
    }
}
async function upsertDailyUsageFallback(tenantId, date, tokensIn, tokensOut, costUsd) {
    const { data: existing } = await supabase
        .from('usage')
        .select('*')
        .eq('tenant_id', tenantId)
        .eq('date', date)
        .single();
    if (existing) {
        await supabase
            .from('usage')
            .update({
            messages_count: (existing.messages_count ?? 0) + 1,
            tokens_input: (existing.tokens_input ?? 0) + tokensIn,
            tokens_output: (existing.tokens_output ?? 0) + tokensOut,
            cost_usd: (existing.cost_usd ?? 0) + costUsd,
        })
            .eq('tenant_id', tenantId)
            .eq('date', date);
    }
    else {
        await supabase
            .from('usage')
            .insert({
            tenant_id: tenantId,
            date,
            messages_count: 1,
            tokens_input: tokensIn,
            tokens_output: tokensOut,
            cost_usd: costUsd,
        });
    }
}
// ─── Daily Usage ────────────────────────────────────────────────────
// `usage` is the canonical aggregate table; it tracks total messages_count
// (no in/out split) and tokens_input/tokens_output. The DailyUsage type
// retains an in/out split for older callers — split is computed lazily from
// the messages table when needed (see avgResponseTime below).
async function avgResponseTimeForDay(tenantId, date) {
    const { data } = await supabase
        .from('messages')
        .select('response_time_ms')
        .eq('tenant_id', tenantId)
        .gte('created_at', `${date}T00:00:00Z`)
        .lt('created_at', `${date}T23:59:59Z`)
        .not('response_time_ms', 'is', null);
    const rows = (data ?? []);
    if (rows.length === 0)
        return 0;
    const total = rows.reduce((s, r) => s + (r.response_time_ms ?? 0), 0);
    return Math.round(total / rows.length);
}
export async function getDailyUsage(tenantId, date) {
    const { data, error } = await supabase
        .from('usage')
        .select('*')
        .eq('tenant_id', tenantId)
        .eq('date', date)
        .single();
    if (error || !data)
        return null;
    return {
        date: data.date,
        messagesIn: 0,
        messagesOut: data.messages_count ?? 0,
        tokensIn: data.tokens_input ?? 0,
        tokensOut: data.tokens_output ?? 0,
        costUsd: data.cost_usd ?? 0,
        avgResponseTimeMs: await avgResponseTimeForDay(tenantId, date),
        uniqueCustomers: data.customers_active ?? 0,
    };
}
// ─── Monthly Usage ──────────────────────────────────────────────────
export async function getMonthlyUsage(tenantId, year, month) {
    const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
    const endDate = month === 12
        ? `${year + 1}-01-01`
        : `${year}-${String(month + 1).padStart(2, '0')}-01`;
    const { data: rows, error } = await supabase
        .from('usage')
        .select('*')
        .eq('tenant_id', tenantId)
        .gte('date', startDate)
        .lt('date', endDate)
        .order('date', { ascending: true });
    if (error) {
        log.error({ tenantId, error: error.message }, 'Failed to get monthly usage');
    }
    const days = rows ?? [];
    const dailyBreakdown = days.map(d => ({
        date: d.date,
        messagesIn: 0,
        messagesOut: d.messages_count ?? 0,
        tokensIn: d.tokens_input ?? 0,
        tokensOut: d.tokens_output ?? 0,
        costUsd: d.cost_usd ?? 0,
        avgResponseTimeMs: 0,
        uniqueCustomers: d.customers_active ?? 0,
    }));
    const totals = dailyBreakdown.reduce((acc, d) => ({
        messages: acc.messages + d.messagesIn + d.messagesOut,
        tokensIn: acc.tokensIn + d.tokensIn,
        tokensOut: acc.tokensOut + d.tokensOut,
        costUsd: acc.costUsd + d.costUsd,
        responseTimeMs: acc.responseTimeMs + d.avgResponseTimeMs * (d.messagesIn + d.messagesOut),
        customers: acc.customers + d.uniqueCustomers,
    }), { messages: 0, tokensIn: 0, tokensOut: 0, costUsd: 0, responseTimeMs: 0, customers: 0 });
    return {
        year,
        month,
        totalMessages: totals.messages,
        totalTokensIn: totals.tokensIn,
        totalTokensOut: totals.tokensOut,
        totalCostUsd: Math.round(totals.costUsd * 100) / 100,
        avgResponseTimeMs: totals.messages > 0 ? Math.round(totals.responseTimeMs / totals.messages) : 0,
        uniqueCustomers: totals.customers,
        dailyBreakdown,
    };
}
// ─── Full Tenant Analytics ──────────────────────────────────────────
export async function getTenantAnalytics(tenantId, fromDate, toDate) {
    // Daily breakdown
    const { data: days, error: dayErr } = await supabase
        .from('usage')
        .select('*')
        .eq('tenant_id', tenantId)
        .gte('date', fromDate)
        .lte('date', toDate)
        .order('date', { ascending: true });
    if (dayErr)
        log.error({ error: dayErr.message }, 'Failed to fetch daily usage');
    const dailyBreakdown = (days ?? []).map(d => ({
        date: d.date,
        messagesIn: 0,
        messagesOut: d.messages_count ?? 0,
        tokensIn: d.tokens_input ?? 0,
        tokensOut: d.tokens_output ?? 0,
        costUsd: d.cost_usd ?? 0,
        avgResponseTimeMs: 0,
        uniqueCustomers: d.customers_active ?? 0,
    }));
    // Model breakdown — read from messages table where the channel routes
    // already write the correct column names.
    const { data: modelRows } = await supabase
        .from('messages')
        .select('model_used, cost_usd')
        .eq('tenant_id', tenantId)
        .gte('created_at', `${fromDate}T00:00:00`)
        .lte('created_at', `${toDate}T23:59:59`)
        .not('model_used', 'is', null);
    const modelMap = new Map();
    for (const row of (modelRows ?? [])) {
        const key = row.model_used ?? 'unknown';
        const existing = modelMap.get(key) ?? { count: 0, cost: 0 };
        existing.count++;
        existing.cost += row.cost_usd ?? 0;
        modelMap.set(key, existing);
    }
    const topModels = [...modelMap.entries()]
        .map(([model, stats]) => ({ model, count: stats.count, cost: Math.round(stats.cost * 100) / 100 }))
        .sort((a, b) => b.count - a.count);
    // Aggregate
    const totals = dailyBreakdown.reduce((acc, d) => ({
        inbound: acc.inbound + d.messagesIn,
        outbound: acc.outbound + d.messagesOut,
        tokensIn: acc.tokensIn + d.tokensIn,
        tokensOut: acc.tokensOut + d.tokensOut,
        costUsd: acc.costUsd + d.costUsd,
        responseTime: acc.responseTime + d.avgResponseTimeMs * (d.messagesIn + d.messagesOut),
        messages: acc.messages + d.messagesIn + d.messagesOut,
        customers: acc.customers + d.uniqueCustomers,
    }), { inbound: 0, outbound: 0, tokensIn: 0, tokensOut: 0, costUsd: 0, responseTime: 0, messages: 0, customers: 0 });
    return {
        tenantId,
        period: { from: fromDate, to: toDate },
        totalMessages: totals.messages,
        totalInbound: totals.inbound,
        totalOutbound: totals.outbound,
        totalTokensIn: totals.tokensIn,
        totalTokensOut: totals.tokensOut,
        totalCostUsd: Math.round(totals.costUsd * 100) / 100,
        avgResponseTimeMs: totals.messages > 0 ? Math.round(totals.responseTime / totals.messages) : 0,
        uniqueCustomers: totals.customers,
        topModels,
        dailyBreakdown,
    };
}
//# sourceMappingURL=tracker.js.map