/**
 * DepartamentoService - Servicio de departamentos/unidades
 *
 * Gestión de departamentos/unidades dentro de niveles de construcción vertical.
 *
 * @module Construction (MAI-002)
 */

import { Repository } from 'typeorm';
import { Departamento } from '../entities/departamento.entity';

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

export interface CreateDepartamentoDto {
  nivelId: string;
  prototipoId?: string;
  code: string;
  unitNumber: string;
  areaM2?: number;
  status?: string;
  priceBase?: number;
  priceFinal?: number;
}

export interface UpdateDepartamentoDto {
  prototipoId?: string;
  unitNumber?: string;
  areaM2?: number;
  status?: string;
  priceBase?: number;
  priceFinal?: number;
}

export interface SellDepartamentoDto {
  buyerId: string;
  priceFinal: number;
  saleDate?: Date;
  deliveryDate?: Date;
}

export interface DepartamentoFilters {
  nivelId?: string;
  torreId?: string;
  prototipoId?: string;
  status?: string;
  minPrice?: number;
  maxPrice?: number;
  search?: string;
  page?: number;
  limit?: number;
}

export class DepartamentoService {
  constructor(
    private readonly repository: Repository<Departamento>,
  ) {}

  async findAll(
    ctx: ServiceContext,
    filters: DepartamentoFilters = {},
  ): Promise<{ data: Departamento[]; 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('d')
      .leftJoinAndSelect('d.nivel', 'nivel')
      .leftJoinAndSelect('nivel.torre', 'torre')
      .leftJoinAndSelect('d.prototipo', 'prototipo')
      .where('d.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('d.deleted_at IS NULL');

    if (filters.nivelId) {
      queryBuilder.andWhere('d.nivel_id = :nivelId', { nivelId: filters.nivelId });
    }

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

    if (filters.prototipoId) {
      queryBuilder.andWhere('d.prototipo_id = :prototipoId', { prototipoId: filters.prototipoId });
    }

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

    if (filters.minPrice !== undefined) {
      queryBuilder.andWhere('d.price_final >= :minPrice', { minPrice: filters.minPrice });
    }

    if (filters.maxPrice !== undefined) {
      queryBuilder.andWhere('d.price_final <= :maxPrice', { maxPrice: filters.maxPrice });
    }

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

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

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

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

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

  async findByNivel(ctx: ServiceContext, nivelId: string): Promise<Departamento[]> {
    return this.repository
      .createQueryBuilder('d')
      .leftJoinAndSelect('d.prototipo', 'prototipo')
      .where('d.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('d.deleted_at IS NULL')
      .andWhere('d.nivel_id = :nivelId', { nivelId })
      .orderBy('d.code', 'ASC')
      .getMany();
  }

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

  async findAvailable(ctx: ServiceContext, torreId?: string): Promise<Departamento[]> {
    const queryBuilder = this.repository
      .createQueryBuilder('d')
      .leftJoinAndSelect('d.nivel', 'nivel')
      .leftJoinAndSelect('nivel.torre', 'torre')
      .leftJoinAndSelect('d.prototipo', 'prototipo')
      .where('d.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('d.deleted_at IS NULL')
      .andWhere('d.status = :status', { status: 'available' });

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

    return queryBuilder
      .orderBy('torre.code', 'ASC')
      .addOrderBy('nivel.floor_number', 'ASC')
      .addOrderBy('d.code', 'ASC')
      .getMany();
  }

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

    const departamento = this.repository.create({
      tenantId: ctx.tenantId,
      createdBy: ctx.userId,
      nivelId: dto.nivelId,
      prototipoId: dto.prototipoId,
      code: dto.code,
      unitNumber: dto.unitNumber,
      areaM2: dto.areaM2,
      status: dto.status ?? 'available',
      priceBase: dto.priceBase,
      priceFinal: dto.priceFinal ?? dto.priceBase,
    });

    return this.repository.save(departamento);
  }

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

    if (departamento.status === 'sold' || departamento.status === 'delivered') {
      throw new Error('Cannot update a sold or delivered unit');
    }

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

    return this.repository.save(departamento);
  }

  async sell(ctx: ServiceContext, id: string, dto: SellDepartamentoDto): Promise<Departamento | null> {
    const departamento = await this.findById(ctx, id);
    if (!departamento) {
      return null;
    }

    if (departamento.status !== 'available' && departamento.status !== 'reserved') {
      throw new Error('Unit is not available for sale');
    }

    departamento.buyerId = dto.buyerId;
    departamento.priceFinal = dto.priceFinal;
    departamento.saleDate = dto.saleDate ?? new Date();
    if (dto.deliveryDate) {
      departamento.deliveryDate = dto.deliveryDate;
    }
    departamento.status = 'sold';
    if (ctx.userId) {
      departamento.updatedBy = ctx.userId;
    }

    return this.repository.save(departamento);
  }

  async reserve(ctx: ServiceContext, id: string, buyerId: string): Promise<Departamento | null> {
    const departamento = await this.findById(ctx, id);
    if (!departamento) {
      return null;
    }

    if (departamento.status !== 'available') {
      throw new Error('Unit is not available for reservation');
    }

    departamento.buyerId = buyerId;
    departamento.status = 'reserved';
    if (ctx.userId) {
      departamento.updatedBy = ctx.userId;
    }

    return this.repository.save(departamento);
  }

  async cancelReservation(ctx: ServiceContext, id: string): Promise<Departamento | null> {
    const departamento = await this.findById(ctx, id);
    if (!departamento) {
      return null;
    }

    if (departamento.status !== 'reserved') {
      throw new Error('Unit is not reserved');
    }

    departamento.buyerId = null as any;
    departamento.status = 'available';
    if (ctx.userId) {
      departamento.updatedBy = ctx.userId;
    }

    return this.repository.save(departamento);
  }

  async markDelivered(ctx: ServiceContext, id: string, deliveryDate?: Date): Promise<Departamento | null> {
    const departamento = await this.findById(ctx, id);
    if (!departamento) {
      return null;
    }

    if (departamento.status !== 'sold') {
      throw new Error('Unit must be sold before delivery');
    }

    departamento.deliveryDate = deliveryDate ?? new Date();
    departamento.status = 'delivered';
    if (ctx.userId) {
      departamento.updatedBy = ctx.userId;
    }

    return this.repository.save(departamento);
  }

  async getStatistics(ctx: ServiceContext, torreId?: string): Promise<{
    totalUnits: number;
    available: number;
    reserved: number;
    sold: number;
    delivered: number;
    totalValue: number;
    soldValue: number;
    averagePrice: number;
  }> {
    const queryBuilder = this.repository
      .createQueryBuilder('d')
      .leftJoin('d.nivel', 'nivel')
      .where('d.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('d.deleted_at IS NULL');

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

    const departamentos = await queryBuilder.getMany();

    const available = departamentos.filter(d => d.status === 'available');
    const reserved = departamentos.filter(d => d.status === 'reserved');
    const sold = departamentos.filter(d => d.status === 'sold');
    const delivered = departamentos.filter(d => d.status === 'delivered');

    const totalValue = departamentos.reduce((sum, d) => sum + Number(d.priceFinal || 0), 0);
    const soldValue = [...sold, ...delivered].reduce((sum, d) => sum + Number(d.priceFinal || 0), 0);

    return {
      totalUnits: departamentos.length,
      available: available.length,
      reserved: reserved.length,
      sold: sold.length,
      delivered: delivered.length,
      totalValue,
      soldValue,
      averagePrice: departamentos.length > 0 ? totalValue / departamentos.length : 0,
    };
  }

  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const departamento = await this.findById(ctx, id);
    if (!departamento) {
      return false;
    }

    if (departamento.status === 'sold' || departamento.status === 'delivered') {
      throw new Error('Cannot delete a sold or delivered unit');
    }

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