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

export interface ClubModule {
  id: string;
  clubId: string;
  moduleId: string;
  enabled: boolean;
  limitsJson: Record<string, unknown> | null;
  usageJson: Record<string, unknown> | null;
  createdAt: Date;
  updatedAt: Date;
}

/**
 * ModulesService manages the club_modules table — feature flags and
 * usage-based limits for each club's licensed modules.
 */
@Injectable()
export class ModulesService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * Check if a specific module is enabled for a club.
   */
  async isEnabled(clubId: string, moduleId: string): Promise<boolean> {
    const rows = await this.prisma.$queryRawUnsafe<{ enabled: boolean }[]>(
      `SELECT enabled FROM club_modules WHERE "clubId" = $1 AND "moduleId" = $2`,
      clubId,
      moduleId,
    );

    return rows.length > 0 && rows[0].enabled;
  }

  /**
   * Get a single club module record (or null if not found).
   */
  async getModule(
    clubId: string,
    moduleId: string,
  ): Promise<ClubModule | null> {
    const rows = await this.prisma.$queryRawUnsafe<ClubModule[]>(
      `SELECT id, "clubId", "moduleId", enabled,
              "limitsJson", "usageJson",
              "createdAt", "updatedAt"
       FROM club_modules
       WHERE "clubId" = $1 AND "moduleId" = $2`,
      clubId,
      moduleId,
    );

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

  /**
   * Get all modules for a club.
   */
  async getAllModules(clubId: string): Promise<ClubModule[]> {
    return this.prisma.$queryRawUnsafe<ClubModule[]>(
      `SELECT id, "clubId", "moduleId", enabled,
              "limitsJson", "usageJson",
              "createdAt", "updatedAt"
       FROM club_modules
       WHERE "clubId" = $1
       ORDER BY "moduleId"`,
      clubId,
    );
  }

  /**
   * Enable a module for a club with optional limits.
   */
  async enableModule(
    clubId: string,
    moduleId: string,
    limits?: Record<string, unknown>,
  ): Promise<ClubModule> {
    const limitsValue = limits ? JSON.stringify(limits) : null;

    const rows = await this.prisma.$queryRawUnsafe<ClubModule[]>(
      `INSERT INTO club_modules ("clubId", "moduleId", enabled, "limitsJson", "createdAt", "updatedAt")
       VALUES ($1, $2, true, $3::jsonb, NOW(), NOW())
       ON CONFLICT ("clubId", "moduleId") DO UPDATE
         SET enabled = true, "limitsJson" = COALESCE($3::jsonb, club_modules."limitsJson"), "updatedAt" = NOW()
       RETURNING id, "clubId", "moduleId", enabled,
                 "limitsJson", "usageJson",
                 "createdAt", "updatedAt"`,
      clubId,
      moduleId,
      limitsValue,
    );

    return rows[0];
  }

  /**
   * Disable a module for a club.
   */
  async disableModule(clubId: string, moduleId: string): Promise<ClubModule> {
    const rows = await this.prisma.$queryRawUnsafe<ClubModule[]>(
      `UPDATE club_modules
       SET enabled = false, "updatedAt" = NOW()
       WHERE "clubId" = $1 AND "moduleId" = $2
       RETURNING id, "clubId", "moduleId", enabled,
                 "limitsJson", "usageJson",
                 "createdAt", "updatedAt"`,
      clubId,
      moduleId,
    );

    if (!rows.length) {
      throw new NotFoundException(
        `Module "${moduleId}" not found for club ${clubId}`,
      );
    }

    return rows[0];
  }

  /**
   * Update the usage counters for a module.
   */
  async updateUsage(
    clubId: string,
    moduleId: string,
    usage: Record<string, unknown>,
  ): Promise<ClubModule> {
    const rows = await this.prisma.$queryRawUnsafe<ClubModule[]>(
      `UPDATE club_modules
       SET "usageJson" = $3::jsonb, "updatedAt" = NOW()
       WHERE "clubId" = $1 AND "moduleId" = $2
       RETURNING id, "clubId", "moduleId", enabled,
                 "limitsJson", "usageJson",
                 "createdAt", "updatedAt"`,
      clubId,
      moduleId,
      JSON.stringify(usage),
    );

    if (!rows.length) {
      throw new NotFoundException(
        `Module "${moduleId}" not found for club ${clubId}`,
      );
    }

    return rows[0];
  }

  /**
   * Check whether adding `amount` of a resource would exceed the limit.
   * Returns true if usage + amount is within the allowed limit.
   */
  async checkLimit(
    clubId: string,
    moduleId: string,
    resource: string,
    amount: number,
  ): Promise<boolean> {
    const mod = await this.getModule(clubId, moduleId);

    if (!mod || !mod.enabled) {
      return false;
    }

    const limits = mod.limitsJson as Record<string, number> | null;
    const usage = mod.usageJson as Record<string, number> | null;

    if (!limits || limits[resource] === undefined) {
      // No limit defined for this resource — allow
      return true;
    }

    const currentUsage = usage?.[resource] ?? 0;
    return currentUsage + amount <= limits[resource];
  }
}
