/**
 * AlmacenTemporalService - Servicio para gestión de almacenes temporales de residuos
 *
 * RF-MAA017-006: Gestión de almacenes temporales con control de capacidad,
 * inspecciones y cumplimiento normativo.
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { AlmacenTemporal, EstadoAlmacen } from '../entities/almacen-temporal.entity';
import { ResiduoGeneracion } from '../entities/residuo-generacion.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface AlmacenFilters {
  fraccionamientoId?: string;
  estado?: EstadoAlmacen;
  tieneContencion?: boolean;
  tieneTecho?: boolean;
  search?: string;
}

export interface CreateAlmacenDto {
  fraccionamientoId: string;
  nombre: string;
  ubicacion?: string;
  capacidadM3?: number;
  tieneContencion?: boolean;
  tieneTecho?: boolean;
  senalizacionOk?: boolean;
}

export interface UpdateAlmacenDto {
  nombre?: string;
  ubicacion?: string;
  capacidadM3?: number;
  tieneContencion?: boolean;
  tieneTecho?: boolean;
  senalizacionOk?: boolean;
  estado?: EstadoAlmacen;
}

export interface InspeccionAlmacenDto {
  tieneContencion: boolean;
  tieneTecho: boolean;
  senalizacionOk: boolean;
  observaciones?: string;
}

export interface AlmacenConOcupacion extends AlmacenTemporal {
  ocupacionActual: number;
  porcentajeOcupacion: number;
  residuosAlmacenados: number;
}

export interface AlmacenStats {
  totalAlmacenes: number;
  almacenesOperativos: number;
  almacenesLlenos: number;
  almacenesMantenimiento: number;
  capacidadTotalM3: number;
  ocupacionTotalM3: number;
  porcentajeOcupacionGlobal: number;
  almacenesSinContencion: number;
  almacenesSinTecho: number;
  almacenesSinSenalizacion: number;
}

export interface AlertaAlmacen {
  almacenId: string;
  nombre: string;
  tipo: 'capacidad' | 'infraestructura' | 'normativo';
  mensaje: string;
  nivel: 'advertencia' | 'critico';
}

export class AlmacenTemporalService {
  constructor(
    private readonly almacenRepository: Repository<AlmacenTemporal>,
    private readonly generacionRepository: Repository<ResiduoGeneracion>
  ) {}

  // ========== CRUD de Almacenes ==========
  async findAll(
    ctx: ServiceContext,
    filters: AlmacenFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<AlmacenTemporal>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.almacenRepository
      .createQueryBuilder('a')
      .leftJoinAndSelect('a.fraccionamiento', 'fraccionamiento')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.fraccionamientoId) {
      queryBuilder.andWhere('a.fraccionamiento_id = :fraccionamientoId', {
        fraccionamientoId: filters.fraccionamientoId,
      });
    }

    if (filters.estado) {
      queryBuilder.andWhere('a.estado = :estado', { estado: filters.estado });
    }

    if (filters.tieneContencion !== undefined) {
      queryBuilder.andWhere('a.tiene_contencion = :tieneContencion', {
        tieneContencion: filters.tieneContencion,
      });
    }

    if (filters.tieneTecho !== undefined) {
      queryBuilder.andWhere('a.tiene_techo = :tieneTecho', {
        tieneTecho: filters.tieneTecho,
      });
    }

    if (filters.search) {
      queryBuilder.andWhere(
        '(a.nombre ILIKE :search OR a.ubicacion ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    queryBuilder
      .orderBy('a.nombre', 'ASC')
      .skip(skip)
      .take(limit);

    const [data, total] = await queryBuilder.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  async findById(ctx: ServiceContext, id: string): Promise<AlmacenTemporal | null> {
    return this.almacenRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<AlmacenTemporal>,
      relations: ['fraccionamiento'],
    });
  }

  async findByFraccionamiento(
    ctx: ServiceContext,
    fraccionamientoId: string
  ): Promise<AlmacenTemporal[]> {
    return this.almacenRepository.find({
      where: {
        tenantId: ctx.tenantId,
        fraccionamientoId,
      } as FindOptionsWhere<AlmacenTemporal>,
      order: { nombre: 'ASC' },
    });
  }

  async create(ctx: ServiceContext, dto: CreateAlmacenDto): Promise<AlmacenTemporal> {
    const almacen = this.almacenRepository.create({
      tenantId: ctx.tenantId,
      fraccionamientoId: dto.fraccionamientoId,
      nombre: dto.nombre,
      ubicacion: dto.ubicacion,
      capacidadM3: dto.capacidadM3,
      tieneContencion: dto.tieneContencion ?? true,
      tieneTecho: dto.tieneTecho ?? true,
      senalizacionOk: dto.senalizacionOk ?? true,
      estado: 'operativo',
    });

    return this.almacenRepository.save(almacen);
  }

  async update(
    ctx: ServiceContext,
    id: string,
    dto: UpdateAlmacenDto
  ): Promise<AlmacenTemporal | null> {
    const almacen = await this.findById(ctx, id);
    if (!almacen) return null;

    if (dto.nombre !== undefined) almacen.nombre = dto.nombre;
    if (dto.ubicacion !== undefined) almacen.ubicacion = dto.ubicacion;
    if (dto.capacidadM3 !== undefined) almacen.capacidadM3 = dto.capacidadM3;
    if (dto.tieneContencion !== undefined) almacen.tieneContencion = dto.tieneContencion;
    if (dto.tieneTecho !== undefined) almacen.tieneTecho = dto.tieneTecho;
    if (dto.senalizacionOk !== undefined) almacen.senalizacionOk = dto.senalizacionOk;
    if (dto.estado !== undefined) almacen.estado = dto.estado;

    return this.almacenRepository.save(almacen);
  }

  async updateEstado(
    ctx: ServiceContext,
    id: string,
    estado: EstadoAlmacen
  ): Promise<AlmacenTemporal | null> {
    const almacen = await this.findById(ctx, id);
    if (!almacen) return null;

    almacen.estado = estado;
    return this.almacenRepository.save(almacen);
  }

  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    // Check if there are residuos stored
    const residuosCount = await this.generacionRepository.count({
      where: {
        tenantId: ctx.tenantId,
        contenedorId: id,
        estado: 'almacenado',
      } as FindOptionsWhere<ResiduoGeneracion>,
    });

    if (residuosCount > 0) {
      throw new Error('No se puede eliminar un almacen con residuos almacenados');
    }

    const result = await this.almacenRepository.delete({
      id,
      tenantId: ctx.tenantId,
    } as FindOptionsWhere<AlmacenTemporal>);

    return (result.affected ?? 0) > 0;
  }

  // ========== Inspección de Almacén ==========
  async registrarInspeccion(
    ctx: ServiceContext,
    id: string,
    dto: InspeccionAlmacenDto
  ): Promise<AlmacenTemporal | null> {
    const almacen = await this.findById(ctx, id);
    if (!almacen) return null;

    almacen.tieneContencion = dto.tieneContencion;
    almacen.tieneTecho = dto.tieneTecho;
    almacen.senalizacionOk = dto.senalizacionOk;

    // Auto-update estado based on inspection
    if (!dto.tieneContencion || !dto.tieneTecho || !dto.senalizacionOk) {
      almacen.estado = 'mantenimiento';
    }

    return this.almacenRepository.save(almacen);
  }

  // ========== Ocupación y Capacidad ==========
  async getAlmacenConOcupacion(
    ctx: ServiceContext,
    id: string
  ): Promise<AlmacenConOcupacion | null> {
    const almacen = await this.findById(ctx, id);
    if (!almacen) return null;

    const ocupacion = await this.calcularOcupacion(ctx, id);

    return {
      ...almacen,
      ocupacionActual: ocupacion.volumenM3,
      porcentajeOcupacion: ocupacion.porcentaje,
      residuosAlmacenados: ocupacion.cantidadResiduos,
    };
  }

  async findAllConOcupacion(
    ctx: ServiceContext,
    fraccionamientoId?: string
  ): Promise<AlmacenConOcupacion[]> {
    const filters: AlmacenFilters = {};
    if (fraccionamientoId) filters.fraccionamientoId = fraccionamientoId;

    const result = await this.findAll(ctx, filters, 1, 1000);
    const almacenes = result.data;

    const almacenesConOcupacion: AlmacenConOcupacion[] = [];

    for (const almacen of almacenes) {
      const ocupacion = await this.calcularOcupacion(ctx, almacen.id);
      almacenesConOcupacion.push({
        ...almacen,
        ocupacionActual: ocupacion.volumenM3,
        porcentajeOcupacion: ocupacion.porcentaje,
        residuosAlmacenados: ocupacion.cantidadResiduos,
      });
    }

    return almacenesConOcupacion;
  }

  private async calcularOcupacion(
    ctx: ServiceContext,
    almacenId: string
  ): Promise<{ volumenM3: number; porcentaje: number; cantidadResiduos: number }> {
    const almacen = await this.almacenRepository.findOne({
      where: { id: almacenId, tenantId: ctx.tenantId } as FindOptionsWhere<AlmacenTemporal>,
    });

    if (!almacen) {
      return { volumenM3: 0, porcentaje: 0, cantidadResiduos: 0 };
    }

    // Get all residuos stored in this almacen
    const residuos = await this.generacionRepository
      .createQueryBuilder('g')
      .where('g.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('g.contenedor_id = :contenedorId', { contenedorId: almacenId })
      .andWhere('g.estado = :estado', { estado: 'almacenado' })
      .select('g.unidad', 'unidad')
      .addSelect('SUM(g.cantidad)', 'total')
      .groupBy('g.unidad')
      .getRawMany();

    // Convert to m3 (approximate conversions)
    let volumenM3 = 0;
    for (const r of residuos) {
      const cantidad = parseFloat(r.total || '0');
      switch (r.unidad) {
        case 'm3':
          volumenM3 += cantidad;
          break;
        case 'litros':
          volumenM3 += cantidad / 1000;
          break;
        case 'kg':
          // Approximate: 1 kg = 0.001 m3 (depends on density)
          volumenM3 += cantidad / 1000;
          break;
        case 'piezas':
          // Approximate: 1 piece = 0.01 m3
          volumenM3 += cantidad * 0.01;
          break;
      }
    }

    const cantidadResiduos = await this.generacionRepository.count({
      where: {
        tenantId: ctx.tenantId,
        contenedorId: almacenId,
        estado: 'almacenado',
      } as FindOptionsWhere<ResiduoGeneracion>,
    });

    const capacidad = almacen.capacidadM3 || 1;
    const porcentaje = Math.min(100, Math.round((volumenM3 / capacidad) * 100));

    return { volumenM3, porcentaje, cantidadResiduos };
  }

  // ========== Alertas ==========
  async getAlertas(ctx: ServiceContext, fraccionamientoId?: string): Promise<AlertaAlmacen[]> {
    const almacenes = await this.findAllConOcupacion(ctx, fraccionamientoId);
    const alertas: AlertaAlmacen[] = [];

    for (const almacen of almacenes) {
      // Alerta de capacidad
      if (almacen.porcentajeOcupacion >= 90) {
        alertas.push({
          almacenId: almacen.id,
          nombre: almacen.nombre,
          tipo: 'capacidad',
          mensaje: `Almacen al ${almacen.porcentajeOcupacion}% de capacidad`,
          nivel: almacen.porcentajeOcupacion >= 95 ? 'critico' : 'advertencia',
        });
      }

      // Alertas de infraestructura
      if (!almacen.tieneContencion) {
        alertas.push({
          almacenId: almacen.id,
          nombre: almacen.nombre,
          tipo: 'infraestructura',
          mensaje: 'Almacen sin sistema de contencion',
          nivel: 'critico',
        });
      }

      if (!almacen.tieneTecho) {
        alertas.push({
          almacenId: almacen.id,
          nombre: almacen.nombre,
          tipo: 'infraestructura',
          mensaje: 'Almacen sin techo de proteccion',
          nivel: 'advertencia',
        });
      }

      // Alertas normativas
      if (!almacen.senalizacionOk) {
        alertas.push({
          almacenId: almacen.id,
          nombre: almacen.nombre,
          tipo: 'normativo',
          mensaje: 'Senalizacion incompleta o danada',
          nivel: 'advertencia',
        });
      }

      if (almacen.estado === 'mantenimiento') {
        alertas.push({
          almacenId: almacen.id,
          nombre: almacen.nombre,
          tipo: 'infraestructura',
          mensaje: 'Almacen en mantenimiento',
          nivel: 'advertencia',
        });
      }
    }

    return alertas;
  }

  // ========== Estadísticas ==========
  async getStats(ctx: ServiceContext, fraccionamientoId?: string): Promise<AlmacenStats> {
    const baseQuery = () => {
      const qb = this.almacenRepository
        .createQueryBuilder('a')
        .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId });

      if (fraccionamientoId) {
        qb.andWhere('a.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
      }
      return qb;
    };

    const totalAlmacenes = await baseQuery().getCount();

    const almacenesOperativos = await baseQuery()
      .andWhere('a.estado = :estado', { estado: 'operativo' })
      .getCount();

    const almacenesLlenos = await baseQuery()
      .andWhere('a.estado = :estado', { estado: 'lleno' })
      .getCount();

    const almacenesMantenimiento = await baseQuery()
      .andWhere('a.estado = :estado', { estado: 'mantenimiento' })
      .getCount();

    const capacidadResult = await baseQuery()
      .select('SUM(a.capacidad_m3)', 'total')
      .getRawOne();
    const capacidadTotalM3 = parseFloat(capacidadResult?.total || '0');

    // Calculate total occupation
    const almacenes = await this.findAllConOcupacion(ctx, fraccionamientoId);
    const ocupacionTotalM3 = almacenes.reduce((sum, a) => sum + a.ocupacionActual, 0);
    const porcentajeOcupacionGlobal = capacidadTotalM3 > 0
      ? Math.round((ocupacionTotalM3 / capacidadTotalM3) * 100)
      : 0;

    const almacenesSinContencion = await baseQuery()
      .andWhere('a.tiene_contencion = false')
      .getCount();

    const almacenesSinTecho = await baseQuery()
      .andWhere('a.tiene_techo = false')
      .getCount();

    const almacenesSinSenalizacion = await baseQuery()
      .andWhere('a.senalizacion_ok = false')
      .getCount();

    return {
      totalAlmacenes,
      almacenesOperativos,
      almacenesLlenos,
      almacenesMantenimiento,
      capacidadTotalM3,
      ocupacionTotalM3,
      porcentajeOcupacionGlobal,
      almacenesSinContencion,
      almacenesSinTecho,
      almacenesSinSenalizacion,
    };
  }
}
