import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';

export interface UsageRecord {
  id: string;
  clubId: string;
  period: Date;
  activeUsers: number;
  clients: number;
  callStorageGb: number;
  webhooksSent: number;
  pushSent: number;
  apiRequests: number;
  createdAt: Date;
  updatedAt: Date;
}

export interface LimitCheck {
  resource: string;
  current: number;
  limit: number;
  exceeded: boolean;
}

/**
 * UsageMeterService tracks and aggregates resource usage per club.
 *
 * Counts active users, clients, call storage, webhooks sent, push
 * notifications, and API requests. Supports limit checking against
 * the club's license limits.
 */
@Injectable()
export class MeteringService {
  private readonly logger = new Logger(MeteringService.name);

  constructor(private readonly prisma: PrismaService) {}

  /**
   * Aggregate all usage metrics for a club for the current month.
   * Upserts into the usage_records table.
   */
  async aggregateUsage(clubId: string): Promise<UsageRecord> {
    const periodStart = this.getCurrentPeriodStart();

    const activeUsers = await this.countActiveUsers(clubId);
    const clients = await this.countClients(clubId);
    const callStorageGb = await this.sumCallStorage(clubId);
    const webhooksSent = await this.countWebhooksSent(clubId, periodStart);
    const pushSent = await this.countPushSent(clubId, periodStart);
    const apiRequests = await this.countApiRequests(clubId, periodStart);

    const rows = await this.prisma.$queryRawUnsafe<UsageRecord[]>(
      `INSERT INTO usage_records
         ("clubId", period, "activeUsers", clients, "callStorageGb",
          "webhooksSent", "pushSent", "apiRequests", "createdAt", "updatedAt")
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW())
       ON CONFLICT ("clubId", period) DO UPDATE SET
         "activeUsers" = $3,
         clients = $4,
         "callStorageGb" = $5,
         "webhooksSent" = $6,
         "pushSent" = $7,
         "apiRequests" = $8,
         "updatedAt" = NOW()
       RETURNING id, "clubId", period, "activeUsers",
                 clients, "callStorageGb",
                 "webhooksSent", "pushSent",
                 "apiRequests",
                 "createdAt", "updatedAt"`,
      clubId,
      periodStart,
      activeUsers,
      clients,
      callStorageGb,
      webhooksSent,
      pushSent,
      apiRequests,
    );

    this.logger.debug(`Aggregated usage for club ${clubId}: users=${activeUsers}, clients=${clients}`);

    return rows[0];
  }

  /**
   * Get the latest usage record for a club (optionally for a specific period).
   */
  async getUsage(clubId: string, period?: Date): Promise<UsageRecord | null> {
    const periodStart = period ?? this.getCurrentPeriodStart();

    const rows = await this.prisma.$queryRawUnsafe<UsageRecord[]>(
      `SELECT id, "clubId", period, "activeUsers",
              clients, "callStorageGb",
              "webhooksSent", "pushSent",
              "apiRequests",
              "createdAt", "updatedAt"
       FROM usage_records
       WHERE "clubId" = $1 AND period = $2`,
      clubId,
      periodStart,
    );

    return rows.length > 0 ? rows[0] : null;
  }

  /**
   * Check all resource limits for a club against its license and module limits.
   * Base limits (users, clients) come from the licenses table.
   * Module-specific limits (webhooks, push, call storage) come from club_modules.limits_json.
   */
  async checkLimits(clubId: string): Promise<LimitCheck[]> {
    const periodStart = this.getCurrentPeriodStart();

    // Get current usage (or aggregate fresh if none exists)
    let usage = await this.getUsage(clubId, periodStart);
    if (!usage) {
      usage = await this.aggregateUsage(clubId);
    }

    // Get base limits from license (max_users, max_clients)
    const licenses = await this.prisma.$queryRawUnsafe<
      { maxUsers: number; maxClients: number }[]
    >(
      `SELECT "maxUsers", "maxClients"
       FROM licenses
       WHERE "clubId" = $1 AND "isActive" = true
       LIMIT 1`,
      clubId,
    );

    if (!licenses.length) {
      return [];
    }

    const license = licenses[0];

    // Get module-specific limits from club_modules.limits_json
    const moduleLimits = await this.prisma.$queryRawUnsafe<
      { moduleId: string; limitsJson: Record<string, number> | null }[]
    >(
      `SELECT "moduleId", "limitsJson"
       FROM club_modules
       WHERE "clubId" = $1 AND enabled = true
         AND "moduleId" IN ('webhooks', 'push', 'sip_recording')`,
      clubId,
    );

    const moduleLimitsMap = new Map(
      moduleLimits.map((m) => [m.moduleId, m.limitsJson ?? {}]),
    );

    const webhookLimits = moduleLimitsMap.get('webhooks') ?? {};
    const pushLimits = moduleLimitsMap.get('push') ?? {};
    const sipRecordingLimits = moduleLimitsMap.get('sip_recording') ?? {};

    return [
      {
        resource: 'active_users',
        current: usage.activeUsers,
        limit: license.maxUsers,
        exceeded: usage.activeUsers > license.maxUsers,
      },
      {
        resource: 'clients',
        current: usage.clients,
        limit: license.maxClients,
        exceeded: usage.clients > license.maxClients,
      },
      {
        resource: 'call_storage_gb',
        current: usage.callStorageGb,
        limit: (sipRecordingLimits as Record<string, number>).max_storage_gb ?? 0,
        exceeded:
          usage.callStorageGb >
          ((sipRecordingLimits as Record<string, number>).max_storage_gb ?? 0),
      },
      {
        resource: 'webhooks_sent',
        current: usage.webhooksSent,
        limit: (webhookLimits as Record<string, number>).max_events ?? 10000,
        exceeded:
          usage.webhooksSent >
          ((webhookLimits as Record<string, number>).max_events ?? 10000),
      },
      {
        resource: 'push_sent',
        current: usage.pushSent,
        limit: (pushLimits as Record<string, number>).max_messages ?? 5000,
        exceeded:
          usage.pushSent >
          ((pushLimits as Record<string, number>).max_messages ?? 5000),
      },
      {
        resource: 'api_requests',
        current: usage.apiRequests,
        limit: 50000,
        exceeded: usage.apiRequests > 50000,
      },
    ];
  }

