import { Repository, FindOptionsWhere, DeepPartial } from 'typeorm';
import { NotFoundException } from '@nestjs/common';

/**
 * Base service para todos los servicios multi-tenant.
 * Proporciona operaciones CRUD con filtrado automatico por tenant.
 *
 * @example
 * @Injectable()
 * export class ClientService extends TenantAwareService<Client> {
 *   constructor(
 *     @InjectRepository(Client)
 *     private readonly clientRepository: Repository<Client>,
 *   ) {
 *     super(clientRepository, 'Client');
 *   }
 * }
 */
export abstract class TenantAwareService<T extends { tenant_id: string }> {
  constructor(
    protected readonly repository: Repository<T>,
    protected readonly entityName: string = 'Entity',
  ) {}

  /**
   * Obtiene todos los registros del tenant
   */
  async findAllByTenant(tenantId: string): Promise<T[]> {
    return this.repository.find({
      where: { tenant_id: tenantId } as FindOptionsWhere<T>,
    });
  }

  /**
   * Obtiene un registro por ID dentro del tenant
   */
  async findOneByTenant(tenantId: string, id: string): Promise<T | null> {
    return this.repository.findOne({
      where: { id, tenant_id: tenantId } as unknown as FindOptionsWhere<T>,
    });
  }

  /**
   * Obtiene un registro o lanza NotFoundException
   */
  async findOneOrFail(tenantId: string, id: string): Promise<T> {
    const entity = await this.findOneByTenant(tenantId, id);
    if (!entity) {
      throw new NotFoundException(`${this.entityName} not found`);
    }
    return entity;
  }

  /**
   * Crea un nuevo registro para el tenant
   */
  async createForTenant(tenantId: string, data: DeepPartial<T>): Promise<T> {
    const entity = this.repository.create({
      ...data,
      tenant_id: tenantId,
    } as DeepPartial<T>);
    return this.repository.save(entity);
  }

  /**
   * Actualiza un registro del tenant
   */
  async updateForTenant(
    tenantId: string,
    id: string,
    data: DeepPartial<T>,
  ): Promise<T> {
    const entity = await this.findOneOrFail(tenantId, id);
    Object.assign(entity, data);
    return this.repository.save(entity);
  }

  /**
   * Elimina (soft delete) un registro del tenant
   */
  async removeForTenant(tenantId: string, id: string): Promise<void> {
    await this.findOneOrFail(tenantId, id);
    await this.repository.softDelete({
      id,
      tenant_id: tenantId,
    } as unknown as FindOptionsWhere<T>);
  }

  /**
   * Cuenta registros del tenant
   */
  async countByTenant(tenantId: string): Promise<number> {
    return this.repository.count({
      where: { tenant_id: tenantId } as FindOptionsWhere<T>,
    });
  }
}
