/**
 * ConceptoService - Catalogo de Conceptos de Obra
 *
 * Gestiona el catálogo jerárquico de conceptos de obra.
 * Los conceptos pueden tener estructura padre-hijo (niveles).
 *
 * @module Budgets
 */

import { Repository, IsNull, FindOptionsWhere } from 'typeorm';
import { Concepto } from '../entities/concepto.entity';

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

interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreateConceptoDto {
  code: string;
  name: string;
  description?: string;
  parentId?: string;
  unitId?: string;
  unitPrice?: number;
  isComposite?: boolean;
}

export interface UpdateConceptoDto {
  name?: string;
  description?: string;
  unitId?: string;
  unitPrice?: number;
  isComposite?: boolean;
}

export class ConceptoService {
  private repository: Repository<Concepto>;

  constructor(repository: Repository<Concepto>) {
    this.repository = repository;
  }

  async create(ctx: ServiceContext, data: Partial<Concepto>): Promise<Concepto> {
    const entity = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
    });
    return this.repository.save(entity);
  }

  async findById(ctx: ServiceContext, id: string): Promise<Concepto | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId, deletedAt: IsNull() } as FindOptionsWhere<Concepto>,
    });
  }

  async findAll(
    ctx: ServiceContext,
    options: { page?: number; limit?: number; where?: FindOptionsWhere<Concepto> } = {}
  ): Promise<PaginatedResult<Concepto>> {
    const page = options.page || 1;
    const limit = options.limit || 20;
    const skip = (page - 1) * limit;

    const where = {
      ...options.where,
      tenantId: ctx.tenantId,
      deletedAt: IsNull(),
    } as FindOptionsWhere<Concepto>;

    const [data, total] = await this.repository.findAndCount({
      where,
      skip,
      take: limit,
      order: { code: 'ASC' },
    });

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

  async find(
    ctx: ServiceContext,
    options: { where?: FindOptionsWhere<Concepto>; order?: Record<string, 'ASC' | 'DESC'> } = {}
  ): Promise<Concepto[]> {
    return this.repository.find({
      where: {
        ...options.where,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      } as FindOptionsWhere<Concepto>,
      order: options.order,
    });
  }

  async update(ctx: ServiceContext, id: string, data: Partial<Concepto>): Promise<Concepto | null> {
    const entity = await this.findById(ctx, id);
    if (!entity) return null;
    Object.assign(entity, data);
    return this.repository.save(entity);
  }

  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const entity = await this.findById(ctx, id);
    if (!entity) return false;
    entity.deletedAt = new Date();
    await this.repository.save(entity);
    return true;
  }

  async exists(ctx: ServiceContext, where: FindOptionsWhere<Concepto>): Promise<boolean> {
    const count = await this.repository.count({
      where: {
        ...where,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      } as FindOptionsWhere<Concepto>,
    });
    return count > 0;
  }

  /**
   * Crear un nuevo concepto con cálculo automático de nivel y path
   */
  async createConcepto(
    ctx: ServiceContext,
    data: CreateConceptoDto
  ): Promise<Concepto> {
    let level = 0;
    let path = data.code;

    if (data.parentId) {
      const parent = await this.findById(ctx, data.parentId);
      if (parent) {
        level = parent.level + 1;
        path = `${parent.path}/${data.code}`;
      }
    }

    return this.create(ctx, {
      ...data,
      level,
      path,
    });
  }

  /**
   * Obtener conceptos raíz (sin padre)
   */
  async findRootConceptos(
    ctx: ServiceContext,
    page = 1,
    limit = 50
  ): Promise<PaginatedResult<Concepto>> {
    return this.findAll(ctx, {
      page,
      limit,
      where: { parentId: IsNull() } as FindOptionsWhere<Concepto>,
    });
  }

  /**
   * Obtener hijos de un concepto
   */
  async findChildren(
    ctx: ServiceContext,
    parentId: string
  ): Promise<Concepto[]> {
    return this.find(ctx, {
      where: { parentId } as FindOptionsWhere<Concepto>,
      order: { code: 'ASC' },
    });
  }

  /**
   * Obtener árbol completo de conceptos
   */
  async getConceptoTree(
    ctx: ServiceContext,
    rootId?: string
  ): Promise<ConceptoNode[]> {
    const where = rootId
      ? { parentId: rootId }
      : { parentId: IsNull() };

    const roots = await this.find(ctx, {
      where: where as FindOptionsWhere<Concepto>,
      order: { code: 'ASC' },
    });

    return this.buildTree(ctx, roots);
  }

  private async buildTree(
    ctx: ServiceContext,
    conceptos: Concepto[]
  ): Promise<ConceptoNode[]> {
    const tree: ConceptoNode[] = [];

    for (const concepto of conceptos) {
      const children = await this.findChildren(ctx, concepto.id);
      const childNodes = children.length > 0
        ? await this.buildTree(ctx, children)
        : [];

      tree.push({
        ...concepto,
        children: childNodes,
      });
    }

    return tree;
  }

  /**
   * Buscar conceptos por código o nombre
   */
  async search(
    ctx: ServiceContext,
    term: string,
    limit = 20
  ): Promise<Concepto[]> {
    return this.repository
      .createQueryBuilder('c')
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('c.deleted_at IS NULL')
      .andWhere('(c.code ILIKE :term OR c.name ILIKE :term)', {
        term: `%${term}%`,
      })
      .orderBy('c.code', 'ASC')
      .take(limit)
      .getMany();
  }

  /**
   * Verificar si un código ya existe
   */
  async codeExists(ctx: ServiceContext, code: string): Promise<boolean> {
    return this.exists(ctx, { code } as FindOptionsWhere<Concepto>);
  }

  /**
   * Actualizar precio unitario de un concepto
   */
  async updateUnitPrice(
    ctx: ServiceContext,
    id: string,
    unitPrice: number
  ): Promise<Concepto | null> {
    return this.update(ctx, id, {
      unitPrice,
      updatedById: ctx.userId,
    });
  }

  /**
   * Obtener conceptos por unidad
   */
  async findByUnit(
    ctx: ServiceContext,
    unitId: string
  ): Promise<Concepto[]> {
    return this.find(ctx, {
      where: { unitId } as FindOptionsWhere<Concepto>,
      order: { code: 'ASC' },
    });
  }

  /**
   * Obtener conceptos compuestos (con sub-conceptos)
   */
  async findCompositeConceptos(
    ctx: ServiceContext,
    page = 1,
    limit = 50
  ): Promise<PaginatedResult<Concepto>> {
    return this.findAll(ctx, {
      page,
      limit,
      where: { isComposite: true } as FindOptionsWhere<Concepto>,
    });
  }

  /**
   * Obtener conceptos simples (hoja del arbol)
   */
  async findSimpleConceptos(
    ctx: ServiceContext,
    page = 1,
    limit = 50
  ): Promise<PaginatedResult<Concepto>> {
    return this.findAll(ctx, {
      page,
      limit,
      where: { isComposite: false } as FindOptionsWhere<Concepto>,
    });
  }

  /**
   * Obtener la ruta completa de un concepto (ancestros)
   */
  async getConceptoPath(
    ctx: ServiceContext,
    id: string
  ): Promise<Concepto[]> {
    const concepto = await this.findById(ctx, id);
    if (!concepto) {
      return [];
    }

    const path: Concepto[] = [concepto];
    let current = concepto;

    while (current.parentId) {
      const parent = await this.findById(ctx, current.parentId);
      if (!parent) break;
      path.unshift(parent);
      current = parent;
    }

    return path;
  }

  /**
   * Mover concepto a otro padre
   */
  async moveConcepto(
    ctx: ServiceContext,
    id: string,
    newParentId: string | null
  ): Promise<Concepto | null> {
    const concepto = await this.findById(ctx, id);
    if (!concepto) {
      return null;
    }

    let level = 0;
    let path = concepto.code;

    if (newParentId) {
      const newParent = await this.findById(ctx, newParentId);
      if (!newParent) {
        throw new Error('New parent concept not found');
      }
      level = newParent.level + 1;
      path = `${newParent.path}/${concepto.code}`;
    }

    return this.update(ctx, id, {
      parentId: newParentId,
      level,
      path,
      updatedById: ctx.userId,
    });
  }

  /**
   * Obtener todos los descendientes de un concepto
   */
  async getAllDescendants(
    ctx: ServiceContext,
    id: string
  ): Promise<Concepto[]> {
    const concepto = await this.findById(ctx, id);
    if (!concepto || !concepto.path) {
      return [];
    }

    return this.repository
      .createQueryBuilder('c')
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('c.deleted_at IS NULL')
      .andWhere('c.path LIKE :pathPrefix', { pathPrefix: `${concepto.path}/%` })
      .orderBy('c.path', 'ASC')
      .getMany();
  }

  /**
   * Contar hijos directos de un concepto
   */
  async countChildren(ctx: ServiceContext, parentId: string): Promise<number> {
    return this.repository.count({
      where: {
        tenantId: ctx.tenantId,
        parentId,
        deletedAt: IsNull(),
      } as FindOptionsWhere<Concepto>,
    });
  }

  /**
   * Importar conceptos desde un array (bulk insert)
   */
  async bulkCreate(
    ctx: ServiceContext,
    conceptos: CreateConceptoDto[]
  ): Promise<Concepto[]> {
    const created: Concepto[] = [];

    for (const data of conceptos) {
      const concepto = await this.createConcepto(ctx, data);
      created.push(concepto);
    }

    return created;
  }

  /**
   * Actualizar precios en lote
   */
  async bulkUpdatePrices(
    ctx: ServiceContext,
    updates: Array<{ id: string; unitPrice: number }>
  ): Promise<number> {
    let updatedCount = 0;

    for (const update of updates) {
      const result = await this.updateUnitPrice(ctx, update.id, update.unitPrice);
      if (result) {
        updatedCount++;
      }
    }

    return updatedCount;
  }

  /**
   * Obtener estadisticas del catalogo de conceptos
   */
  async getCatalogStats(ctx: ServiceContext): Promise<ConceptoCatalogStats> {
    const result = await this.repository
      .createQueryBuilder('c')
      .select([
        'COUNT(*) as total_conceptos',
        'COUNT(CASE WHEN c.is_composite = true THEN 1 END) as composite_count',
        'COUNT(CASE WHEN c.is_composite = false THEN 1 END) as simple_count',
        'COUNT(CASE WHEN c.parent_id IS NULL THEN 1 END) as root_count',
        'MAX(c.level) as max_depth',
        'AVG(c.unit_price) as avg_unit_price',
      ])
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('c.deleted_at IS NULL')
      .getRawOne();

    return {
      totalConceptos: parseInt(result?.total_conceptos || '0'),
      compositeCount: parseInt(result?.composite_count || '0'),
      simpleCount: parseInt(result?.simple_count || '0'),
      rootCount: parseInt(result?.root_count || '0'),
      maxDepth: parseInt(result?.max_depth || '0'),
      avgUnitPrice: parseFloat(result?.avg_unit_price || '0'),
    };
  }
}

export interface ConceptoNode extends Concepto {
  children: ConceptoNode[];
}

export interface ConceptoCatalogStats {
  totalConceptos: number;
  compositeCount: number;
  simpleCount: number;
  rootCount: number;
  maxDepth: number;
  avgUnitPrice: number;
}