  /**
   * Increment the API requests counter for the current month.
   */
  async recordApiRequest(clubId: string): Promise<void> {
    const periodStart = this.getCurrentPeriodStart();
    await this.prisma.$executeRawUnsafe(
      `INSERT INTO usage_records ("clubId", period, "apiRequests", "createdAt", "updatedAt")
       VALUES ($1, $2, 1, NOW(), NOW())
       ON CONFLICT ("clubId", period) DO UPDATE
         SET "apiRequests" = usage_records."apiRequests" + 1, "updatedAt" = NOW()`,
      clubId,
      periodStart,
    );
  }

  /**
   * Increment the webhooks sent counter for the current month.
   */
  async recordWebhookSent(clubId: string): Promise<void> {
    const periodStart = this.getCurrentPeriodStart();
    await this.prisma.$executeRawUnsafe(
      `INSERT INTO usage_records ("clubId", period, "webhooksSent", "createdAt", "updatedAt")
       VALUES ($1, $2, 1, NOW(), NOW())
       ON CONFLICT ("clubId", period) DO UPDATE
         SET "webhooksSent" = usage_records."webhooksSent" + 1, "updatedAt" = NOW()`,
      clubId,
      periodStart,
    );
  }

  /**
   * Increment the push notifications sent counter for the current month.
   */
  async recordPushSent(clubId: string): Promise<void> {
    const periodStart = this.getCurrentPeriodStart();
    await this.prisma.$executeRawUnsafe(
      `INSERT INTO usage_records ("clubId", period, "pushSent", "createdAt", "updatedAt")
       VALUES ($1, $2, 1, NOW(), NOW())
       ON CONFLICT ("clubId", period) DO UPDATE
         SET "pushSent" = usage_records."pushSent" + 1, "updatedAt" = NOW()`,
      clubId,
      periodStart,
    );
  }

  /**
   * Count active users for a club.
   */
  private async countActiveUsers(clubId: string): Promise<number> {
    const rows = await this.prisma.$queryRawUnsafe<{ count: bigint }[]>(
      `SELECT COUNT(*)::bigint AS count FROM users WHERE "clubId" = $1 AND "isActive" = true`,
      clubId,
    );
    return Number(rows[0]?.count ?? 0);
  }

  /**
   * Count total clients for a club.
   */
  private async countClients(clubId: string): Promise<number> {
    const rows = await this.prisma.$queryRawUnsafe<{ count: bigint }[]>(
      `SELECT COUNT(*)::bigint AS count FROM clients WHERE "clubId" = $1`,
      clubId,
    );
    return Number(rows[0]?.count ?? 0);
  }

  /**
   * Sum call recording storage in GB for a club.
   * Note: Call recording is not implemented in MVP (sip_calls stores metadata only).
   * Returns 0 until a call_recordings table is added.
   */
  private async sumCallStorage(_clubId: string): Promise<number> {
    // TODO: Query actual call recording storage when sip_recording module is implemented
    return 0;
  }

  /**
   * Count webhooks sent since a given date.
   */
  private async countWebhooksSent(
    clubId: string,
    since: Date,
  ): Promise<number> {
    const rows = await this.prisma.$queryRawUnsafe<{ count: bigint }[]>(
      `SELECT COUNT(*)::bigint AS count FROM webhook_logs
       WHERE "clubId" = $1 AND "createdAt" >= $2`,
      clubId,
      since,
    );
    return Number(rows[0]?.count ?? 0);
  }

  /**
   * Count push notifications sent since a given date.
   */
  private async countPushSent(clubId: string, since: Date): Promise<number> {
    const rows = await this.prisma.$queryRawUnsafe<{ count: bigint }[]>(
      `SELECT COUNT(*)::bigint AS count FROM notifications
       WHERE "clubId" = $1 AND "createdAt" >= $2`,
      clubId,
      since,
    );
    return Number(rows[0]?.count ?? 0);
  }

  /**
   * Count API requests since a given date (from the current usage record).
   */
  private async countApiRequests(
    clubId: string,
    since: Date,
  ): Promise<number> {
    const rows = await this.prisma.$queryRawUnsafe<{ apiRequests: number }[]>(
      `SELECT COALESCE("apiRequests", 0) AS "apiRequests"
       FROM usage_records
       WHERE "clubId" = $1 AND period = $2`,
      clubId,
      since,
    );
    return rows.length > 0 ? rows[0].apiRequests : 0;
  }

  /**
   * Get the first day of the current month (period start).
   */
  private getCurrentPeriodStart(): Date {
    const now = new Date();
    return new Date(now.getFullYear(), now.getMonth(), 1);
  }
}
