// Entitlements Service
// Manages tenant/program/region/channel entitlements with allowlist support

import { v4 as uuidv4 } from 'uuid';
import type { Prisma } from '@prisma/client';
import prisma from '@/shared/database/prisma';
import { DbisError, ErrorCode } from '@/shared/types';
import { logger } from '@/infrastructure/monitoring/logger';
import {
  Entitlement,
  CreateEntitlementRequest,
  EntitlementCheckContext,
  CapabilityState,
} from '@/shared/solacenet/types';

export class EntitlementsService {
  /**
   * Get entitlements for a tenant/program
   */
  async getEntitlements(
    tenantId: string,
    programId?: string
  ): Promise<Entitlement[]> {
    const entitlements = await prisma.solacenet_entitlement.findMany({
      where: {
        tenantId,
        programId: programId || null,
      },
      include: {
        capability: true,
      },
      orderBy: {
        createdAt: 'desc',
      },
    });

    return entitlements.map(this.mapToEntitlement);
  }

  /**
   * Check entitlement for a specific context
   */
  async checkEntitlement(context: EntitlementCheckContext): Promise<Entitlement | null> {
    const now = new Date();

    const entitlement = await prisma.solacenet_entitlement.findFirst({
      where: {
        tenantId: context.tenantId,
        programId: context.programId || null,
        capabilityId: context.capabilityId,
        region: context.region || null,
        channel: context.channel || null,
        effectiveFrom: {
          lte: now,
        },
        OR: [
          { effectiveTo: null },
          { effectiveTo: { gte: now } },
        ],
      },
      include: {
        capability: true,
      },
    });

    if (!entitlement) {
      return null;
    }

    return this.mapToEntitlement(entitlement);
  }

  /**
   * Create an entitlement
   */
  async createEntitlement(request: CreateEntitlementRequest): Promise<Entitlement> {
    // Check if capability exists
    const capability = await prisma.solacenet_capability.findUnique({
      where: { capabilityId: request.capabilityId },
    });

    if (!capability) {
      throw new DbisError(ErrorCode.NOT_FOUND, `Capability ${request.capabilityId} not found`);
    }

    // Check if entitlement already exists
    const existing = await prisma.solacenet_entitlement.findUnique({
      where: {
        // Generated client types require string for nullable composite dims; DB/query accept null.
        tenantId_programId_capabilityId_region_channel: {
          tenantId: request.tenantId,
          programId: request.programId ?? null,
          capabilityId: request.capabilityId,
          region: request.region ?? null,
          channel: request.channel ?? null,
        } as any,
      },
    });

    if (existing) {
      throw new DbisError(
        ErrorCode.VALIDATION_ERROR,
        'Entitlement already exists for this combination'
      );
    }

    const entitlement = await prisma.solacenet_entitlement.create({
      data: {
        id: uuidv4(),
        tenantId: request.tenantId,
        programId: request.programId ?? undefined,
        capabilityId: request.capabilityId,
        region: request.region ?? undefined,
        channel: request.channel ?? undefined,
        stateOverride: request.stateOverride ?? undefined,
        allowlist: (request.allowlist ?? []) as Prisma.InputJsonValue,
        effectiveFrom: request.effectiveFrom || new Date(),
        effectiveTo: request.effectiveTo ?? undefined,
        metadata:
          request.metadata != null ? (request.metadata as Prisma.InputJsonValue) : undefined,
      },
      include: {
        capability: true,
      },
    });

    logger.info(`Entitlement created: ${request.capabilityId} for tenant ${request.tenantId}`);
    return this.mapToEntitlement(entitlement);
  }

  /**
   * Update an entitlement
   */
  async updateEntitlement(
    id: string,
    updates: Partial<CreateEntitlementRequest>
  ): Promise<Entitlement> {
    const entitlement = await prisma.solacenet_entitlement.findUnique({
      where: { id },
    });

    if (!entitlement) {
      throw new DbisError(ErrorCode.NOT_FOUND, `Entitlement ${id} not found`);
    }

    const nextAllowlist = updates.allowlist ?? entitlement.allowlist;
    const nextMetadata = updates.metadata ?? entitlement.metadata;

    const updated = await prisma.solacenet_entitlement.update({
      where: { id },
      data: {
        stateOverride: updates.stateOverride ?? entitlement.stateOverride ?? undefined,
        allowlist: nextAllowlist as Prisma.InputJsonValue,
        effectiveFrom: updates.effectiveFrom || entitlement.effectiveFrom,
        effectiveTo: updates.effectiveTo !== undefined ? updates.effectiveTo : entitlement.effectiveTo,
        metadata:
          nextMetadata != null ? (nextMetadata as Prisma.InputJsonValue) : undefined,
      },
      include: {
        capability: true,
      },
    });

    logger.info(`Entitlement updated: ${id}`);
    return this.mapToEntitlement(updated);
  }

  /**
   * Delete an entitlement
   */
  async deleteEntitlement(id: string): Promise<void> {
    const entitlement = await prisma.solacenet_entitlement.findUnique({
      where: { id },
    });

    if (!entitlement) {
      throw new DbisError(ErrorCode.NOT_FOUND, `Entitlement ${id} not found`);
    }

    await prisma.solacenet_entitlement.delete({
      where: { id },
    });

    logger.info(`Entitlement deleted: ${id}`);
  }

  /**
   * Bulk update entitlements (admin-only)
   */
  async bulkUpdateEntitlements(
    updates: Array<{ id: string; updates: Partial<CreateEntitlementRequest> }>
  ): Promise<Entitlement[]> {
    const results: Entitlement[] = [];

    for (const { id, updates: updateData } of updates) {
      try {
        const updated = await this.updateEntitlement(id, updateData);
        results.push(updated);
      } catch (error) {
        logger.error(`Failed to update entitlement ${id}`, { error });
        throw error;
      }
    }

    return results;
  }

  /**
   * Map Prisma model to Entitlement type
   */
  private mapToEntitlement(entitlement: {
    id: string;
    tenantId: string;
    programId: string | null;
    capabilityId: string;
    region: string | null;
    channel: string | null;
    stateOverride: string | null;
    allowlist: unknown;
    effectiveFrom: Date;
    effectiveTo: Date | null;
    metadata: unknown;
    createdAt: Date;
    updatedAt: Date;
  }): Entitlement {
    return {
      id: entitlement.id,
      tenantId: entitlement.tenantId,
      programId: entitlement.programId || undefined,
      capabilityId: entitlement.capabilityId,
      region: entitlement.region || undefined,
      channel: entitlement.channel || undefined,
      stateOverride: entitlement.stateOverride as CapabilityState | undefined,
      allowlist: (entitlement.allowlist as string[]) || [],
      effectiveFrom: entitlement.effectiveFrom,
      effectiveTo: entitlement.effectiveTo || undefined,
      metadata: entitlement.metadata as Record<string, unknown> | undefined,
      createdAt: entitlement.createdAt,
      updatedAt: entitlement.updatedAt,
    };
  }
}

export const entitlementsService = new EntitlementsService();
