/**
 * CostCenterService - Gestión de Centros de Costo
 *
 * Administra centros de costo jerárquicos para imputación de gastos.
 *
 * @module Admin
 */

import { Repository } from 'typeorm';
import { BaseService, ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
import { CostCenter, CostCenterType, CostCenterLevel } from '../entities/cost-center.entity';

export interface CreateCostCenterDto {
  code: string;
  name: string;
  description?: string;
  costCenterType?: CostCenterType;
  level?: CostCenterLevel;
  parentId?: string;
  fraccionamientoId?: string;
  responsibleId?: string;
  budget?: number;
  distributionPercentage?: number;
  distributionBase?: string;
  accountingCode?: string;
  isBillable?: boolean;
  metadata?: Record<string, any>;
}

export interface UpdateCostCenterDto extends Partial<CreateCostCenterDto> {
  isActive?: boolean;
  sortOrder?: number;
}

export interface CostCenterFilters {
  costCenterType?: CostCenterType;
  level?: CostCenterLevel;
  fraccionamientoId?: string;
  parentId?: string;
  responsibleId?: string;
  isActive?: boolean;
  search?: string;
}

export class CostCenterService extends BaseService<CostCenter> {
  constructor(repository: Repository<CostCenter>) {
    super(repository);
  }

  /**
   * Buscar centros de costo con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: CostCenterFilters,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<CostCenter>> {
    const qb = this.repository
      .createQueryBuilder('cc')
      .leftJoinAndSelect('cc.responsible', 'r')
      .where('cc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('cc.deleted_at IS NULL');

    if (filters.costCenterType) {
      qb.andWhere('cc.cost_center_type = :costCenterType', { costCenterType: filters.costCenterType });
    }
    if (filters.level) {
      qb.andWhere('cc.level = :level', { level: filters.level });
    }
    if (filters.fraccionamientoId) {
      qb.andWhere('cc.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId: filters.fraccionamientoId });
    }
    if (filters.parentId !== undefined) {
      if (filters.parentId === null) {
        qb.andWhere('cc.parent_id IS NULL');
      } else {
        qb.andWhere('cc.parent_id = :parentId', { parentId: filters.parentId });
      }
    }
    if (filters.responsibleId) {
      qb.andWhere('cc.responsible_id = :responsibleId', { responsibleId: filters.responsibleId });
    }
    if (filters.isActive !== undefined) {
      qb.andWhere('cc.is_active = :isActive', { isActive: filters.isActive });
    }
    if (filters.search) {
      qb.andWhere('(cc.name ILIKE :search OR cc.code ILIKE :search OR cc.description ILIKE :search)', {
        search: `%${filters.search}%`,
      });
    }

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

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

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

  /**
   * Buscar por código
   */
  async findByCode(ctx: ServiceContext, code: string): Promise<CostCenter | null> {
    return this.repository.findOne({
      where: {
        tenantId: ctx.tenantId,
        code,
        deletedAt: null,
      } as any,
    });
  }

  /**
   * Obtener árbol de centros de costo
   */
  async getTree(ctx: ServiceContext, fraccionamientoId?: string): Promise<CostCenter[]> {
    const qb = this.repository
      .createQueryBuilder('cc')
      .where('cc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('cc.deleted_at IS NULL')
      .andWhere('cc.parent_id IS NULL');

    if (fraccionamientoId) {
      qb.andWhere('(cc.fraccionamiento_id = :fraccionamientoId OR cc.fraccionamiento_id IS NULL)', {
        fraccionamientoId,
      });
    }

    const roots = await qb.orderBy('cc.sort_order', 'ASC').getMany();

    // Load children recursively
    for (const root of roots) {
      await this.loadChildren(ctx, root, fraccionamientoId);
    }

    return roots;
  }

  /**
   * Cargar hijos recursivamente
   */
  private async loadChildren(
    ctx: ServiceContext,
    parent: CostCenter,
    fraccionamientoId?: string
  ): Promise<void> {
    const qb = this.repository
      .createQueryBuilder('cc')
      .where('cc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('cc.deleted_at IS NULL')
      .andWhere('cc.parent_id = :parentId', { parentId: parent.id });

    if (fraccionamientoId) {
      qb.andWhere('(cc.fraccionamiento_id = :fraccionamientoId OR cc.fraccionamiento_id IS NULL)', {
        fraccionamientoId,
      });
    }

    parent.children = await qb.orderBy('cc.sort_order', 'ASC').getMany();

    for (const child of parent.children) {
      await this.loadChildren(ctx, child, fraccionamientoId);
    }
  }

  /**
   * Obtener hijos directos
   */
  async getChildren(ctx: ServiceContext, parentId: string): Promise<CostCenter[]> {
    return this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        parentId,
        deletedAt: null,
      } as any,
      order: { sortOrder: 'ASC', code: 'ASC' },
    });
  }

  /**
   * Crear centro de costo
   */
  async createCostCenter(ctx: ServiceContext, data: CreateCostCenterDto): Promise<CostCenter> {
    const existing = await this.findByCode(ctx, data.code);
    if (existing) {
      throw new Error(`Cost center with code ${data.code} already exists`);
    }

    // Validate parent if provided
    if (data.parentId) {
      const parent = await this.findById(ctx, data.parentId);
      if (!parent) {
        throw new Error('Parent cost center not found');
      }
    }

    return this.create(ctx, {
      ...data,
      isActive: true,
      budgetConsumed: 0,
    });
  }

  /**
   * Actualizar presupuesto consumido
   */
  async updateBudgetConsumed(
    ctx: ServiceContext,
    id: string,
    amount: number
  ): Promise<CostCenter | null> {
    const cc = await this.findById(ctx, id);
    if (!cc) {
      return null;
    }

    return this.update(ctx, id, {
      budgetConsumed: Number(cc.budgetConsumed) + amount,
    });
  }

  /**
   * Obtener resumen de presupuesto
   */
  async getBudgetSummary(
    ctx: ServiceContext,
    fraccionamientoId?: string
  ): Promise<CostCenterBudgetSummary> {
    const qb = this.repository
      .createQueryBuilder('cc')
      .select([
        'cc.cost_center_type as type',
        'SUM(cc.budget) as total_budget',
        'SUM(cc.budget_consumed) as total_consumed',
        'COUNT(*) as count',
      ])
      .where('cc.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('cc.deleted_at IS NULL')
      .andWhere('cc.is_active = true')
      .groupBy('cc.cost_center_type');

    if (fraccionamientoId) {
      qb.andWhere('cc.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
    }

    const results = await qb.getRawMany();

    let totalBudget = 0;
    let totalConsumed = 0;
    const byType: { type: CostCenterType; budget: number; consumed: number; count: number }[] = [];

    for (const r of results) {
      const budget = parseFloat(r.total_budget || '0');
      const consumed = parseFloat(r.total_consumed || '0');
      totalBudget += budget;
      totalConsumed += consumed;
      byType.push({
        type: r.type,
        budget,
        consumed,
        count: parseInt(r.count),
      });
    }

    return {
      totalBudget,
      totalConsumed,
      totalAvailable: totalBudget - totalConsumed,
      utilizationPercentage: totalBudget > 0 ? (totalConsumed / totalBudget) * 100 : 0,
      byType,
    };
  }

  /**
   * Obtener estadísticas
   */
  async getStats(ctx: ServiceContext): Promise<CostCenterStats> {
    const all = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as any,
    });

    const byType = new Map<CostCenterType, number>();
    const byLevel = new Map<CostCenterLevel, number>();
    let activeCount = 0;

    for (const cc of all) {
      byType.set(cc.costCenterType, (byType.get(cc.costCenterType) || 0) + 1);
      byLevel.set(cc.level, (byLevel.get(cc.level) || 0) + 1);
      if (cc.isActive) activeCount++;
    }

    return {
      total: all.length,
      active: activeCount,
      inactive: all.length - activeCount,
      byType: Array.from(byType.entries()).map(([type, count]) => ({ type, count })),
      byLevel: Array.from(byLevel.entries()).map(([level, count]) => ({ level, count })),
    };
  }
}

export interface CostCenterBudgetSummary {
  totalBudget: number;
  totalConsumed: number;
  totalAvailable: number;
  utilizationPercentage: number;
  byType: {
    type: CostCenterType;
    budget: number;
    consumed: number;
    count: number;
  }[];
}

export interface CostCenterStats {
  total: number;
  active: number;
  inactive: number;
  byType: { type: CostCenterType; count: number }[];
  byLevel: { level: CostCenterLevel; count: number }[];
}
