/**
 * BidBudgetService - Gestión de Presupuestos de Licitación
 *
 * CRUD y cálculos para propuestas económicas.
 *
 * @module Bidding
 */

import { Repository } from 'typeorm';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
import { BidBudget, BudgetItemType, BudgetStatus } from '../entities/bid-budget.entity';

export interface CreateBudgetItemDto {
  bidId: string;
  parentId?: string;
  code: string;
  name: string;
  description?: string;
  itemType: BudgetItemType;
  unit?: string;
  quantity?: number;
  unitPrice?: number;
  materialsCost?: number;
  laborCost?: number;
  equipmentCost?: number;
  subcontractCost?: number;
  indirectPercentage?: number;
  profitPercentage?: number;
  financingPercentage?: number;
  baseAmount?: number;
  catalogConceptId?: string;
  notes?: string;
  metadata?: Record<string, any>;
}

export interface UpdateBudgetItemDto extends Partial<CreateBudgetItemDto> {
  status?: BudgetStatus;
  adjustmentReason?: string;
}

export interface BudgetFilters {
  bidId: string;
  itemType?: BudgetItemType;
  status?: BudgetStatus;
  parentId?: string | null;
  isSummary?: boolean;
}

export class BidBudgetService {
  constructor(private readonly repository: Repository<BidBudget>) {}

