import { queryOneWithRLS, queryWithRLS, getTransactionClient } from "../db.js";
import { BILLING_REGISTRY } from "@nextsparkjs/registries/billing-registry";
import { SubscriptionService } from "./subscription.service.js";
import { PlanService } from "./plan.service.js";
import { getPeriodKey, calculatePercentUsed } from "../billing/helpers.js";
class UsageService {
  // ===========================================
  // QUERIES
  // ===========================================
  /**
   * Get usage record for a specific subscription, limit, and period
   *
   * @param subscriptionId - Subscription ID
   * @param limitSlug - Limit slug
   * @param periodKey - Period key (e.g., '2024-01')
   * @returns Usage record or null
   *
   * @example
   * const usage = await UsageService.get('sub-123', 'projects', '2024-01')
   */
  static async get(subscriptionId, limitSlug, periodKey) {
    if (!subscriptionId || !limitSlug || !periodKey) {
      return null;
    }
    return queryOneWithRLS(
      `
      SELECT * FROM "usage"
      WHERE "subscriptionId" = $1
        AND "limitSlug" = $2
        AND "periodKey" = $3
      `,
      [subscriptionId, limitSlug, periodKey]
    );
  }
  /**
   * Get current usage value for a subscription and limit
   *
   * @param subscriptionId - Subscription ID
   * @param limitSlug - Limit slug
   * @returns Current usage value (0 if not found)
   *
   * @example
   * const current = await UsageService.getCurrent('sub-123', 'api_calls')
   */
  static async getCurrent(subscriptionId, limitSlug) {
    if (!subscriptionId || !limitSlug) {
      return 0;
    }
    const limitConfig = BILLING_REGISTRY.limits[limitSlug];
    const periodKey = limitConfig ? getPeriodKey(limitConfig.resetPeriod) : "all_time";
    const usage = await this.get(subscriptionId, limitSlug, periodKey);
    return (usage == null ? void 0 : usage.currentValue) ?? 0;
  }
  /**
   * Get all usage records for a subscription in current period
   *
   * @param subscriptionId - Subscription ID
   * @returns Array of usage records
   *
   * @example
   * const allUsage = await UsageService.getAll('sub-123')
   */
  static async getAll(subscriptionId) {
    if (!subscriptionId) {
      return [];
    }
    return queryWithRLS(
      `
      SELECT * FROM "usage"
      WHERE "subscriptionId" = $1
      ORDER BY "limitSlug" ASC
      `,
      [subscriptionId]
    );
  }
  /**
   * Get current usage for a team by limit slug
   * Convenience method that doesn't require subscriptionId
   *
   * @param teamId - Team ID
   * @param limitSlug - Limit slug
   * @returns Current usage value (0 if not found)
   *
   * @example
   * const apiCalls = await UsageService.getByTeam('team-123', 'api_calls')
   */
  static async getByTeam(teamId, limitSlug) {
    if (!teamId || !limitSlug) {
      return 0;
    }
    const subscription = await SubscriptionService.getActive(teamId);
    if (!subscription) {
      return 0;
    }
    return this.getCurrent(subscription.id, limitSlug);
  }
  /**
   * List subscriptions near their quota threshold
   *
   * @param percentThreshold - Percentage threshold (e.g., 80 means >= 80% used)
   * @param limitSlug - Optional limit slug to check (all limits if not specified)
   * @returns Array of subscriptions with their usage info
   *
   * @example
   * const nearLimit = await UsageService.listNearQuota(80, 'api_calls')
   */
  static async listNearQuota(percentThreshold = 80, limitSlug) {
    if (percentThreshold < 0 || percentThreshold > 100) {
      throw new Error("Threshold must be between 0 and 100");
    }
    let query = `
      SELECT
        s.id as "subscriptionId",
        s."teamId",
        p.slug as "planSlug",
        u."limitSlug",
        u."currentValue" as current
      FROM "subscriptions" s
      JOIN "plans" p ON s."planId" = p.id
      JOIN "usage" u ON u."subscriptionId" = s.id
      WHERE s.status IN ('active', 'trialing')
    `;
    const params = [];
    if (limitSlug) {
      query += ` AND u."limitSlug" = $1`;
      params.push(limitSlug);
    }
    const results = await queryWithRLS(query, params);
    const nearQuota = [];
    for (const row of results) {
      const max = PlanService.getLimit(row.planSlug, row.limitSlug);
      if (max <= 0) continue;
      const percentUsed = calculatePercentUsed(row.current, max);
      if (percentUsed >= percentThreshold) {
        nearQuota.push({
          ...row,
          max,
          percentUsed
        });
      }
    }
    return nearQuota.sort((a, b) => b.percentUsed - a.percentUsed);
  }
  /**
   * Get usage trend over multiple periods
   *
   * @param subscriptionId - Subscription ID
   * @param limitSlug - Limit slug
   * @param periods - Number of periods to look back
   * @returns Array of usage by period
   *
   * @example
   * const trend = await UsageService.getTrend('sub-123', 'api_calls', 6)
   */
  static async getTrend(subscriptionId, limitSlug, periods = 6) {
    if (!subscriptionId || !limitSlug) {
      return [];
    }
    if (periods < 1 || periods > 24) {
      throw new Error("Periods must be between 1 and 24");
    }
    const results = await queryWithRLS(
      `
      SELECT "periodKey", "currentValue"
      FROM "usage"
      WHERE "subscriptionId" = $1 AND "limitSlug" = $2
      ORDER BY "periodKey" DESC
      LIMIT $3
      `,
      [subscriptionId, limitSlug, periods]
    );
    return results.map((r) => ({
      periodKey: r.periodKey,
      value: r.currentValue
    }));
  }
  // ===========================================
  // MUTATIONS
  // ===========================================
  /**
   * Track usage - Updates both usage and usage_events tables atomically
   *
   * This is the primary function for recording quota consumption.
   * It updates the aggregate usage table (for fast O(1) checks) and
   * creates a detailed event record (for reporting and audit).
   *
   * @param params - Usage tracking parameters
   * @returns Updated usage record
   *
   * @example
   * const usage = await UsageService.track({
   *   teamId: 'team-123',
   *   userId: 'user-456',
   *   limitSlug: 'api_calls',
   *   delta: 1,
   *   action: 'api.request',
   * })
   */
  static async track(params) {
    const {
      teamId,
      userId,
      limitSlug,
      delta,
      action,
      resourceType,
      resourceId,
      metadata = {}
    } = params;
    if (!teamId || teamId.trim() === "") {
      throw new Error("Team ID is required");
    }
    if (!userId || userId.trim() === "") {
      throw new Error("User ID is required");
    }
    if (!limitSlug || limitSlug.trim() === "") {
      throw new Error("Limit slug is required");
    }
    const subscription = await SubscriptionService.getActive(teamId);
    if (!subscription) {
      throw new Error(`No active subscription found for team ${teamId}`);
    }
    const limitConfig = BILLING_REGISTRY.limits[limitSlug];
    const periodKey = limitConfig ? getPeriodKey(limitConfig.resetPeriod) : "all_time";
    const tx = await getTransactionClient();
    try {
      await tx.query(
        `
        INSERT INTO "usage_events" (
          "subscriptionId",
          "userId",
          "teamId",
          "limitSlug",
          delta,
          action,
          "resourceType",
          "resourceId",
          "periodKey",
          metadata
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
        `,
        [
          subscription.id,
          userId,
          teamId,
          limitSlug,
          delta,
          action,
          resourceType,
          resourceId,
          periodKey,
          JSON.stringify(metadata)
        ]
      );
      const usage = await tx.queryOne(
        `
        INSERT INTO "usage" (
          "subscriptionId",
          "limitSlug",
          "periodKey",
          "currentValue",
          "lastIncrementAt"
        ) VALUES ($1, $2, $3, $4, NOW())
        ON CONFLICT ("subscriptionId", "limitSlug", "periodKey")
        DO UPDATE SET
          "currentValue" = "usage"."currentValue" + $4,
          "lastIncrementAt" = NOW()
        RETURNING *
        `,
        [subscription.id, limitSlug, periodKey, delta]
      );
      await tx.commit();
      if (!usage) {
        throw new Error("Failed to track usage");
      }
      return usage;
    } catch (error) {
      await tx.rollback();
      throw error;
    }
  }
  /**
   * Increment usage (convenience wrapper)
   *
   * @param subscriptionId - Subscription ID
   * @param limitSlug - Limit slug
   * @param amount - Amount to increment (default: 1)
   * @returns Updated usage record
   *
   * @example
   * const usage = await UsageService.increment('sub-123', 'api_calls')
   */
  static async increment(subscriptionId, limitSlug, amount = 1) {
    if (!subscriptionId || !limitSlug) {
      throw new Error("Subscription ID and limit slug are required");
    }
    const limitConfig = BILLING_REGISTRY.limits[limitSlug];
    const periodKey = limitConfig ? getPeriodKey(limitConfig.resetPeriod) : "all_time";
    const result = await queryOneWithRLS(
      `
      INSERT INTO "usage" (
        "subscriptionId",
        "limitSlug",
        "periodKey",
        "currentValue",
        "lastIncrementAt"
      ) VALUES ($1, $2, $3, $4, NOW())
      ON CONFLICT ("subscriptionId", "limitSlug", "periodKey")
      DO UPDATE SET
        "currentValue" = "usage"."currentValue" + $4,
        "lastIncrementAt" = NOW()
      RETURNING *
      `,
      [subscriptionId, limitSlug, periodKey, amount]
    );
    if (!result) {
      throw new Error("Failed to increment usage");
    }
    return result;
  }
  /**
   * Decrement usage
   *
   * @param subscriptionId - Subscription ID
   * @param limitSlug - Limit slug
   * @param amount - Amount to decrement (default: 1)
   * @returns Updated usage record
   *
   * @example
   * await UsageService.decrement('sub-123', 'projects', 1)
   */
  static async decrement(subscriptionId, limitSlug, amount = 1) {
    return this.increment(subscriptionId, limitSlug, -amount);
  }
  /**
   * Reset usage for a subscription
   *
   * @param subscriptionId - Subscription ID
   * @param limitSlug - Optional limit slug (if not provided, resets all)
   *
   * @example
   * // Reset specific limit
   * await UsageService.reset('sub-123', 'api_calls')
   *
   * // Reset all limits
   * await UsageService.reset('sub-123')
   */
  static async reset(subscriptionId, limitSlug) {
    if (!subscriptionId || subscriptionId.trim() === "") {
      throw new Error("Subscription ID is required");
    }
    if (limitSlug) {
      await queryWithRLS(
        `
        UPDATE "usage"
        SET "currentValue" = 0, "lastIncrementAt" = NOW()
        WHERE "subscriptionId" = $1 AND "limitSlug" = $2
        `,
        [subscriptionId, limitSlug]
      );
    } else {
      await queryWithRLS(
        `
        UPDATE "usage"
        SET "currentValue" = 0, "lastIncrementAt" = NOW()
        WHERE "subscriptionId" = $1
        `,
        [subscriptionId]
      );
    }
  }
  // ===========================================
  // BATCH OPERATIONS
  // ===========================================
  /**
   * Process monthly usage reset (batch job)
   * Resets usage for all limits with monthly reset period
   *
   * @returns Number of records reset
   *
   * @example
   * const count = await UsageService.processMonthlyReset()
   */
  static async processMonthlyReset() {
    var _a;
    const monthlyLimits = Object.entries(BILLING_REGISTRY.limits).filter(([_, config]) => config.resetPeriod === "monthly").map(([slug]) => slug);
    if (monthlyLimits.length === 0) {
      return 0;
    }
    const placeholders = monthlyLimits.map((_, i) => `$${i + 1}`).join(", ");
    const result = await queryWithRLS(
      `
      WITH deleted AS (
        DELETE FROM "usage"
        WHERE "limitSlug" IN (${placeholders})
          AND "periodKey" != $${monthlyLimits.length + 1}
        RETURNING 1
      )
      SELECT COUNT(*)::int as count FROM deleted
      `,
      [...monthlyLimits, getPeriodKey("monthly")]
    );
    return ((_a = result[0]) == null ? void 0 : _a.count) ?? 0;
  }
  // ===========================================
  // REPORTING
  // ===========================================
  /**
   * Get usage breakdown by user for a team
   *
   * @param teamId - Team ID
   * @param periodKey - Period key (e.g., '2024-01')
   * @returns Array of user usage summaries
   *
   * @example
   * const byUser = await UsageService.getTeamUsageByUser('team-123', '2024-01')
   */
  static async getTeamUsageByUser(teamId, periodKey) {
    if (!teamId || !periodKey) {
      return [];
    }
    const subscription = await SubscriptionService.getActive(teamId);
    if (!subscription) return [];
    const results = await queryWithRLS(
      `
      SELECT
        ue."userId",
        u.name as "userName",
        u.email as "userEmail",
        ue."limitSlug",
        SUM(ue.delta)::int as "totalUsage"
      FROM "usage_events" ue
      LEFT JOIN "users" u ON u.id = ue."userId"
      WHERE ue."subscriptionId" = $1
        AND ue."periodKey" = $2
      GROUP BY ue."userId", u.name, u.email, ue."limitSlug"
      ORDER BY "totalUsage" DESC
      `,
      [subscription.id, periodKey]
    );
    const limitTotals = {};
    results.forEach((r) => {
      limitTotals[r.limitSlug] = (limitTotals[r.limitSlug] || 0) + r.totalUsage;
    });
    return results.map((r) => ({
      ...r,
      percentage: limitTotals[r.limitSlug] > 0 ? Math.round(r.totalUsage / limitTotals[r.limitSlug] * 100) : 0
    }));
  }
  /**
   * Get top consumers for a specific limit
   *
   * @param teamId - Team ID
   * @param limitSlug - Limit slug
   * @param periodKey - Period key
   * @param limit - Number of top consumers (default: 5)
   * @returns Array of top consumers
   *
   * @example
   * const top = await UsageService.getTopConsumers('team-123', 'api_calls', '2024-01')
   */
  static async getTopConsumers(teamId, limitSlug, periodKey, limit = 5) {
    if (!teamId || !limitSlug || !periodKey) {
      return [];
    }
    const subscription = await SubscriptionService.getActive(teamId);
    if (!subscription) return [];
    const results = await queryWithRLS(
      `
      SELECT
        ue."userId",
        u.name as "userName",
        SUM(ue.delta)::int as "totalUsage"
      FROM "usage_events" ue
      LEFT JOIN "users" u ON u.id = ue."userId"
      WHERE ue."subscriptionId" = $1
        AND ue."limitSlug" = $2
        AND ue."periodKey" = $3
      GROUP BY ue."userId", u.name
      ORDER BY "totalUsage" DESC
      LIMIT $4
      `,
      [subscription.id, limitSlug, periodKey, limit]
    );
    const total = results.reduce((sum, r) => sum + r.totalUsage, 0);
    return results.map((r) => ({
      ...r,
      percentage: total > 0 ? Math.round(r.totalUsage / total * 100) : 0
    }));
  }
  /**
   * Get user usage timeline
   *
   * @param userId - User ID
   * @param options - Query options
   * @returns Array of usage events
   *
   * @example
   * const timeline = await UsageService.getUserTimeline('user-123', {
   *   limitSlug: 'api_calls',
   *   limit: 50,
   * })
   */
  static async getUserTimeline(userId, options = {}) {
    if (!userId || userId.trim() === "") {
      return [];
    }
    const {
      limitSlug,
      startDate,
      endDate,
      limit = 100
    } = options;
    const params = [userId];
    let paramIndex = 2;
    let query = `
      SELECT * FROM "usage_events"
      WHERE "userId" = $1
    `;
    if (limitSlug) {
      query += ` AND "limitSlug" = $${paramIndex}`;
      params.push(limitSlug);
      paramIndex++;
    }
    if (startDate) {
      query += ` AND "createdAt" >= $${paramIndex}`;
      params.push(startDate.toISOString());
      paramIndex++;
    }
    if (endDate) {
      query += ` AND "createdAt" <= $${paramIndex}`;
      params.push(endDate.toISOString());
      paramIndex++;
    }
    query += `
      ORDER BY "createdAt" DESC
      LIMIT $${paramIndex}
    `;
    params.push(limit);
    return queryWithRLS(query, params);
  }
  /**
   * Get comprehensive team usage summary
   *
   * @param teamId - Team ID
   * @param periodKey - Period key (default: current month)
   * @returns Complete team usage summary
   *
   * @example
   * const summary = await UsageService.getTeamSummary('team-123')
   */
  static async getTeamSummary(teamId, periodKey) {
    if (!teamId) {
      return { byLimit: {}, byUser: [], topConsumers: [] };
    }
    const subscription = await SubscriptionService.getActive(teamId);
    if (!subscription) {
      return { byLimit: {}, byUser: [], topConsumers: [] };
    }
    const effectivePeriodKey = periodKey || getPeriodKey("monthly");
    const usageRows = await queryWithRLS(
      `
      SELECT * FROM "usage"
      WHERE "subscriptionId" = $1
        AND "periodKey" = $2
      `,
      [subscription.id, effectivePeriodKey]
    );
    const byLimit = {};
    const planConfig = BILLING_REGISTRY.plans.find(
      (p) => p.slug === subscription.plan.slug
    );
    for (const row of usageRows) {
      const limit = (planConfig == null ? void 0 : planConfig.limits[row.limitSlug]) ?? 0;
      byLimit[row.limitSlug] = {
        current: row.currentValue,
        limit,
        percentUsed: calculatePercentUsed(row.currentValue, limit)
      };
    }
    const byUser = await this.getTeamUsageByUser(teamId, effectivePeriodKey);
    const primaryLimit = Object.keys(byLimit)[0] || "api_calls";
    const topConsumers = await this.getTopConsumers(teamId, primaryLimit, effectivePeriodKey, 5);
    return { byLimit, byUser, topConsumers };
  }
}
export {
  UsageService
};
