import { CanActivate, ExecutionContext, Injectable, NotFoundException } 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 contraparte de TenantGuard para rutas SIN JWT (un cliente externo
// aceptando una cotizacion, o consultando el estado de su OT, por un link
// mandado por WhatsApp) — misma necesidad de resolver y adquirir el tenant
// UNA vez por request, pero el tenant no viaja en un token firmado por
// nosotros via Passport, viaja embebido en el propio `:token` de la ruta:
// "<tenantId>.<token original>". Puebla el MISMO TenantContextStorage que
// TenantGuard, con via='publico' — asi TODO el resto del grafo de llamadas
// (CotizacionesService, OtsService, etc.) sigue funcionando SIN saber que
// esta request no tiene usuario logueado, exactamente como ya funciona para
// las rutas autenticadas. Requiere TenantReleaseInterceptor igual que
// TenantGuard, nunca uno sin el otro.
@Injectable()
export class PublicTenantGuard 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 raw = String(request.params?.token ?? "");
    const { tenantId, localToken } = await this.resolveTenantId(raw);

    let dataSource;
    try {
      dataSource = await this.tenantRegistry.acquire(tenantId);
    } catch {
      // Nunca confirmarle a quien intenta adivinar si el tenant existe,
      // esta suspendido, etc. — mismo "Recurso no encontrado" generico que
      // el resto de estas rutas ya usa para token invalido/expirado.
      throw new NotFoundException("Recurso no encontrado");
    }

    // Reescribe el param ANTES de que el handler lo reciba: el resto del
    // codigo (regex de formato, busqueda por publicToken/payload firmado)
    // sigue exactamente igual que antes de existir el prefijo de tenant —
    // nunca vio, ni necesita ver, el tenantId.
    request.params.token = localToken;

    this.tenantContext.populate({
      tenantId,
      // 0 nunca es un id real de `identidades` (autoincrement arranca en 1)
      // — sentinela explicito de "no hay identidad, acceso publico".
      identidadId: 0,
      usuarioLocalId: null,
      rolTenant: null,
      tiendaId: null,
      rolPlataforma: RolPlataforma.NINGUNO,
      via: "publico",
      dataSource,
    });

    return true;
  }

  private async resolveTenantId(raw: string): Promise<{ tenantId: number; localToken: string }> {
    const dot = raw.indexOf(".");
    if (dot > 0) {
      const prefix = raw.slice(0, dot);
      if (/^\d+$/.test(prefix)) {
        return { tenantId: Number(prefix), localToken: raw.slice(dot + 1) };
      }
    }

    // Fallback LEGACY, deuda documentada a proposito: links publicos
    // emitidos ANTES de esta conversion (ventana de un solo tenant) no
    // llevan el prefijo de tenantId. Solo se pueden resolver mientras haya
    // EXACTAMENTE un tenant activo — mismo patron ya usado en
    // AuthService.resolveIdentidadPorLogin() para el puente de username. En
    // cuanto exista un segundo tenant activo, estos links viejos dejan de
    // poder resolverse (ambiguo a cual tenant pertenecen) — hay que
    // reenviarlos antes de onboardear al tenant #2.
    const activos = await this.identityService.getActiveTenants();
    if (activos.length === 1) {
      return { tenantId: activos[0].id, localToken: raw };
    }

    throw new NotFoundException("Recurso no encontrado");
  }
}
