import { ProductResponseAttributes } from 'src/application/models';
import { FilterAttribute } from 'src/contexts/types';
import { SelectedFilterOption } from 'src/atoms/search/searchAPI';
import { ChatSearchFilter, ChatSearchFilterItem } from './types';
import { formatFilterDisplayName } from './utils';

const getPriceBucket = (price: number, bucketSize: number) => {
  const bucket = Math.floor(price / bucketSize);
  return bucket * bucketSize;
};

const isStringArray = (value: unknown): value is string[] => {
  if (!Array.isArray(value)) {
    return false;
  }
  return value.every(item => typeof item === 'string');
};

type FilterSortFunction = (a: ChatSearchFilterItem, b: ChatSearchFilterItem) => number;

const sortFilter = (filterConfig: FilterAttribute): FilterSortFunction => {
  if (filterConfig.type === 'price') {
    return (a, b) => Number(a.filterItemId) - Number(b.filterItemId);
  }
  if (filterConfig.type === 'dynamic') {
    if (filterConfig.sorting.type === 'alphabetic') {
      return (a, b) => a.displayName.localeCompare(b.displayName);
    }
    // Sort by the number of products, if they match, sort by alphabetical
    if (filterConfig.sorting.type === 'productCount') {
      return (a, b) =>
        a.productCount === b.productCount
          ? a.displayName.localeCompare(b.displayName)
          : b.productCount - a.productCount;
    }
    if (filterConfig.sorting.type === 'custom') {
      const sortedKeys = filterConfig.sorting.map;
      return (a, b) => {
        if (sortedKeys[a.displayName] && sortedKeys[b.displayName]) {
          // If both are in the sortedKeys, sort by the index
          return sortedKeys[a.displayName] - sortedKeys[b.displayName];
        }
        // If only 'a' is in the sortedKeys, put it before the other
        if (sortedKeys[a.displayName]) {
          return -1;
        }
        // If only 'b' is in the sortedKeys, put it before the other
        if (sortedKeys[b.displayName]) {
          return 1;
        }
        // If neither are in the sortedKeys, sort by productCount
        return b.productCount - a.productCount;
      };
    }
  }
  throw new Error('Invaalid search filter configuration');
};

export class ProductFilters {
  static getFiltersForProducts(
    products: ProductResponseAttributes['attributes'][],
    filterConfigs: FilterAttribute[],
    selectedFilterOptions: SelectedFilterOption[],
  ): ChatSearchFilter[] {
    if (!products || products.length === 0) {
      return [];
    }

    const filters = filterConfigs.map(filterConfig =>
      products.reduce(
        (acc, product) => {
          // Price has special handling for the filters
          if (filterConfig.type === 'price') {
            const priceBucket = getPriceBucket(
              product.salePrice ?? product.originalPrice ?? 0,
              filterConfig.bucketSize,
            );
            const current = acc.map[priceBucket] ?? {
              filterItemId: priceBucket,
              displayName: `$${priceBucket}-$${priceBucket + filterConfig.bucketSize}`,
              productCount: 0,
            };
            acc.map[priceBucket] = { ...current, productCount: current.productCount + 1 };
            return acc;
          }

          // Assume that the "attribute" is an array of values
          const attributeValues =
            (product.filters && product.filters?.[filterConfig.attribute]) ?? [];
          if (isStringArray(attributeValues)) {
            attributeValues.forEach(val => {
              const normalizedVal = val.toLowerCase();
              const current = acc.map[normalizedVal] ?? {
                filterItemId: normalizedVal,
                displayName: formatFilterDisplayName(val), // Keep original case for display
                productCount: 0,
              };
              acc.map[normalizedVal] = { ...current, productCount: current.productCount + 1 };
            });
          }

          return acc;
        },
        {
          filterConfig,
          map: {} as Record<string, ChatSearchFilterItem>,
        },
      ),
    );

    const transformedFilters: ChatSearchFilter[] = filters.map(({ filterConfig, map }) => ({
      filterId: filterConfig.filterId,
      displayName: filterConfig.displayName,
      items: Object.values(map)
        .map(filter => ({
          filterItemId: filter.filterItemId,
          displayName: filter.displayName,
          productCount: filter.productCount,
          isSelected:
            selectedFilterOptions.some(
              option => option.id === `${filterConfig.filterId}:${filter.filterItemId}`,
            ) ?? false,
        }))
        .sort(sortFilter(filterConfig)),
    }));

    return [...transformedFilters];
  }

  static filterProducts(
    products: ProductResponseAttributes['attributes'][],
    filterConfigs: FilterAttribute[],
    selectedFilterOptions: SelectedFilterOption[],
    additiveDynamicFilters = false, // Set this to true to restore the original behavior where stacking filters has OR logic.
  ) {
    const selectedFilters = filterConfigs
      .map(filterConfig => ({
        filterConfig,
        hasSelectedOptions: selectedFilterOptions.some(
          option => option.filterId === filterConfig.filterId,
        ),
      }))
      .filter(({ hasSelectedOptions }) => hasSelectedOptions);
    return selectedFilterOptions.length === 0
      ? products
      : products.filter(product =>
          selectedFilters.reduce((acc, selectedFilter) => {
            // Finding one false removes the product
            if (!acc) {
              return acc;
            }
            // Price is special
            if (selectedFilter.filterConfig.type === 'price') {
              const priceBucket = getPriceBucket(
                product.salePrice ?? product.originalPrice ?? 0,
                selectedFilter.filterConfig.bucketSize,
              );
              const matchesPriceFilter = selectedFilterOptions.some(
                option => option.id === `price:${priceBucket}`,
              );
              if (!matchesPriceFilter) {
                return false;
              }
            }

            if (selectedFilter.filterConfig.type === 'dynamic') {
              // dynamic is misleading here. Its not just dynamic filters, but static filters because the config counts static filters as type: dynamic.
              const { filterId, attribute } = selectedFilter.filterConfig;
              const attributeValues = product.filters?.[attribute];

              if (!isStringArray(attributeValues)) {
                return false;
              }

              const isDynamicFilter = selectedFilter.filterConfig.attribute === 'dynamic'; // True dynamic filters (the ones that appear beneath the search input)
              const isSubtractiveLogic = isDynamicFilter && !additiveDynamicFilters;

              if (isSubtractiveLogic) {
                // AND logic for subtractive dynamic filters and between static filter categories
                const selectedOptionsForThisFilter = selectedFilterOptions
                  .filter(option => option.filterId === filterId)
                  .map(option => option.filterItemId.toLowerCase());

                const hasAllSelectedValues = selectedOptionsForThisFilter.every(selectedValue =>
                  attributeValues.some(val => val.toLowerCase() === selectedValue),
                );

                if (!hasAllSelectedValues) {
                  return false;
                }
              } else {
                // OR logic for static filters in the sidebar within a category.
                const matches = attributeValues.some(val =>
                  selectedFilterOptions.some(
                    option => option.id === `${filterId}:${val.toLowerCase()}`,
                  ),
                );
                if (!matches) {
                  return false;
                }
              }
            }
            return true;
          }, true),
        );
  }
}
