/**
 * TorreService - Servicio de torres/edificios
 *
 * Gestión de torres/edificios verticales dentro de etapas de construcción.
 *
 * @module Construction (MAI-002)
 */

import { Repository } from 'typeorm';
import { Torre } from '../entities/torre.entity';

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

export interface CreateTorreDto {
  etapaId: string;
  code: string;
  name: string;
  totalFloors?: number;
  totalUnits?: number;
  status?: string;
}

export interface UpdateTorreDto {
  name?: string;
  totalFloors?: number;
  totalUnits?: number;
  status?: string;
}

export interface TorreFilters {
  etapaId?: string;
  status?: string;
  search?: string;
  page?: number;
  limit?: number;
}

export class TorreService {
  constructor(
    private readonly repository: Repository<Torre>,
  ) {}

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

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

    if (filters.etapaId) {
      queryBuilder.andWhere('t.etapa_id = :etapaId', { etapaId: filters.etapaId });
    }

    if (filters.status) {
      queryBuilder.andWhere('t.status = :status', { status: filters.status });
    }

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

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

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

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

  async findByCode(ctx: ServiceContext, etapaId: string, code: string): Promise<Torre | null> {
    return this.repository.findOne({
      where: {
        etapaId,
        code,
        tenantId: ctx.tenantId,
      },
    });
  }

  async findByEtapa(ctx: ServiceContext, etapaId: string): Promise<Torre[]> {
    return this.repository
      .createQueryBuilder('t')
      .leftJoinAndSelect('t.niveles', 'niveles', 'niveles.deleted_at IS NULL')
      .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('t.deleted_at IS NULL')
      .andWhere('t.etapa_id = :etapaId', { etapaId })
      .orderBy('t.code', 'ASC')
      .addOrderBy('niveles.floor_number', 'ASC')
      .getMany();
  }

  async create(ctx: ServiceContext, dto: CreateTorreDto): Promise<Torre> {
    const existing = await this.findByCode(ctx, dto.etapaId, dto.code);
    if (existing) {
      throw new Error(`Torre with code ${dto.code} already exists in this etapa`);
    }

    const torre = this.repository.create({
      tenantId: ctx.tenantId,
      createdBy: ctx.userId,
      etapaId: dto.etapaId,
      code: dto.code,
      name: dto.name,
      totalFloors: dto.totalFloors ?? 1,
      totalUnits: dto.totalUnits ?? 0,
      status: dto.status ?? 'draft',
    });

    return this.repository.save(torre);
  }

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

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

    return this.repository.save(torre);
  }

  async updateStatus(ctx: ServiceContext, id: string, status: string): Promise<Torre | null> {
    const torre = await this.findById(ctx, id);
    if (!torre) {
      return null;
    }

    torre.status = status;
    if (ctx.userId) {
      torre.updatedBy = ctx.userId;
    }

    return this.repository.save(torre);
  }

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

    if (!torre) {
      return null;
    }

    const totalUnits = torre.niveles?.reduce((sum, nivel) => sum + nivel.totalUnits, 0) || 0;
    torre.totalUnits = totalUnits;

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

    return this.repository.save(torre);
  }

  async getStatistics(ctx: ServiceContext, etapaId?: string): Promise<{
    totalTorres: number;
    byStatus: Record<string, number>;
    totalFloors: number;
    totalUnits: number;
  }> {
    const queryBuilder = this.repository
      .createQueryBuilder('t')
      .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('t.deleted_at IS NULL');

    if (etapaId) {
      queryBuilder.andWhere('t.etapa_id = :etapaId', { etapaId });
    }

    const torres = await queryBuilder.getMany();

    const byStatus: Record<string, number> = {};
    let totalFloors = 0;
    let totalUnits = 0;

    for (const torre of torres) {
      byStatus[torre.status] = (byStatus[torre.status] || 0) + 1;
      totalFloors += torre.totalFloors;
      totalUnits += torre.totalUnits;
    }

    return {
      totalTorres: torres.length,
      byStatus,
      totalFloors,
      totalUnits,
    };
  }

  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;
  }
}
