import { ProductBrandNotFoundError, ProductCategoryNotFoundError } from '../domain/errors.js';
import type { NewProduct, Product, ProductPatch } from '../domain/product.js';
import type { ProductRepository, ProductSearchRepository } from '../domain/ports.js';

export class GetActiveProductBySlug {
  constructor(private readonly products: ProductRepository) {}

  async execute(slug: string): Promise<Product | undefined> {
    return this.products.findActiveBySlug(slug);
  }
}

export interface SearchProductsInput {
  q?: string;
  limit?: number;
  offset?: number;
  brandSlug?: string;
  categorySlug?: string;
}

export class SearchProducts {
  constructor(private readonly searchRepository: ProductSearchRepository) {}

  async execute(input: SearchProductsInput = {}): Promise<Product[]> {
    return this.searchRepository.search({
      q: input.q,
      limit: input.limit ?? 20,
      offset: input.offset ?? 0,
      activeOnly: true,
      brandSlug: input.brandSlug,
      categorySlug: input.categorySlug,
    });
  }
}

export class SuggestCorrections {
  constructor(private readonly searchRepository: ProductSearchRepository) {}

  async execute(term: string): Promise<string[]> {
    return this.searchRepository.suggestCorrections(term);
  }
}

export class CreateProduct {
  constructor(private readonly products: ProductRepository) {}

  async execute(input: NewProduct): Promise<Product> {
    await this.assertCategoriesExist(input.categoryIds ?? []);
    await this.assertBrandExists(input.brandId);
    return this.products.create({ ...input, state: input.state ?? 'draft' });
  }

  private async assertCategoriesExist(categoryIds: readonly string[]): Promise<void> {
    if (categoryIds.length === 0) {
      return;
    }
    if (!(await this.products.categoriesExist(categoryIds))) {
      throw new ProductCategoryNotFoundError();
    }
  }

  private async assertBrandExists(brandId: string | null | undefined): Promise<void> {
    if (brandId === undefined || brandId === null) {
      return;
    }
    if (!(await this.products.brandExists(brandId))) {
      throw new ProductBrandNotFoundError();
    }
  }
}

export class UpdateProduct {
  constructor(private readonly products: ProductRepository) {}

  async execute(id: string, patch: ProductPatch): Promise<Product | undefined> {
    if (patch.categoryIds !== undefined) {
      await this.assertCategoriesExist(patch.categoryIds);
    }
    if (patch.brandId !== undefined) {
      await this.assertBrandExists(patch.brandId);
    }
    return this.products.update(id, patch);
  }

  private async assertCategoriesExist(categoryIds: readonly string[]): Promise<void> {
    if (categoryIds.length === 0) {
      return;
    }
    if (!(await this.products.categoriesExist(categoryIds))) {
      throw new ProductCategoryNotFoundError();
    }
  }

  private async assertBrandExists(brandId: string | null): Promise<void> {
    if (brandId === null) {
      return;
    }
    if (!(await this.products.brandExists(brandId))) {
      throw new ProductBrandNotFoundError();
    }
  }
}