  /**
   * Crear item de presupuesto
   */
  async create(ctx: ServiceContext, data: CreateBudgetItemDto): Promise<BidBudget> {
    // Calcular nivel jerárquico
    let level = 0;
    if (data.parentId) {
      const parent = await this.repository.findOne({
        where: { id: data.parentId, tenantId: ctx.tenantId },
      });
      if (parent) {
        level = parent.level + 1;
      }
    }

    // Calcular orden
    const lastItem = await this.repository
      .createQueryBuilder('bb')
      .where('bb.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('bb.bid_id = :bidId', { bidId: data.bidId })
      .andWhere(data.parentId ? 'bb.parent_id = :parentId' : 'bb.parent_id IS NULL', { parentId: data.parentId })
      .orderBy('bb.sort_order', 'DESC')
      .getOne();

    const sortOrder = lastItem ? lastItem.sortOrder + 1 : 0;

    // Calcular totales
    const quantity = data.quantity || 0;
    const unitPrice = data.unitPrice || 0;
    const totalAmount = quantity * unitPrice;

    // Calcular varianza si hay base
    let varianceAmount = null;
    let variancePercentage = null;
    if (data.baseAmount !== undefined && data.baseAmount > 0) {
      varianceAmount = totalAmount - data.baseAmount;
      variancePercentage = (varianceAmount / data.baseAmount) * 100;
    }

    const item = this.repository.create({
      tenantId: ctx.tenantId,
      bidId: data.bidId,
      parentId: data.parentId,
      code: data.code,
      name: data.name,
      description: data.description,
      itemType: data.itemType,
      unit: data.unit,
      quantity: data.quantity || 0,
      unitPrice: data.unitPrice || 0,
      materialsCost: data.materialsCost,
      laborCost: data.laborCost,
      equipmentCost: data.equipmentCost,
      subcontractCost: data.subcontractCost,
      indirectPercentage: data.indirectPercentage,
      profitPercentage: data.profitPercentage,
      financingPercentage: data.financingPercentage,
      baseAmount: data.baseAmount,
      catalogConceptId: data.catalogConceptId,
      notes: data.notes,
      metadata: data.metadata,
      level,
      sortOrder,
      totalAmount,
      varianceAmount: varianceAmount ?? undefined,
      variancePercentage: variancePercentage ?? undefined,
      status: 'draft',
      isSummary: false,
      isCalculated: true,
      createdBy: ctx.userId,
      updatedBy: ctx.userId,
    });

    const saved = await this.repository.save(item);

    // Recalcular padres
    if (data.parentId) {
      await this.recalculateParent(ctx, data.parentId);
    }

    return saved;
  }

  /**
   * Buscar por ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<BidBudget | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId, deletedAt: undefined },
    });
  }

  /**
   * Buscar items de un presupuesto
   */
  async findByBid(ctx: ServiceContext, bidId: string): Promise<BidBudget[]> {
    return this.repository.find({
      where: { bidId, tenantId: ctx.tenantId, deletedAt: undefined },
      order: { sortOrder: 'ASC' },
    });
  }

  /**
   * Buscar items con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: BudgetFilters,
    page = 1,
    limit = 100
  ): Promise<PaginatedResult<BidBudget>> {
    const qb = this.repository
      .createQueryBuilder('bb')
      .where('bb.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('bb.bid_id = :bidId', { bidId: filters.bidId })
      .andWhere('bb.deleted_at IS NULL');

    if (filters.itemType) {
      qb.andWhere('bb.item_type = :itemType', { itemType: filters.itemType });
    }
    if (filters.status) {
      qb.andWhere('bb.status = :status', { status: filters.status });
    }
    if (filters.parentId !== undefined) {
      if (filters.parentId === null) {
        qb.andWhere('bb.parent_id IS NULL');
      } else {
        qb.andWhere('bb.parent_id = :parentId', { parentId: filters.parentId });
      }
    }
    if (filters.isSummary !== undefined) {
      qb.andWhere('bb.is_summary = :isSummary', { isSummary: filters.isSummary });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('bb.sort_order', 'ASC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  /**
   * Obtener árbol jerárquico
   */
  async getTree(ctx: ServiceContext, bidId: string): Promise<BidBudget[]> {
    const items = await this.findByBid(ctx, bidId);
    return this.buildTree(items);
  }

  private buildTree(items: BidBudget[], parentId: string | null = null): (BidBudget & { children?: BidBudget[] })[] {
    return items
      .filter((item) => item.parentId === parentId)
      .map((item) => ({
        ...item,
        children: this.buildTree(items, item.id),
      }));
  }

  /**
   * Actualizar item
   */
  async update(ctx: ServiceContext, id: string, data: UpdateBudgetItemDto): Promise<BidBudget | null> {
    const item = await this.findById(ctx, id);
    if (!item) return null;

    // Recalcular totales si cambian cantidad o precio
    const quantity = data.quantity ?? item.quantity;
    const unitPrice = data.unitPrice ?? item.unitPrice;
    const totalAmount = quantity * unitPrice;

    // Recalcular varianza
    const baseAmount = data.baseAmount ?? item.baseAmount;
    let varianceAmount = item.varianceAmount;
    let variancePercentage = item.variancePercentage;
    if (baseAmount !== undefined && baseAmount > 0) {
      varianceAmount = totalAmount - baseAmount;
      variancePercentage = (varianceAmount / baseAmount) * 100;
    }

    // Marcar como ajustado si hay razón
    const isAdjusted = data.adjustmentReason ? true : item.isAdjusted;

    Object.assign(item, {
      ...data,
      totalAmount,
      varianceAmount,
      variancePercentage,
      isAdjusted,
      isCalculated: true,
      updatedBy: ctx.userId,
    });

    const saved = await this.repository.save(item);

    // Recalcular padres
    if (item.parentId) {
      await this.recalculateParent(ctx, item.parentId);
    }

    return saved;
  }

  /**
   * Recalcular item padre
   */
  private async recalculateParent(ctx: ServiceContext, parentId: string): Promise<void> {
    const parent = await this.findById(ctx, parentId);
    if (!parent) return;

    const children = await this.repository.find({
      where: { parentId, tenantId: ctx.tenantId, deletedAt: undefined },
    });

    const totalAmount = children.reduce((sum, child) => sum + (Number(child.totalAmount) || 0), 0);

    parent.totalAmount = totalAmount;
    parent.isSummary = children.length > 0;
    parent.isCalculated = true;
    parent.updatedBy = ctx.userId;

    // Recalcular varianza
    if (parent.baseAmount !== undefined && parent.baseAmount > 0) {
      parent.varianceAmount = totalAmount - Number(parent.baseAmount);
      parent.variancePercentage = (parent.varianceAmount / Number(parent.baseAmount)) * 100;
    }

    await this.repository.save(parent);

    // Recursivamente actualizar ancestros
    if (parent.parentId) {
      await this.recalculateParent(ctx, parent.parentId);
    }
  }

  /**
   * Obtener resumen de presupuesto
   */
  async getSummary(ctx: ServiceContext, bidId: string): Promise<BudgetSummary> {
    const items = await this.findByBid(ctx, bidId);

    const directCosts = items
      .filter((i) => i.itemType === 'direct_cost' || i.itemType === 'labor' || i.itemType === 'materials' || i.itemType === 'equipment' || i.itemType === 'subcontract')
      .reduce((sum, i) => sum + (Number(i.totalAmount) || 0), 0);

    const indirectCosts = items
      .filter((i) => i.itemType === 'indirect_cost' || i.itemType === 'overhead')
      .reduce((sum, i) => sum + (Number(i.totalAmount) || 0), 0);

    const profit = items
      .filter((i) => i.itemType === 'profit')
      .reduce((sum, i) => sum + (Number(i.totalAmount) || 0), 0);

    const financing = items
      .filter((i) => i.itemType === 'financing')
      .reduce((sum, i) => sum + (Number(i.totalAmount) || 0), 0);

    const taxes = items
      .filter((i) => i.itemType === 'taxes')
      .reduce((sum, i) => sum + (Number(i.totalAmount) || 0), 0);

    const bonds = items
      .filter((i) => i.itemType === 'bonds')
      .reduce((sum, i) => sum + (Number(i.totalAmount) || 0), 0);

    const contingency = items
      .filter((i) => i.itemType === 'contingency')
      .reduce((sum, i) => sum + (Number(i.totalAmount) || 0), 0);

    const subtotal = directCosts + indirectCosts + profit + financing + contingency + bonds;
    const total = subtotal + taxes;

    const baseTotal = items.reduce((sum, i) => sum + (Number(i.baseAmount) || 0), 0);

    return {
      directCosts,
      indirectCosts,
      profit,
      financing,
      taxes,
      bonds,
      contingency,
      subtotal,
      total,
      baseTotal,
      variance: total - baseTotal,
      variancePercentage: baseTotal > 0 ? ((total - baseTotal) / baseTotal) * 100 : 0,
      itemCount: items.length,
    };
  }

  /**
   * Cambiar estado del presupuesto
   */
  async changeStatus(ctx: ServiceContext, bidId: string, status: BudgetStatus): Promise<number> {
    const result = await this.repository.update(
      { bidId, tenantId: ctx.tenantId, deletedAt: undefined },
      { status, updatedBy: ctx.userId }
    );
    return result.affected || 0;
  }

  /**
   * Soft delete
   */
  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.update(
      { id, tenantId: ctx.tenantId },
      { deletedAt: new Date(), updatedBy: ctx.userId }
    );
    return (result.affected || 0) > 0;
  }
}

export interface BudgetSummary {
  directCosts: number;
  indirectCosts: number;
  profit: number;
  financing: number;
  taxes: number;
  bonds: number;
  contingency: number;
  subtotal: number;
  total: number;
  baseTotal: number;
  variance: number;
  variancePercentage: number;
  itemCount: number;
}
