/**
 * LoteService - Gestión de lotes/terrenos
 *
 * CRUD de lotes con soporte multi-tenant.
 *
 * @module Construction
 */

import { Repository, IsNull } from 'typeorm';
import { Lote } from '../entities/lote.entity';

export interface CreateLoteDto {
  manzanaId: string;
  prototipoId?: string;
  code: string;
  officialNumber?: string;
  areaM2?: number;
  frontM?: number;
  depthM?: number;
  status?: string;
  priceBase?: number;
  priceFinal?: number;
}

export interface UpdateLoteDto extends Partial<CreateLoteDto> {
  buyerId?: string;
  saleDate?: Date;
  deliveryDate?: Date;
}

export interface LoteListOptions {
  tenantId: string;
  manzanaId?: string;
  prototipoId?: string;
  page?: number;
  limit?: number;
  search?: string;
  status?: string;
}

export class LoteService {
  constructor(private readonly repository: Repository<Lote>) {}

  /**
   * Listar lotes
   */
  async findAll(options: LoteListOptions): Promise<{ items: Lote[]; total: number }> {
    const { tenantId, manzanaId, prototipoId, page = 1, limit = 20, search, status } = options;

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

    if (manzanaId) {
      query.andWhere('l.manzana_id = :manzanaId', { manzanaId });
    }

    if (prototipoId) {
      query.andWhere('l.prototipo_id = :prototipoId', { prototipoId });
    }

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

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

    const total = await query.getCount();
    const items = await query
      .leftJoinAndSelect('l.prototipo', 'prototipo')
      .skip((page - 1) * limit)
      .take(limit)
      .orderBy('l.code', 'ASC')
      .getMany();

    return { items, total };
  }

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

  /**
   * Obtener lote por código dentro de una manzana
   */
  async findByCode(code: string, manzanaId: string, tenantId: string): Promise<Lote | null> {
    return this.repository.findOne({
      where: { code, manzanaId, tenantId, deletedAt: IsNull() } as any,
    });
  }

  /**
   * Crear lote
   */
  async create(tenantId: string, dto: CreateLoteDto, createdBy?: string): Promise<Lote> {
    const existing = await this.findByCode(dto.code, dto.manzanaId, tenantId);
    if (existing) {
      throw new Error('Lot code already exists in this block');
    }

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

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

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

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

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

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

    // Verificar que no esté vendido
    if (lote.status === 'sold') {
      throw new Error('Cannot delete a sold lot');
    }

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

  /**
   * Obtener lotes por manzana
   */
  async findByManzana(manzanaId: string, tenantId: string): Promise<Lote[]> {
    return this.repository.find({
      where: { manzanaId, tenantId, deletedAt: IsNull() } as any,
      order: { code: 'ASC' },
      relations: ['prototipo'],
    });
  }

  /**
   * Asignar prototipo a lote
   */
  async assignPrototipo(id: string, tenantId: string, prototipoId: string, updatedBy?: string): Promise<Lote> {
    const lote = await this.findById(id, tenantId);
    if (!lote) {
      throw new Error('Lot not found');
    }

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

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

  /**
   * Cambiar estado del lote
   */
  async changeStatus(id: string, tenantId: string, status: string, updatedBy?: string): Promise<Lote> {
    const lote = await this.findById(id, tenantId);
    if (!lote) {
      throw new Error('Lot not found');
    }

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

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

  /**
   * Obtener estadísticas de lotes por estado
   */
  async getStatsByStatus(tenantId: string, manzanaId?: string): Promise<{ status: string; count: number }[]> {
    const query = this.repository
      .createQueryBuilder('l')
      .select('l.status', 'status')
      .addSelect('COUNT(*)', 'count')
      .where('l.tenant_id = :tenantId', { tenantId })
      .andWhere('l.deleted_at IS NULL')
      .groupBy('l.status');

    if (manzanaId) {
      query.andWhere('l.manzana_id = :manzanaId', { manzanaId });
    }

    return query.getRawMany();
  }
}
