/**
 * Usage REST endpoints.
 *
 * GET    /usage                  — list usage records (filterable)
 * GET    /usage/summary          — aggregated summary
 * POST   /usage                  — upsert a usage record
 * POST   /usage/check-limits     — check if user is within plan limits
 */

import type { FastifyInstance } from 'fastify';
import { getRequestProductId } from '../../lib/request-context.js';
import { BadRequestError } from '../../lib/errors.js';
import * as repo from './repository.js';
import {
  UpsertUsageSchema,
  CheckLimitsSchema,
  type UsageDoc,
  type ModelBreakdown,
  type SourceBreakdown,
  type ProductBreakdown,
} from './types.js';

/**
 * Resolve productId for usage queries.
 * - If query has productId=_all → return undefined (cross-product)
 * - If query has a specific productId → use it
 * - Otherwise → use the request's default productId from JWT/header/env
 */
function resolveProductFilter(req: import('fastify').FastifyRequest): string | undefined {
  const qp = (req.query as Record<string, string>).productId;
  if (qp === '_all') return undefined;
  if (qp && qp.length > 0) return qp;
  return getRequestProductId(req);
}

/**
 * Plan limits — configurable per product via PLAN_LIMITS_JSON env var.
 * Format: JSON object keyed by plan name, value is metric→limit map.
 * Metric names are product-configurable (not just tokens/words/dictations).
 */
function loadPlanLimits(): Record<string, Record<string, number>> {
  const json = process.env.PLAN_LIMITS_JSON;
  if (json) {
    try {
      return JSON.parse(json);
    } catch {
      /* fall through to defaults */
    }
  }
  return {
    free: { tokens: 10_000, words: 5_000, dictations: 100 },
    pro: { tokens: 100_000, words: 50_000, dictations: 5_000 },
    enterprise: { tokens: 1_000_000, words: 500_000, dictations: 50_000 },
  };
}

const PLAN_LIMITS = loadPlanLimits();
const WARN_THRESHOLD = Number(process.env.USAGE_WARN_THRESHOLD || '0.8');

export async function usageRoutes(app: FastifyInstance) {
  // List usage
  app.get('/usage', async req => {
    const { userId, days = '30', limit = '100' } = req.query as Record<string, string>;
    const productId = resolveProductFilter(req);
    const records = await repo.list({
      userId,
      days: Number(days),
      limit: Number(limit),
      productId,
    });
    return { records, count: records.length };
  });

  // Summary
  app.get('/usage/summary', async req => {
    const { userId, days = '30' } = req.query as Record<string, string>;
    const productId = resolveProductFilter(req);
    const records = await repo.list({ userId, days: Number(days), productId });

    const byModel: Record<string, { tokens: number; requests: number; cost: number }> = {};
    const bySource: Record<string, { tokens: number; requests: number; cost: number }> = {};
    const byProduct: Record<string, { tokens: number; requests: number; cost: number }> = {};
    for (const r of records) {
      const m = r.model || 'gpt-4o-mini';
      if (!byModel[m]) byModel[m] = { tokens: 0, requests: 0, cost: 0 };
      byModel[m].tokens += r.tokensUsed;
      byModel[m].requests += r.dictations;
      byModel[m].cost += r.costUsd;

      const s = r.source || 'unknown';
      if (!bySource[s]) bySource[s] = { tokens: 0, requests: 0, cost: 0 };
      bySource[s].tokens += r.tokensUsed;
      bySource[s].requests += r.dictations;
      bySource[s].cost += r.costUsd;

      const p = r.productId;
      if (!byProduct[p]) byProduct[p] = { tokens: 0, requests: 0, cost: 0 };
      byProduct[p].tokens += r.tokensUsed;
      byProduct[p].requests += r.dictations;
      byProduct[p].cost += r.costUsd;
    }

    const modelBreakdown: ModelBreakdown[] = Object.entries(byModel).map(([model, s]) => ({
      model,
      ...s,
    }));

    const sourceBreakdown: SourceBreakdown[] = Object.entries(bySource).map(([source, s]) => ({
      source,
      ...s,
    }));

    const productBreakdown: ProductBreakdown[] = Object.entries(byProduct).map(([pid, s]) => ({
      productId: pid,
      ...s,
    }));

    return {
      totalWords: records.reduce((sum, r) => sum + r.words, 0),
      totalDictations: records.reduce((sum, r) => sum + r.dictations, 0),
      totalTokens: records.reduce((sum, r) => sum + r.tokensUsed, 0),
      totalCost: records.reduce((sum, r) => sum + r.costUsd, 0),
      records,
      modelBreakdown,
      sourceBreakdown,
      productBreakdown,
    };
  });

  // Upsert usage
  app.post('/usage', async req => {
    const parsed = UpsertUsageSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const input = parsed.data;
    const productId = getRequestProductId(req);
    const doc: UsageDoc = {
      id: `usg_${input.date}_${input.userId}${input.model ? `_${input.model}` : ''}${input.source ? `_${input.source}` : ''}`,
      productId,
      ...input,
      createdAt: new Date().toISOString(),
    };
    return repo.upsert(doc);
  });

  // Check limits
  app.post('/usage/check-limits', async req => {
    const parsed = CheckLimitsSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const { userId, plan } = parsed.data;
    const limits = PLAN_LIMITS[plan] ?? PLAN_LIMITS.free;
    const productId = getRequestProductId(req);
    const usage = await repo.getMonthlyUsage(userId, productId);

    const exceeded: string[] = [];
    const warnings: string[] = [];

    for (const metric of ['tokens', 'words', 'dictations'] as const) {
      const limitVal = limits[metric] ?? 0;
      const usageVal = usage[metric] ?? 0;
      if (limitVal > 0 && usageVal >= limitVal) exceeded.push(metric);
      else if (limitVal > 0 && usageVal >= limitVal * WARN_THRESHOLD) warnings.push(metric);
    }

    return {
      allowed: exceeded.length === 0,
      usage,
      limits,
      warnings,
      exceeded,
      plan,
    };
  });
}
