import { Repository, FindOptionsWhere, ILike } from 'typeorm';
import { Product, ProductCategory } from '../entities';
import {
  CreateProductDto,
  UpdateProductDto,
  CreateProductCategoryDto,
  UpdateProductCategoryDto,
} from '../dto';

export interface ProductSearchParams {
  tenantId: string;
  search?: string;
  categoryId?: string;
  productType?: 'product' | 'service' | 'consumable' | 'kit';
  isActive?: boolean;
  isSellable?: boolean;
  isPurchasable?: boolean;
  limit?: number;
  offset?: number;
}

export interface CategorySearchParams {
  tenantId: string;
  search?: string;
  parentId?: string;
  isActive?: boolean;
  limit?: number;
  offset?: number;
}

export class ProductsService {
  constructor(
    private readonly productRepository: Repository<Product>,
    private readonly categoryRepository: Repository<ProductCategory>
  ) {}

  // ==================== Products ====================

  async findAll(params: ProductSearchParams): Promise<{ data: Product[]; total: number }> {
    const {
      tenantId,
      search,
      categoryId,
      productType,
      isActive,
      isSellable,
      isPurchasable,
      limit = 50,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<Product>[] = [];
    const baseWhere: FindOptionsWhere<Product> = { tenantId };

    if (categoryId) {
      baseWhere.categoryId = categoryId;
    }

    if (productType) {
      baseWhere.productType = productType;
    }

    if (isActive !== undefined) {
      baseWhere.isActive = isActive;
    }

    if (isSellable !== undefined) {
      baseWhere.isSellable = isSellable;
    }

    if (isPurchasable !== undefined) {
      baseWhere.isPurchasable = isPurchasable;
    }

    if (search) {
      where.push(
        { ...baseWhere, name: ILike(`%${search}%`) },
        { ...baseWhere, sku: ILike(`%${search}%`) },
        { ...baseWhere, barcode: ILike(`%${search}%`) },
        { ...baseWhere, description: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

    const [data, total] = await this.productRepository.findAndCount({
      where,
      relations: ['category'],
      take: limit,
      skip: offset,
      order: { name: 'ASC' },
    });

    return { data, total };
  }

  async findOne(id: string, tenantId: string): Promise<Product | null> {
    return this.productRepository.findOne({
      where: { id, tenantId },
      relations: ['category'],
    });
  }

  async findBySku(sku: string, tenantId: string): Promise<Product | null> {
    return this.productRepository.findOne({
      where: { sku, tenantId },
      relations: ['category'],
    });
  }

  async findByBarcode(barcode: string, tenantId: string): Promise<Product | null> {
    return this.productRepository.findOne({
      where: { barcode, tenantId },
      relations: ['category'],
    });
  }

  async create(tenantId: string, dto: CreateProductDto, createdBy?: string): Promise<Product> {
    // Check for existing SKU
    const existingSku = await this.findBySku(dto.sku, tenantId);
    if (existingSku) {
      throw new Error('A product with this SKU already exists');
    }

    // Check for existing barcode
    if (dto.barcode) {
      const existingBarcode = await this.findByBarcode(dto.barcode, tenantId);
      if (existingBarcode) {
        throw new Error('A product with this barcode already exists');
      }
    }

    const product = this.productRepository.create({
      ...dto,
      tenantId,
      createdBy,
    });

    return this.productRepository.save(product);
  }

  async update(
    id: string,
    tenantId: string,
    dto: UpdateProductDto,
    updatedBy?: string
  ): Promise<Product | null> {
    const product = await this.findOne(id, tenantId);
    if (!product) return null;

    // If changing SKU, check for duplicates
    if (dto.sku && dto.sku !== product.sku) {
      const existing = await this.findBySku(dto.sku, tenantId);
      if (existing) {
        throw new Error('A product with this SKU already exists');
      }
    }

    // If changing barcode, check for duplicates
    if (dto.barcode && dto.barcode !== product.barcode) {
      const existing = await this.findByBarcode(dto.barcode, tenantId);
      if (existing && existing.id !== id) {
        throw new Error('A product with this barcode already exists');
      }
    }

    Object.assign(product, {
      ...dto,
      updatedBy,
    });

    return this.productRepository.save(product);
  }

  async delete(id: string, tenantId: string): Promise<boolean> {
    const product = await this.findOne(id, tenantId);
    if (!product) return false;

    const result = await this.productRepository.softDelete(id);
    return (result.affected ?? 0) > 0;
  }

  async getSellableProducts(tenantId: string): Promise<Product[]> {
    return this.productRepository.find({
      where: { tenantId, isActive: true, isSellable: true },
      relations: ['category'],
      order: { name: 'ASC' },
    });
  }

  async getPurchasableProducts(tenantId: string): Promise<Product[]> {
    return this.productRepository.find({
      where: { tenantId, isActive: true, isPurchasable: true },
      relations: ['category'],
      order: { name: 'ASC' },
    });
  }

  // ==================== Categories ====================

  async findAllCategories(
    params: CategorySearchParams
  ): Promise<{ data: ProductCategory[]; total: number }> {
    const { tenantId, search, parentId, isActive, limit = 100, offset = 0 } = params;

    const where: FindOptionsWhere<ProductCategory>[] = [];
    const baseWhere: FindOptionsWhere<ProductCategory> = { tenantId };

    if (parentId !== undefined) {
      baseWhere.parentId = parentId || undefined;
    }

    if (isActive !== undefined) {
      baseWhere.isActive = isActive;
    }

    if (search) {
      where.push(
        { ...baseWhere, name: ILike(`%${search}%`) },
        { ...baseWhere, code: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

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

    return { data, total };
  }

  async findCategory(id: string, tenantId: string): Promise<ProductCategory | null> {
    return this.categoryRepository.findOne({ where: { id, tenantId } });
  }

  async findCategoryByCode(code: string, tenantId: string): Promise<ProductCategory | null> {
    return this.categoryRepository.findOne({ where: { code, tenantId } });
  }

  async createCategory(
    tenantId: string,
    dto: CreateProductCategoryDto
  ): Promise<ProductCategory> {
    // Check for existing code
    const existingCode = await this.findCategoryByCode(dto.code, tenantId);
    if (existingCode) {
      throw new Error('A category with this code already exists');
    }

    // Calculate hierarchy if parent exists
    let hierarchyPath = `/${dto.code}`;
    let hierarchyLevel = 0;

    if (dto.parentId) {
      const parent = await this.findCategory(dto.parentId, tenantId);
      if (parent) {
        hierarchyPath = `${parent.hierarchyPath}/${dto.code}`;
        hierarchyLevel = parent.hierarchyLevel + 1;
      }
    }

    const category = this.categoryRepository.create({
      ...dto,
      tenantId,
      hierarchyPath,
      hierarchyLevel,
    });

    return this.categoryRepository.save(category);
  }

  async updateCategory(
    id: string,
    tenantId: string,
    dto: UpdateProductCategoryDto
  ): Promise<ProductCategory | null> {
    const category = await this.findCategory(id, tenantId);
    if (!category) return null;

    // If changing code, check for duplicates
    if (dto.code && dto.code !== category.code) {
      const existing = await this.findCategoryByCode(dto.code, tenantId);
      if (existing) {
        throw new Error('A category with this code already exists');
      }
    }

    Object.assign(category, dto);
    return this.categoryRepository.save(category);
  }

  async deleteCategory(id: string, tenantId: string): Promise<boolean> {
    const category = await this.findCategory(id, tenantId);
    if (!category) return false;

    // Check if category has children
    const children = await this.categoryRepository.findOne({
      where: { parentId: id, tenantId },
    });
    if (children) {
      throw new Error('Cannot delete category with children');
    }

    // Check if category has products
    const products = await this.productRepository.findOne({
      where: { categoryId: id, tenantId },
    });
    if (products) {
      throw new Error('Cannot delete category with products');
    }

    const result = await this.categoryRepository.softDelete(id);
    return (result.affected ?? 0) > 0;
  }

  async getCategoryTree(tenantId: string): Promise<ProductCategory[]> {
    const categories = await this.categoryRepository.find({
      where: { tenantId, isActive: true },
      order: { hierarchyPath: 'ASC', sortOrder: 'ASC' },
    });

    return categories;
  }
}
