/**
 * NivelService - Servicio de niveles/pisos
 *
 * Gestión de niveles/pisos dentro de torres de construcción.
 *
 * @module Construction (MAI-002)
 */

import { Repository } from 'typeorm';
import { Nivel } from '../entities/nivel.entity';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface CreateNivelDto {
  torreId: string;
  floorNumber: number;
  name?: string;
  totalUnits?: number;
}

export interface UpdateNivelDto {
  name?: string;
  totalUnits?: number;
}

export interface NivelFilters {
  torreId?: string;
  search?: string;
  page?: number;
  limit?: number;
}

export class NivelService {
  constructor(
    private readonly repository: Repository<Nivel>,
  ) {}

  async findAll(
    ctx: ServiceContext,
    filters: NivelFilters = {},
  ): Promise<{ data: Nivel[]; total: number; page: number; limit: number }> {
    const page = filters.page || 1;
    const limit = filters.limit || 50;
    const skip = (page - 1) * limit;

    const queryBuilder = this.repository
      .createQueryBuilder('n')
      .leftJoinAndSelect('n.torre', 'torre')
      .where('n.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('n.deleted_at IS NULL');

    if (filters.torreId) {
      queryBuilder.andWhere('n.torre_id = :torreId', { torreId: filters.torreId });
    }

    if (filters.search) {
      queryBuilder.andWhere('n.name ILIKE :search', { search: `%${filters.search}%` });
    }

    const [data, total] = await queryBuilder
      .orderBy('n.floor_number', 'ASC')
      .skip(skip)
      .take(limit)
      .getManyAndCount();

    return { data, total, page, limit };
  }

  async findById(ctx: ServiceContext, id: string): Promise<Nivel | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
      },
      relations: ['torre', 'departamentos'],
    });
  }

  async findByFloorNumber(ctx: ServiceContext, torreId: string, floorNumber: number): Promise<Nivel | null> {
    return this.repository.findOne({
      where: {
        torreId,
        floorNumber,
        tenantId: ctx.tenantId,
      },
    });
  }

  async findByTorre(ctx: ServiceContext, torreId: string): Promise<Nivel[]> {
    return this.repository
      .createQueryBuilder('n')
      .leftJoinAndSelect('n.departamentos', 'departamentos', 'departamentos.deleted_at IS NULL')
      .where('n.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('n.deleted_at IS NULL')
      .andWhere('n.torre_id = :torreId', { torreId })
      .orderBy('n.floor_number', 'ASC')
      .addOrderBy('departamentos.code', 'ASC')
      .getMany();
  }

  async create(ctx: ServiceContext, dto: CreateNivelDto): Promise<Nivel> {
    const existing = await this.findByFloorNumber(ctx, dto.torreId, dto.floorNumber);
    if (existing) {
      throw new Error(`Floor ${dto.floorNumber} already exists in this torre`);
    }

    const nivel = this.repository.create({
      tenantId: ctx.tenantId,
      createdBy: ctx.userId,
      torreId: dto.torreId,
      floorNumber: dto.floorNumber,
      name: dto.name || `Nivel ${dto.floorNumber}`,
      totalUnits: dto.totalUnits ?? 0,
    });

    return this.repository.save(nivel);
  }

  async createBulk(ctx: ServiceContext, torreId: string, totalFloors: number): Promise<Nivel[]> {
    const niveles: Nivel[] = [];

    for (let floor = 1; floor <= totalFloors; floor++) {
      const existing = await this.findByFloorNumber(ctx, torreId, floor);
      if (!existing) {
        const nivel = this.repository.create({
          tenantId: ctx.tenantId,
          createdBy: ctx.userId,
          torreId,
          floorNumber: floor,
          name: `Nivel ${floor}`,
          totalUnits: 0,
        });
        niveles.push(nivel);
      }
    }

    if (niveles.length > 0) {
      return this.repository.save(niveles);
    }

    return [];
  }

  async update(ctx: ServiceContext, id: string, dto: UpdateNivelDto): Promise<Nivel | null> {
    const nivel = await this.findById(ctx, id);
    if (!nivel) {
      return null;
    }

    Object.assign(nivel, {
      ...dto,
      updatedBy: ctx.userId,
    });

    return this.repository.save(nivel);
  }

  async updateUnitCount(ctx: ServiceContext, id: string): Promise<Nivel | null> {
    const nivel = await this.repository.findOne({
      where: { id, tenantId: ctx.tenantId },
      relations: ['departamentos'],
    });

    if (!nivel) {
      return null;
    }

    const activeUnits = nivel.departamentos?.filter(d => !d.deletedAt) || [];
    nivel.totalUnits = activeUnits.length;

    if (ctx.userId) {
      nivel.updatedBy = ctx.userId;
    }

    return this.repository.save(nivel);
  }

  async getStatistics(ctx: ServiceContext, torreId?: string): Promise<{
    totalNiveles: number;
    totalUnits: number;
    averageUnitsPerFloor: number;
  }> {
    const queryBuilder = this.repository
      .createQueryBuilder('n')
      .where('n.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('n.deleted_at IS NULL');

    if (torreId) {
      queryBuilder.andWhere('n.torre_id = :torreId', { torreId });
    }

    const niveles = await queryBuilder.getMany();

    const totalUnits = niveles.reduce((sum, n) => sum + n.totalUnits, 0);

    return {
      totalNiveles: niveles.length,
      totalUnits,
      averageUnitsPerFloor: niveles.length > 0 ? totalUnits / niveles.length : 0,
    };
  }

  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.update(
      { id, tenantId: ctx.tenantId },
      { deletedAt: new Date(), deletedBy: ctx.userId },
    );
    return (result.affected ?? 0) > 0;
  }
}
