import {
  CanActivate,
  ExecutionContext,
  ForbiddenException,
  Injectable,
  Logger,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { PrismaService } from 'src/prisma/prisma.service';
import { REQUIRE_SUBMODULO_KEY } from '../decorators/require-submodulo.decorator';
import { estadoPrueba, metodoEsLectura } from 'src/common/prueba-submodulo';

/**
 * Guard que verifica acceso a un submódulo:
 *  - Si el submódulo NO es addon, exige que la empresa tenga el módulo padre
 *    en `suscripcion_modulos`.
 *  - Si el submódulo es addon, exige fila activa en `suscripcion_submodulos`.
 *
 * Uso: `@RequireSubmodulo('VEN_POS') @UseGuards(AuthGuard('jwt'), SubmoduloGuard)`.
 * SuperAdmin y empresas holding/reseller bypasean.
 *
 * Además aplica el período de prueba del add-on (ver src/common/prueba-submodulo.ts):
 * vencida la prueba la empresa queda 30 días en solo lectura y después bloqueada.
 * Con `fecha_fin_prueba IS NULL`, que es el caso de todas las empresas hoy, no
 * cambia absolutamente nada.
 */
@Injectable()
export class SubmoduloGuard implements CanActivate {
  private readonly logger = new Logger(SubmoduloGuard.name);

  constructor(
    private reflector: Reflector,
    private prisma: PrismaService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const codigo = this.reflector.getAllAndOverride<string>(REQUIRE_SUBMODULO_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!codigo) return true;

    const request = context.switchToHttp().getRequest();
    const user = request.user;
    if (!user?.id || !user?.empresa_id) {
      throw new ForbiddenException('Usuario no autenticado');
    }

    const [usuario, empresa] = await Promise.all([
      this.prisma.usuario.findUnique({
        where: { id: user.id },
        select: { is_superadmin: true, username: true },
      }),
      this.prisma.empresas.findUnique({
        where: { id: user.empresa_id },
        select: { type: true },
      }),
    ]);

    if (usuario?.is_superadmin) {
      this.logger.log(
        `[SUPERADMIN_BYPASS] usuario=${usuario.username ?? user.id} submodulo=${codigo}`,
      );
      return true;
    }
    if (empresa?.type === 'holding' || empresa?.type === 'reseller') return true;

    const submodulo = await this.prisma.submodulos.findFirst({
      where: { codigo, active: true },
      select: { id: true, modulo_id: true, es_addon: true },
    });
    if (!submodulo) {
      throw new ForbiddenException(`Submódulo "${codigo}" no existe`);
    }

    const suscripcion = await this.prisma.suscripciones.findFirst({
      where: {
        empresa_id: user.empresa_id,
        estado: { in: ['Activo', 'EnGracia'] },
      },
      orderBy: { created_at: 'desc' },
      select: { id: true },
    });
    if (!suscripcion) {
      throw new ForbiddenException('La empresa no tiene una suscripción activa');
    }

    if (submodulo.es_addon) {
      const addon = await this.prisma.suscripcion_submodulos.findFirst({
        where: {
          suscripcion_id: suscripcion.id,
          submodulo_id: submodulo.id,
          active: true,
        },
        select: { id: true, fecha_fin_prueba: true },
      });
      if (!addon) {
        throw new ForbiddenException(
          `El submódulo "${codigo}" requiere un addon no contratado en su plan`,
        );
      }
      this.verificarPrueba(addon.fecha_fin_prueba, codigo, request.method);
      return true;
    }

    const modulo = await this.prisma.suscripcion_modulos.findFirst({
      where: {
        suscripcion_id: suscripcion.id,
        modulo_id: submodulo.modulo_id,
        activo: true,
      },
      select: { id: true },
    });
    if (!modulo) {
      throw new ForbiddenException(
        `El módulo padre del submódulo "${codigo}" no está disponible en su plan actual`,
      );
    }

    // Exclusión per-empresa: la admin puede deshabilitar un submódulo no-addon
    // mediante `suscripcion_submodulos.active=false` (ver Fase 4).
    const exclusion = await this.prisma.suscripcion_submodulos.findFirst({
      where: {
        suscripcion_id: suscripcion.id,
        submodulo_id: submodulo.id,
        active: false,
      },
      select: { id: true },
    });
    if (exclusion) {
      throw new ForbiddenException(
        `El submódulo "${codigo}" está deshabilitado para esta empresa`,
      );
    }

    // Un submódulo no-addon también puede estar en prueba: la empresa lo recibe
    // con el plan pero con fecha de corte hasta que confirme la contratación.
    const enPrueba = await this.prisma.suscripcion_submodulos.findFirst({
      where: {
        suscripcion_id: suscripcion.id,
        submodulo_id: submodulo.id,
        active: true,
        fecha_fin_prueba: { not: null },
      },
      select: { fecha_fin_prueba: true },
    });
    this.verificarPrueba(enPrueba?.fecha_fin_prueba ?? null, codigo, request.method);
    return true;
  }

  /**
   * Corta el acceso según el estado de la prueba. `activo` y `prueba` pasan;
   * `solo_lectura` deja pasar sólo GET/HEAD/OPTIONS para que la empresa siga
   * viendo y exportando sus datos; `bloqueado` no pasa nada.
   */
  private verificarPrueba(fechaFinPrueba: Date | null, codigo: string, metodo: string): void {
    const estado = estadoPrueba(fechaFinPrueba);
    if (estado === 'activo' || estado === 'prueba') return;

    if (estado === 'solo_lectura') {
      if (metodoEsLectura(metodo)) return;
      throw new ForbiddenException(
        `El período de prueba de "${codigo}" venció. El módulo quedó en solo lectura: ` +
          'los datos se conservan, pero para volver a cargar hay que contratarlo.',
      );
    }

    throw new ForbiddenException(
      `El período de prueba de "${codigo}" venció hace más de 30 días y el módulo está bloqueado. ` +
        'Contratalo para recuperar el acceso.',
    );
  }
}
