import { Repository } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Uom, UomType } from './entities/uom.entity.js';
import { UomCategory } from './entities/uom-category.entity.js';
import { NotFoundError, ConflictError } from '../../shared/errors/index.js';
import { logger } from '../../shared/utils/logger.js';

export interface CreateUomDto {
  name: string;
  code: string;
  category_id?: string;
  categoryId?: string; // Accept camelCase too
  uom_type?: 'reference' | 'bigger' | 'smaller';
  uomType?: 'reference' | 'bigger' | 'smaller'; // Accept camelCase too
  ratio?: number;
}

export interface UpdateUomDto {
  name?: string;
  ratio?: number;
  active?: boolean;
}

class UomService {
  private repository: Repository<Uom>;
  private categoryRepository: Repository<UomCategory>;

  constructor() {
    this.repository = AppDataSource.getRepository(Uom);
    this.categoryRepository = AppDataSource.getRepository(UomCategory);
  }

  // Categories
  async findAllCategories(activeOnly: boolean = false): Promise<UomCategory[]> {
    logger.debug('Finding all UOM categories', { activeOnly });

    const queryBuilder = this.categoryRepository
      .createQueryBuilder('category')
      .orderBy('category.name', 'ASC');

    // Note: activeOnly is not supported since the table doesn't have an active field
    // Keeping the parameter for backward compatibility

    return queryBuilder.getMany();
  }

  async findCategoryById(id: string): Promise<UomCategory> {
    logger.debug('Finding UOM category by id', { id });

    const category = await this.categoryRepository.findOne({
      where: { id },
    });

    if (!category) {
      throw new NotFoundError('Categoría de UdM no encontrada');
    }

    return category;
  }

  // UoM
  async findAll(categoryId?: string, activeOnly: boolean = false): Promise<Uom[]> {
    logger.debug('Finding all UOMs', { categoryId, activeOnly });

    const queryBuilder = this.repository
      .createQueryBuilder('u')
      .leftJoinAndSelect('u.category', 'uc')
      .orderBy('uc.name', 'ASC')
      .addOrderBy('u.uomType', 'ASC')
      .addOrderBy('u.name', 'ASC');

    if (categoryId) {
      queryBuilder.where('u.categoryId = :categoryId', { categoryId });
    }

    if (activeOnly) {
      queryBuilder.andWhere('u.active = :active', { active: true });
    }

    return queryBuilder.getMany();
  }

  async findById(id: string): Promise<Uom> {
    logger.debug('Finding UOM by id', { id });

    const uom = await this.repository.findOne({
      where: { id },
      relations: ['category'],
    });

    if (!uom) {
      throw new NotFoundError('Unidad de medida no encontrada');
    }

    return uom;
  }

  async create(dto: CreateUomDto): Promise<Uom> {
    logger.debug('Creating UOM', { dto });

    // Accept both snake_case and camelCase
    const categoryId = dto.category_id ?? dto.categoryId;
    const uomType = dto.uom_type ?? dto.uomType ?? 'reference';
    const factor = dto.ratio ?? 1;

    if (!categoryId) {
      throw new NotFoundError('category_id es requerido');
    }

    // Validate category exists
    await this.findCategoryById(categoryId);

    // Check unique code
    if (dto.code) {
      const existing = await this.repository.findOne({
        where: { code: dto.code },
      });

      if (existing) {
        throw new ConflictError(`Ya existe una UdM con código ${dto.code}`);
      }
    }

    const uom = this.repository.create({
      name: dto.name,
      code: dto.code,
      categoryId,
      uomType: uomType as UomType,
      factor,
    });

    const saved = await this.repository.save(uom);
    logger.info('UOM created', { id: saved.id, code: saved.code });

    return saved;
  }

  async update(id: string, dto: UpdateUomDto): Promise<Uom> {
    logger.debug('Updating UOM', { id, dto });

    const uom = await this.findById(id);

    if (dto.name !== undefined) {
      uom.name = dto.name;
    }

    if (dto.ratio !== undefined) {
      uom.factor = dto.ratio;
    }

    if (dto.active !== undefined) {
      uom.active = dto.active;
    }

    const updated = await this.repository.save(uom);
    logger.info('UOM updated', { id: updated.id, code: updated.code });

    return updated;
  }

  /**
   * Convert quantity from one UoM to another
   * Both UoMs must be in the same category
   */
  async convertQuantity(quantity: number, fromUomId: string, toUomId: string): Promise<number> {
    logger.debug('Converting quantity', { quantity, fromUomId, toUomId });

    // Same UoM = no conversion needed
    if (fromUomId === toUomId) {
      return quantity;
    }

    const fromUom = await this.findById(fromUomId);
    const toUom = await this.findById(toUomId);

    // Validate same category
    if (fromUom.categoryId !== toUom.categoryId) {
      throw new Error('No se pueden convertir unidades de diferentes categorías');
    }

    // Convert: first to reference unit, then to target unit
    // quantity * fromFactor = reference quantity
    // reference quantity / toFactor = target quantity
    const result = (quantity * fromUom.factor) / toUom.factor;

    logger.debug('Conversion result', {
      quantity,
      fromUom: fromUom.name,
      toUom: toUom.name,
      result
    });

    return result;
  }

  /**
   * Get the reference UoM for a category
   */
  async getReferenceUom(categoryId: string): Promise<Uom | null> {
    logger.debug('Getting reference UoM', { categoryId });

    return this.repository.findOne({
      where: {
        categoryId,
        uomType: 'reference' as UomType,
        active: true,
      },
      relations: ['category'],
    });
  }

  /**
   * Find UoM by code
   */
  async findByCode(code: string): Promise<Uom | null> {
    logger.debug('Finding UoM by code', { code });

    return this.repository.findOne({
      where: { code },
      relations: ['category'],
    });
  }

  /**
   * Get all UoMs in a category with their conversion factors
   */
  async getConversionTable(categoryId: string): Promise<{
    referenceUom: Uom;
    conversions: Array<{ uom: Uom; toReference: number; fromReference: number }>;
  }> {
    logger.debug('Getting conversion table', { categoryId });

    const referenceUom = await this.getReferenceUom(categoryId);
    if (!referenceUom) {
      throw new NotFoundError('No se encontró unidad de referencia para esta categoría');
    }

    const uoms = await this.findAll(categoryId, true);
    const conversions = uoms.map(uom => ({
      uom,
      toReference: uom.factor,        // Multiply by this to get reference unit
      fromReference: 1 / uom.factor,  // Multiply by this to get this unit from reference
    }));

    return { referenceUom, conversions };
  }
}

export const uomService = new UomService();
