/**
 * PresupuestoService - Gestión de Presupuestos de Obra
 *
 * Gestiona presupuestos de obra con sus partidas.
 * Soporta versionamiento y aprobación.
 *
 * @module Budgets
 */

import { Repository } from 'typeorm';
import { BaseService, ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
import { Presupuesto } from '../entities/presupuesto.entity';
import { PresupuestoPartida } from '../entities/presupuesto-partida.entity';

export interface CreatePresupuestoDto {
  code: string;
  name: string;
  description?: string;
  fraccionamientoId?: string;
  prototipoId?: string;
  currencyId?: string;
}

export interface AddPartidaDto {
  conceptoId: string;
  quantity: number;
  unitPrice: number;
  sequence?: number;
}

export interface UpdatePartidaDto {
  quantity?: number;
  unitPrice?: number;
  sequence?: number;
}

export class PresupuestoService extends BaseService<Presupuesto> {
  constructor(
    repository: Repository<Presupuesto>,
    private readonly partidaRepository: Repository<PresupuestoPartida>
  ) {
    super(repository);
  }

  /**
   * Crear nuevo presupuesto
   */
  async createPresupuesto(
    ctx: ServiceContext,
    data: CreatePresupuestoDto
  ): Promise<Presupuesto> {
    return this.create(ctx, {
      ...data,
      version: 1,
      isActive: true,
      totalAmount: 0,
    });
  }

  /**
   * Obtener presupuestos por fraccionamiento
   */
  async findByFraccionamiento(
    ctx: ServiceContext,
    fraccionamientoId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<Presupuesto>> {
    return this.findAll(ctx, {
      page,
      limit,
      where: { fraccionamientoId, isActive: true } as any,
    });
  }

  /**
   * Obtener presupuesto con sus partidas
   */
  async findWithPartidas(
    ctx: ServiceContext,
    id: string
  ): Promise<Presupuesto | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as any,
      relations: ['partidas', 'partidas.concepto'],
    });
  }

  /**
   * Agregar partida al presupuesto
   */
  async addPartida(
    ctx: ServiceContext,
    presupuestoId: string,
    data: AddPartidaDto
  ): Promise<PresupuestoPartida> {
    const presupuesto = await this.findById(ctx, presupuestoId);
    if (!presupuesto) {
      throw new Error('Presupuesto not found');
    }

    const partida = this.partidaRepository.create({
      tenantId: ctx.tenantId,
      presupuestoId,
      conceptoId: data.conceptoId,
      quantity: data.quantity,
      unitPrice: data.unitPrice,
      sequence: data.sequence || 0,
      createdById: ctx.userId,
    });

    const savedPartida = await this.partidaRepository.save(partida);
    await this.recalculateTotal(ctx, presupuestoId);

    return savedPartida;
  }

  /**
   * Actualizar partida
   */
  async updatePartida(
    ctx: ServiceContext,
    partidaId: string,
    data: UpdatePartidaDto
  ): Promise<PresupuestoPartida | null> {
    const partida = await this.partidaRepository.findOne({
      where: {
        id: partidaId,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as any,
    });

    if (!partida) {
      return null;
    }

    const updated = this.partidaRepository.merge(partida, {
      ...data,
      updatedById: ctx.userId,
    });

    const saved = await this.partidaRepository.save(updated);
    await this.recalculateTotal(ctx, partida.presupuestoId);

    return saved;
  }

  /**
   * Eliminar partida
   */
  async removePartida(ctx: ServiceContext, partidaId: string): Promise<boolean> {
    const partida = await this.partidaRepository.findOne({
      where: {
        id: partidaId,
        tenantId: ctx.tenantId,
      } as any,
    });

    if (!partida) {
      return false;
    }

    await this.partidaRepository.update(
      { id: partidaId },
      {
        deletedAt: new Date(),
        deletedById: ctx.userId,
      }
    );

    await this.recalculateTotal(ctx, partida.presupuestoId);
    return true;
  }

  /**
   * Recalcular total del presupuesto
   */
  async recalculateTotal(ctx: ServiceContext, presupuestoId: string): Promise<void> {
    const result = await this.partidaRepository
      .createQueryBuilder('p')
      .select('SUM(p.quantity * p.unit_price)', 'total')
      .where('p.presupuesto_id = :presupuestoId', { presupuestoId })
      .andWhere('p.deleted_at IS NULL')
      .getRawOne();

    const total = parseFloat(result?.total || '0');

    await this.repository.update(
      { id: presupuestoId },
      { totalAmount: total, updatedById: ctx.userId }
    );
  }

  /**
   * Crear nueva versión del presupuesto
   */
  async createNewVersion(
    ctx: ServiceContext,
    presupuestoId: string
  ): Promise<Presupuesto> {
    const original = await this.findWithPartidas(ctx, presupuestoId);
    if (!original) {
      throw new Error('Presupuesto not found');
    }

    // Desactivar versión anterior
    await this.repository.update(
      { id: presupuestoId },
      { isActive: false, updatedById: ctx.userId }
    );

    // Crear nueva versión
    const newVersion = await this.create(ctx, {
      code: original.code,
      name: original.name,
      description: original.description,
      fraccionamientoId: original.fraccionamientoId,
      prototipoId: original.prototipoId,
      currencyId: original.currencyId,
      version: original.version + 1,
      isActive: true,
      totalAmount: original.totalAmount,
    });

    // Copiar partidas
    for (const partida of original.partidas) {
      await this.partidaRepository.save(
        this.partidaRepository.create({
          tenantId: ctx.tenantId,
          presupuestoId: newVersion.id,
          conceptoId: partida.conceptoId,
          quantity: partida.quantity,
          unitPrice: partida.unitPrice,
          sequence: partida.sequence,
          createdById: ctx.userId,
        })
      );
    }

    return newVersion;
  }

  /**
   * Aprobar presupuesto
   */
  async approve(ctx: ServiceContext, presupuestoId: string): Promise<Presupuesto | null> {
    const presupuesto = await this.findById(ctx, presupuestoId);
    if (!presupuesto) {
      return null;
    }

    return this.update(ctx, presupuestoId, {
      approvedAt: new Date(),
      approvedById: ctx.userId,
    });
  }
}
