import { Repository, IsNull, ILike } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Product, ProductType, TrackingType, ValuationMethod } from './entities/product.entity.js';
import { StockQuant } from './entities/stock-quant.entity.js';
import { NotFoundError, ValidationError, ConflictError } from '../../shared/types/index.js';
import { logger } from '../../shared/utils/logger.js';

// ===== Interfaces =====

export interface CreateProductDto {
  name: string;
  code?: string;
  barcode?: string;
  description?: string;
  productType?: ProductType;
  tracking?: TrackingType;
  categoryId?: string;
  uomId: string;
  purchaseUomId?: string;
  costPrice?: number;
  listPrice?: number;
  valuationMethod?: ValuationMethod;
  weight?: number;
  volume?: number;
  canBeSold?: boolean;
  canBePurchased?: boolean;
  imageUrl?: string;
}

export interface UpdateProductDto {
  name?: string;
  barcode?: string | null;
  description?: string | null;
  tracking?: TrackingType;
  categoryId?: string | null;
  uomId?: string;
  purchaseUomId?: string | null;
  costPrice?: number;
  listPrice?: number;
  valuationMethod?: ValuationMethod;
  weight?: number | null;
  volume?: number | null;
  canBeSold?: boolean;
  canBePurchased?: boolean;
  imageUrl?: string | null;
  active?: boolean;
}

export interface ProductFilters {
  search?: string;
  categoryId?: string;
  productType?: ProductType;
  canBeSold?: boolean;
  canBePurchased?: boolean;
  active?: boolean;
  page?: number;
  limit?: number;
}

export interface ProductWithRelations extends Product {
  categoryName?: string;
  uomName?: string;
  purchaseUomName?: string;
}

// ===== Service Class =====

class ProductsService {
  private productRepository: Repository<Product>;
  private stockQuantRepository: Repository<StockQuant>;

  constructor() {
    this.productRepository = AppDataSource.getRepository(Product);
    this.stockQuantRepository = AppDataSource.getRepository(StockQuant);
  }

