/**
 * EtapaService - Gestión de etapas de fraccionamientos
 *
 * CRUD de etapas con soporte multi-tenant.
 *
 * @module Construction
 */

import { Repository, IsNull } from 'typeorm';
import { Etapa } from '../entities/etapa.entity';

export interface CreateEtapaDto {
  fraccionamientoId: string;
  code: string;
  name: string;
  description?: string;
  sequence?: number;
  totalLots?: number;
  status?: string;
  startDate?: Date;
  expectedEndDate?: Date;
}

export interface UpdateEtapaDto extends Partial<CreateEtapaDto> {
  actualEndDate?: Date;
}

export interface EtapaListOptions {
  tenantId: string;
  fraccionamientoId?: string;
  page?: number;
  limit?: number;
  search?: string;
  status?: string;
}

export class EtapaService {
  constructor(private readonly repository: Repository<Etapa>) {}

  /**
   * Listar etapas
   */
  async findAll(options: EtapaListOptions): Promise<{ items: Etapa[]; total: number }> {
    const { tenantId, fraccionamientoId, page = 1, limit = 20, search, status } = options;

    const query = this.repository
      .createQueryBuilder('e')
      .where('e.tenant_id = :tenantId', { tenantId })
      .andWhere('e.deleted_at IS NULL');

    if (fraccionamientoId) {
      query.andWhere('e.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
    }

    if (search) {
      query.andWhere('(e.code ILIKE :search OR e.name ILIKE :search)', { search: `%${search}%` });
    }

    if (status) {
      query.andWhere('e.status = :status', { status });
    }

    const total = await query.getCount();
    const items = await query
      .skip((page - 1) * limit)
      .take(limit)
      .orderBy('e.sequence', 'ASC')
      .addOrderBy('e.name', 'ASC')
      .getMany();

    return { items, total };
  }

  /**
   * Obtener etapa por ID
   */
  async findById(id: string, tenantId: string): Promise<Etapa | null> {
    return this.repository.findOne({
      where: { id, tenantId, deletedAt: IsNull() } as any,
      relations: ['manzanas'],
    });
  }

  /**
   * Obtener etapa por código dentro de un fraccionamiento
   */
  async findByCode(code: string, fraccionamientoId: string, tenantId: string): Promise<Etapa | null> {
    return this.repository.findOne({
      where: { code, fraccionamientoId, tenantId, deletedAt: IsNull() } as any,
    });
  }

  /**
   * Crear etapa
   */
  async create(tenantId: string, dto: CreateEtapaDto, createdBy?: string): Promise<Etapa> {
    const existing = await this.findByCode(dto.code, dto.fraccionamientoId, tenantId);
    if (existing) {
      throw new Error('Stage code already exists in this fraccionamiento');
    }

    return this.repository.save(
      this.repository.create({
        tenantId,
        ...dto,
        createdBy,
        status: dto.status || 'draft',
      })
    );
  }

  /**
   * Actualizar etapa
   */
  async update(id: string, tenantId: string, dto: UpdateEtapaDto, updatedBy?: string): Promise<Etapa> {
    const etapa = await this.findById(id, tenantId);
    if (!etapa) {
      throw new Error('Stage not found');
    }

    // Verificar código único si se está cambiando
    if (dto.code && dto.code !== etapa.code) {
      const existing = await this.findByCode(dto.code, etapa.fraccionamientoId, tenantId);
      if (existing) {
        throw new Error('Stage code already exists in this fraccionamiento');
      }
    }

    await this.repository.update(id, {
      ...dto,
      updatedBy,
      updatedAt: new Date(),
    });

    return this.findById(id, tenantId) as Promise<Etapa>;
  }

  /**
   * Eliminar etapa (soft delete)
   */
  async delete(id: string, tenantId: string, _deletedBy?: string): Promise<void> {
    const etapa = await this.findById(id, tenantId);
    if (!etapa) {
      throw new Error('Stage not found');
    }

    // TODO: Verificar si tiene manzanas antes de eliminar

    await this.repository.update(id, {
      deletedAt: new Date(),
    });
  }

  /**
   * Obtener etapas por fraccionamiento
   */
  async findByFraccionamiento(fraccionamientoId: string, tenantId: string): Promise<Etapa[]> {
    return this.repository.find({
      where: { fraccionamientoId, tenantId, deletedAt: IsNull() } as any,
      order: { sequence: 'ASC' },
    });
  }
}
