import { FindOptionsWhere, ILike, IsNull } from 'typeorm';
import { AppDataSource } from '../../config/typeorm.js';
import { Product } from './entities/product.entity.js';
import { ProductCategory } from './entities/product-category.entity.js';

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 interface CreateProductDto {
  sku: string;
  name: string;
  description?: string;
  shortName?: string;
  barcode?: string;
  categoryId?: string;
  productType?: 'product' | 'service' | 'consumable' | 'kit';
  salePrice?: number;
  costPrice?: number;
  currency?: string;
  taxRate?: number;
  isActive?: boolean;
  isSellable?: boolean;
  isPurchasable?: boolean;
}

export interface UpdateProductDto {
  sku?: string;
  name?: string;
  description?: string | null;
  shortName?: string | null;
  barcode?: string | null;
  categoryId?: string | null;
  productType?: 'product' | 'service' | 'consumable' | 'kit';
  salePrice?: number;
  costPrice?: number;
  currency?: string;
  taxRate?: number;
  isActive?: boolean;
  isSellable?: boolean;
  isPurchasable?: boolean;
}

export interface CreateCategoryDto {
  code: string;
  name: string;
  description?: string;
  parentId?: string;
  isActive?: boolean;
}

export interface UpdateCategoryDto {
  code?: string;
  name?: string;
  description?: string | null;
  parentId?: string | null;
  isActive?: boolean;
}

class ProductsServiceClass {
  private get productRepository() {
    return AppDataSource.getRepository(Product);
  }

  private get categoryRepository() {
    return AppDataSource.getRepository(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, deletedAt: IsNull() };

    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}%`) }
      );
    } 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, deletedAt: IsNull() },
      relations: ['category'],
    });
  }

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

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

  async create(tenantId: string, dto: CreateProductDto, createdBy?: string): Promise<Product> {
    // Validate unique SKU within tenant (RLS compliance)
    const existingSku = await this.productRepository.findOne({
      where: { sku: dto.sku, tenantId, deletedAt: IsNull() },
    });
    if (existingSku) {
      throw new Error(`Product with SKU '${dto.sku}' already exists`);
    }

    // Validate unique barcode within tenant if provided (RLS compliance)
    if (dto.barcode) {
      const existingBarcode = await this.productRepository.findOne({
        where: { barcode: dto.barcode, tenantId, deletedAt: IsNull() },
      });
      if (existingBarcode) {
        throw new Error(`Product with barcode '${dto.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;

    // Validate unique SKU within tenant if changing (RLS compliance)
    if (dto.sku && dto.sku !== product.sku) {
      const existingSku = await this.productRepository.findOne({
        where: { sku: dto.sku, tenantId, deletedAt: IsNull() },
      });
      if (existingSku) {
        throw new Error(`Product with SKU '${dto.sku}' already exists`);
      }
    }

    // Validate unique barcode within tenant if changing (RLS compliance)
    if (dto.barcode && dto.barcode !== product.barcode) {
      const existingBarcode = await this.productRepository.findOne({
        where: { barcode: dto.barcode, tenantId, deletedAt: IsNull() },
      });
      if (existingBarcode) {
        throw new Error(`Product with barcode '${dto.barcode}' already exists`);
      }
    }

    Object.assign(product, { ...dto, updatedBy });
    return this.productRepository.save(product);
  }

  async delete(id: string, tenantId: string): Promise<boolean> {
    const result = await this.productRepository.softDelete({ id, tenantId });
    return (result.affected ?? 0) > 0;
  }

  async getSellableProducts(tenantId: string, limit = 50, offset = 0): Promise<{ data: Product[]; total: number }> {
    return this.findAll({ tenantId, isSellable: true, isActive: true, limit, offset });
  }

  async getPurchasableProducts(tenantId: string, limit = 50, offset = 0): Promise<{ data: Product[]; total: number }> {
    return this.findAll({ tenantId, isPurchasable: true, isActive: true, limit, offset });
  }

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

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

    const where: FindOptionsWhere<ProductCategory> = { tenantId, deletedAt: IsNull() };

    if (parentId) {
      where.parentId = parentId;
    }

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

    if (search) {
      const [data, total] = await this.categoryRepository.findAndCount({
        where: [
          { ...where, name: ILike(`%${search}%`) },
          { ...where, code: ILike(`%${search}%`) },
        ],
        take: limit,
        skip: offset,
        order: { name: 'ASC' },
      });
      return { data, total };
    }

    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, deletedAt: IsNull() },
    });
  }

  async createCategory(tenantId: string, dto: CreateCategoryDto, _createdBy?: string): Promise<ProductCategory> {
    const category = this.categoryRepository.create({
      ...dto,
      tenantId,
    });
    return this.categoryRepository.save(category);
  }

  async updateCategory(id: string, tenantId: string, dto: UpdateCategoryDto, _updatedBy?: string): Promise<ProductCategory | null> {
    const category = await this.findCategory(id, tenantId);
    if (!category) return null;
    Object.assign(category, dto);
    return this.categoryRepository.save(category);
  }

  async deleteCategory(id: string, tenantId: string): Promise<boolean> {
    const result = await this.categoryRepository.softDelete({ id, tenantId });
    return (result.affected ?? 0) > 0;
  }
}

// Export singleton instance
export const productsService = new ProductsServiceClass();
