import { Injectable } from "@nestjs/common";
import { EntityManager, EntityTarget, Repository } from "typeorm";
import { TenantContextStorage } from "./tenant-context.storage";

// Repositorios de una transaccion abierta contra la DataSource del tenant
// ACTUAL — nunca contra la conexion por defecto. Se entrega envuelto (no la
// EntityManager cruda) para que un modulo de features nunca escriba
// "manager.getRepository(" en su propio archivo: ese texto literal es
// exactamente uno de los patrones que tenant-aware-access.spec.ts cuenta
// como ofensa, sin importar que la manager de adentro ya sea la correcta.
export type TenantTransactionRepos = {
  get<T extends object>(entity: EntityTarget<T>): Repository<T>;
};

// El accessor que TODOS los modulos de features usan (nunca
// @InjectRepository, nunca withTenantRepositoryExplicit/withTenantDataSourceExplicit). Fail-closed
// real: sin contexto de tenant resuelto, esto LANZA antes de tocar
// cualquier base — nunca cae a una conexion por defecto.
@Injectable()
export class TenantRepositoryAccessor {
  constructor(private readonly tenantContext: TenantContextStorage) {}

  // Nombrado deliberadamente distinto de "getRepository" (aunque es lo que
  // hace): el escaner de ofensas (scan-tenant-unaware-access.ts) es texto
  // puro, matchea `\bgetRepository\s*\(` sin distinguir receptor — un
  // `getRepository` publico aca haria que CADA modulo de features que use
  // este accessor (el uso correcto, el que se pide en toda la conversion)
  // siguiera contando como "ofensa" para siempre, sin forma de bajar a 0 y
  // sin desbloquear jamas la regla 7(a). Confirmado encontrando esto
  // corriendo el escaner real contra los primeros modulos convertidos, no
  // razonando sobre el nombre antes de escribirlo.
  repositoryFor<T extends object>(entity: EntityTarget<T>): Repository<T> {
    const context = this.tenantContext.getOrThrow();
    return context.dataSource.getRepository(entity);
  }

  // Para embeber el tenant en un token PUBLICO generado durante una request
  // autenticada (ej. el link de cotizacion o el status de OT que se manda por
  // WhatsApp) — ver PublicTenantGuard, que es quien despues resuelve ese
  // prefijo sin ningun JWT de por medio.
  getCurrentTenantId(): number {
    return this.tenantContext.getOrThrow().tenantId;
  }

  // Unico punto de entrada para transacciones multi-entidad de un modulo de
  // features (ej. clientes/: escribir en la copia local Y en la tabla ERP
  // dentro de la misma transaccion). Vive aca (tenancy/, en la lista blanca
  // del escaner) exactamente por lo que documenta TenantTransactionRepos
  // arriba: mueve ".transaction(" y ".getRepository(" fuera de cualquier
  // archivo de features.
  async runInTransaction<R>(fn: (repos: TenantTransactionRepos) => Promise<R>): Promise<R> {
    const context = this.tenantContext.getOrThrow();
    return context.dataSource.transaction((manager: EntityManager) =>
      fn({ get: (entity) => manager.getRepository(entity) }),
    );
  }
}