  /**
   * Get all products with filters and pagination
   */
  async findAll(
    tenantId: string,
    filters: ProductFilters = {}
  ): Promise<{ data: ProductWithRelations[]; total: number }> {
    try {
      const { search, categoryId, productType, canBeSold, canBePurchased, active, page = 1, limit = 20 } = filters;
      const skip = (page - 1) * limit;

      const queryBuilder = this.productRepository
        .createQueryBuilder('product')
        .where('product.tenantId = :tenantId', { tenantId })
        .andWhere('product.deletedAt IS NULL');

      // Apply search filter
      if (search) {
        queryBuilder.andWhere(
          '(product.name ILIKE :search OR product.code ILIKE :search OR product.barcode ILIKE :search)',
          { search: `%${search}%` }
        );
      }

      // Filter by category
      if (categoryId) {
        queryBuilder.andWhere('product.categoryId = :categoryId', { categoryId });
      }

      // Filter by product type
      if (productType) {
        queryBuilder.andWhere('product.productType = :productType', { productType });
      }

      // Filter by can be sold
      if (canBeSold !== undefined) {
        queryBuilder.andWhere('product.canBeSold = :canBeSold', { canBeSold });
      }

      // Filter by can be purchased
      if (canBePurchased !== undefined) {
        queryBuilder.andWhere('product.canBePurchased = :canBePurchased', { canBePurchased });
      }

      // Filter by active status
      if (active !== undefined) {
        queryBuilder.andWhere('product.active = :active', { active });
      }

      // Get total count
      const total = await queryBuilder.getCount();

      // Get paginated results
      const products = await queryBuilder
        .orderBy('product.name', 'ASC')
        .skip(skip)
        .take(limit)
        .getMany();

      // Note: categoryName, uomName, purchaseUomName would need joins to core schema tables
      // For now, we return the products as-is. If needed, these can be fetched with raw queries.
      const data: ProductWithRelations[] = products;

      logger.debug('Products retrieved', { tenantId, count: data.length, total });

      return { data, total };
    } catch (error) {
      logger.error('Error retrieving products', {
        error: (error as Error).message,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get product by ID
   */
  async findById(id: string, tenantId: string): Promise<ProductWithRelations> {
    try {
      const product = await this.productRepository.findOne({
        where: {
          id,
          tenantId,
          deletedAt: IsNull(),
        },
      });

      if (!product) {
        throw new NotFoundError('Producto no encontrado');
      }

      return product;
    } catch (error) {
      logger.error('Error finding product', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get product by code
   */
  async findByCode(code: string, tenantId: string): Promise<Product | null> {
    return this.productRepository.findOne({
      where: {
        code,
        tenantId,
        deletedAt: IsNull(),
      },
    });
  }

  /**
   * Create a new product
   */
  async create(dto: CreateProductDto, tenantId: string, userId: string): Promise<Product> {
    try {
      // Check unique code
      if (dto.code) {
        const existing = await this.findByCode(dto.code, tenantId);
        if (existing) {
          throw new ConflictError(`Ya existe un producto con código ${dto.code}`);
        }
      }

      // Check unique barcode
      if (dto.barcode) {
        const existingBarcode = await this.productRepository.findOne({
          where: {
            barcode: dto.barcode,
            deletedAt: IsNull(),
          },
        });
        if (existingBarcode) {
          throw new ConflictError(`Ya existe un producto con código de barras ${dto.barcode}`);
        }
      }

      // Create product
      const product = this.productRepository.create({
        tenantId,
        name: dto.name,
        code: dto.code || null,
        barcode: dto.barcode || null,
        description: dto.description || null,
        productType: dto.productType || ProductType.STORABLE,
        tracking: dto.tracking || TrackingType.NONE,
        categoryId: dto.categoryId || null,
        uomId: dto.uomId,
        purchaseUomId: dto.purchaseUomId || null,
        costPrice: dto.costPrice || 0,
        listPrice: dto.listPrice || 0,
        valuationMethod: dto.valuationMethod || ValuationMethod.FIFO,
        weight: dto.weight || null,
        volume: dto.volume || null,
        canBeSold: dto.canBeSold !== false,
        canBePurchased: dto.canBePurchased !== false,
        imageUrl: dto.imageUrl || null,
        createdBy: userId,
      });

      await this.productRepository.save(product);

      logger.info('Product created', {
        productId: product.id,
        tenantId,
        name: product.name,
        createdBy: userId,
      });

      return product;
    } catch (error) {
      logger.error('Error creating product', {
        error: (error as Error).message,
        tenantId,
        dto,
      });
      throw error;
    }
  }

  /**
   * Update a product
   */
  async update(id: string, dto: UpdateProductDto, tenantId: string, userId: string): Promise<Product> {
    try {
      const existing = await this.findById(id, tenantId);

      // Check unique barcode if changing
      if (dto.barcode !== undefined && dto.barcode !== existing.barcode) {
        if (dto.barcode) {
          const duplicate = await this.productRepository.findOne({
            where: {
              barcode: dto.barcode,
              deletedAt: IsNull(),
            },
          });

          if (duplicate && duplicate.id !== id) {
            throw new ConflictError(`Ya existe un producto con código de barras ${dto.barcode}`);
          }
        }
      }

      // Update allowed fields
      if (dto.name !== undefined) existing.name = dto.name;
      if (dto.barcode !== undefined) existing.barcode = dto.barcode;
      if (dto.description !== undefined) existing.description = dto.description;
      if (dto.tracking !== undefined) existing.tracking = dto.tracking;
      if (dto.categoryId !== undefined) existing.categoryId = dto.categoryId;
      if (dto.uomId !== undefined) existing.uomId = dto.uomId;
      if (dto.purchaseUomId !== undefined) existing.purchaseUomId = dto.purchaseUomId;
      if (dto.costPrice !== undefined) existing.costPrice = dto.costPrice;
      if (dto.listPrice !== undefined) existing.listPrice = dto.listPrice;
      if (dto.valuationMethod !== undefined) existing.valuationMethod = dto.valuationMethod;
      if (dto.weight !== undefined) existing.weight = dto.weight;
      if (dto.volume !== undefined) existing.volume = dto.volume;
      if (dto.canBeSold !== undefined) existing.canBeSold = dto.canBeSold;
      if (dto.canBePurchased !== undefined) existing.canBePurchased = dto.canBePurchased;
      if (dto.imageUrl !== undefined) existing.imageUrl = dto.imageUrl;
      if (dto.active !== undefined) existing.active = dto.active;

      existing.updatedBy = userId;
      existing.updatedAt = new Date();

      await this.productRepository.save(existing);

      logger.info('Product updated', {
        productId: id,
        tenantId,
        updatedBy: userId,
      });

      return await this.findById(id, tenantId);
    } catch (error) {
      logger.error('Error updating product', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Soft delete a product
   */
  async delete(id: string, tenantId: string, userId: string): Promise<void> {
    try {
      await this.findById(id, tenantId);

      // Check if product has stock
      const stockQuantCount = await this.stockQuantRepository
        .createQueryBuilder('sq')
        .where('sq.productId = :productId', { productId: id })
        .andWhere('sq.quantity > 0')
        .getCount();

      if (stockQuantCount > 0) {
        throw new ConflictError('No se puede eliminar un producto que tiene stock');
      }

      // Soft delete
      await this.productRepository.update(
        { id, tenantId },
        {
          deletedAt: new Date(),
          deletedBy: userId,
          active: false,
        }
      );

      logger.info('Product deleted', {
        productId: id,
        tenantId,
        deletedBy: userId,
      });
    } catch (error) {
      logger.error('Error deleting product', {
        error: (error as Error).message,
        id,
        tenantId,
      });
      throw error;
    }
  }

  /**
   * Get stock for a product
   */
  async getStock(productId: string, tenantId: string): Promise<any[]> {
    try {
      await this.findById(productId, tenantId);

      const stock = await this.stockQuantRepository
        .createQueryBuilder('sq')
        .leftJoinAndSelect('sq.location', 'location')
        .leftJoinAndSelect('location.warehouse', 'warehouse')
        .where('sq.productId = :productId', { productId })
        .orderBy('warehouse.name', 'ASC')
        .addOrderBy('location.name', 'ASC')
        .getMany();

      // Map to include relation names
      return stock.map((sq) => ({
        id: sq.id,
        productId: sq.productId,
        locationId: sq.locationId,
        locationName: sq.location?.name,
        warehouseName: sq.location?.warehouse?.name,
        lotId: sq.lotId,
        quantity: sq.quantity,
        reservedQuantity: sq.reservedQuantity,
        createdAt: sq.createdAt,
        updatedAt: sq.updatedAt,
      }));
    } catch (error) {
      logger.error('Error getting product stock', {
        error: (error as Error).message,
        productId,
        tenantId,
      });
      throw error;
    }
  }
}

// ===== Export Singleton Instance =====

export const productsService = new ProductsService();
