import { Repository, IsNull } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { ProductCategory } from './entities/product-category.entity.js';
import { NotFoundError, ConflictError } from '../../shared/errors/index.js';
import { logger } from '../../shared/utils/logger.js';

export interface CreateProductCategoryDto {
  name: string;
  code: string;
  parent_id?: string;
  parentId?: string; // Accept camelCase too
}

export interface UpdateProductCategoryDto {
  name?: string;
  parent_id?: string | null;
  parentId?: string | null; // Accept camelCase too
  active?: boolean;
}

class ProductCategoriesService {
  private repository: Repository<ProductCategory>;

  constructor() {
    this.repository = AppDataSource.getRepository(ProductCategory);
  }

  async findAll(
    tenantId: string,
    parentId?: string,
    activeOnly: boolean = false
  ): Promise<ProductCategory[]> {
    logger.debug('Finding all product categories', {
      tenantId,
      parentId,
      activeOnly,
    });

    const queryBuilder = this.repository
      .createQueryBuilder('pc')
      .leftJoinAndSelect('pc.parent', 'parent')
      .where('pc.tenantId = :tenantId', { tenantId })
      .andWhere('pc.deletedAt IS NULL');

    if (parentId !== undefined) {
      if (parentId === null || parentId === 'null') {
        queryBuilder.andWhere('pc.parentId IS NULL');
      } else {
        queryBuilder.andWhere('pc.parentId = :parentId', { parentId });
      }
    }

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

    queryBuilder.orderBy('pc.name', 'ASC');

    return queryBuilder.getMany();
  }

  async findById(id: string, tenantId: string): Promise<ProductCategory> {
    logger.debug('Finding product category by id', { id, tenantId });

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

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

    return category;
  }

  async create(
    dto: CreateProductCategoryDto,
    tenantId: string,
    userId: string
  ): Promise<ProductCategory> {
    logger.debug('Creating product category', { dto, tenantId, userId });

    // Accept both snake_case and camelCase
    const parentId = dto.parent_id ?? dto.parentId;

    // Check unique code within tenant
    const existing = await this.repository.findOne({
      where: {
        tenantId,
        code: dto.code,
        deletedAt: IsNull(),
      },
    });

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

    // Validate parent if specified
    if (parentId) {
      const parent = await this.repository.findOne({
        where: {
          id: parentId,
          tenantId,
          deletedAt: IsNull(),
        },
      });

      if (!parent) {
        throw new NotFoundError('Categoría padre no encontrada');
      }
    }

    const category = this.repository.create({
      tenantId,
      name: dto.name,
      code: dto.code,
      parentId: parentId || null,
      createdBy: userId,
    });

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

    return saved;
  }

  async update(
    id: string,
    dto: UpdateProductCategoryDto,
    tenantId: string,
    userId: string
  ): Promise<ProductCategory> {
    logger.debug('Updating product category', { id, dto, tenantId, userId });

    const category = await this.findById(id, tenantId);

    // Accept both snake_case and camelCase
    const parentId = dto.parent_id ?? dto.parentId;

    // Validate parent (prevent self-reference)
    if (parentId !== undefined) {
      if (parentId === id) {
        throw new ConflictError('Una categoría no puede ser su propio padre');
      }

      if (parentId !== null) {
        const parent = await this.repository.findOne({
          where: {
            id: parentId,
            tenantId,
            deletedAt: IsNull(),
          },
        });

        if (!parent) {
          throw new NotFoundError('Categoría padre no encontrada');
        }
      }

      category.parentId = parentId;
    }

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

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

    category.updatedBy = userId;

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

    return updated;
  }

  async delete(id: string, tenantId: string): Promise<void> {
    logger.debug('Deleting product category', { id, tenantId });

    const category = await this.findById(id, tenantId);

    // Check if has children
    const childrenCount = await this.repository.count({
      where: {
        parentId: id,
        tenantId,
        deletedAt: IsNull(),
      },
    });

    if (childrenCount > 0) {
      throw new ConflictError(
        'No se puede eliminar una categoría que tiene subcategorías'
      );
    }

    // Note: We should check for products in inventory schema
    // For now, we'll just perform a hard delete as in original
    // In a real scenario, you'd want to check inventory.products table

    await this.repository.delete({ id, tenantId });

    logger.info('Product category deleted', { id, tenantId });
  }
}

export const productCategoriesService = new ProductCategoriesService();
