import {
  CanActivate,
  ExecutionContext,
  ForbiddenException,
  Injectable,
  UnauthorizedException,
} from "@nestjs/common";
import { RolPlataforma } from "../control-plane/entities/identidad.entity";
import { IdentityService } from "../control-plane/identity.service";
import { TenantConnectionRegistry } from "./tenant-connection-registry.service";
import { TenantContextStorage } from "./tenant-context.storage";

// La pieza que faltaba entre "el JWT trae tenantId" y "el modulo de negocio
// puede leer/escribir la base de ESE tenant": corre DESPUES de JwtAuthGuard
// (necesita req.user ya poblado por JwtStrategy), UNA vez por request.
//
// Vuelve a chequear la membresia contra la base real (nunca confia solo en
// que el payload del token diga tenantId) — asi una membresia revocada
// despues de emitido el token deja de poder operar en cuanto se re-chequea
// aca, sin esperar a que expire el access token. Mismo trade-off de TTL
// corto ya elegido en JwtStrategy (ver el comentario ahi): esto NO reemplaza
// esa decision, la complementa donde SI hay una conexion de tenant de por
// medio de todos modos (adquirirla es obligatorio para que el handler pueda
// hacer cualquier cosa) — no es una llamada extra que antes no existiera.
//
// Adquiere la conexion (TenantConnectionRegistry.acquire) y puebla el mismo
// objeto que TenancyMiddleware abrio vacio (ver tenant-context.storage.ts
// para el porque de ese diseño en dos pasos). Liberar esa conexion NO es
// responsabilidad de este guard — canActivate() devuelve antes de que el
// handler corra — la libera SIEMPRE TenantReleaseInterceptor, que tiene que
// registrarse junto a este guard en cada controller (nunca uno sin el otro).
@Injectable()
export class TenantGuard implements CanActivate {
  constructor(
    private readonly tenantRegistry: TenantConnectionRegistry,
    private readonly tenantContext: TenantContextStorage,
    private readonly identityService: IdentityService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const user = request.user;

    if (!user || user.tenantId == null) {
      throw new UnauthorizedException("No hay tenant resuelto para este acceso.");
    }

    if (user.via === "plataforma") {
      // Acceso de plataforma: no hay tenant_membership que chequear (por
      // diseño, ver AuthService.resolveTenantAccess) — la unica garantia
      // releible en vivo es rolPlataforma.
      if (user.rolPlataforma !== RolPlataforma.ADMIN_PLATAFORMA) {
        throw new ForbiddenException("El acceso de plataforma de esta identidad fue revocado.");
      }
    } else {
      const membership = await this.identityService.findActiveMembershipByUsuarioLocal(
        user.tenantId,
        user.usuarioLocalId,
      );
      if (!membership) {
        throw new UnauthorizedException(
          "La membresia a este tenant ya no esta activa — inicia sesion de nuevo.",
        );
      }
    }

    const dataSource = await this.tenantRegistry.acquire(user.tenantId);

    this.tenantContext.populate({
      tenantId: user.tenantId,
      identidadId: user.identidadId,
      usuarioLocalId: user.usuarioLocalId,
      rolTenant: user.rol ?? null,
      tiendaId: user.tiendaId ?? null,
      rolPlataforma: user.rolPlataforma,
      via: user.via,
      dataSource,
    });

    return true;
  }
}
