import { Injectable, inject } from '@angular/core';
import { ParamMap, Params } from '@angular/router';
import { Observable, Subject, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators';
import { Category } from '../../../core/categories/models/category-domain.model';
import { ConfigService } from '../../../core/config/config.service';
import { Product, ProductListResult } from '../../../core/products/models/product-domain.model';
import { CategoryFacade } from '../../../facades/platform/category.facade';
import { ProductFacade } from '../../../facades/platform/product.facade';
import { TranslateService } from '../../../i18n/translate.service';
import { SearchAutocompleteService } from '../services/search-autocomplete.service';
import { SearchCacheService } from '../services/search-cache.service';
import { SearchHistoryService } from '../services/search-history.service';
import { SearchStore } from '../store/search.store';
import {
  FilterGroup,
  SearchFilterState,
  SearchHistory,
  SearchQuery,
  SearchResult,
  SearchSuggestion,
  SortOption,
} from '../models/search.model';
import { createSearchQueryKey } from '../utils/search-query-key.util';
import { SearchTrendingService } from '../services/search-trending.service';

interface LegacySearchState {
  text: string;
  sort: SearchQuery['sort'];
  layout: 'grid' | 'large-grid' | 'compact-grid' | 'grid-2' | 'grid-3' | 'grid-4' | 'compact' | 'list';
  page: number;
  pageSize: number;
  filters: SearchFilterState;
}

@Injectable({ providedIn: 'root' })
export class SearchFacade {
  private readonly productFacade = inject(ProductFacade);
  private readonly categoryFacade = inject(CategoryFacade);
  private readonly translate = inject(TranslateService);
  private readonly autocompleteService = inject(SearchAutocompleteService);
  private readonly historyService = inject(SearchHistoryService);
  private readonly trendingService = inject(SearchTrendingService);
  private readonly cacheService = inject(SearchCacheService);
  private readonly store = inject(SearchStore);
  private readonly configService = inject(ConfigService);

  private readonly metadataMemo = new Map<string, FilterGroup[]>();
  private readonly autocompleteInput$ = new Subject<{
    query: string;
    products: Product[];
    categories: Category[];
    limit: number;
  }>();

  readonly state = this.store.state;

  /**
   * Search query text stays the stable English canonical term (what the
   * backend search index matches against); only the displayed title/text
   * are translated.
   */
  get popularSearches(): SearchSuggestion[] {
    return [
      {
        id: 'popular-smartphones',
        type: 'collection',
        title: this.translate.t('search.popularSmartphones'),
        text: this.translate.t('search.popularSmartphones'),
        icon: 'trendingUp',
        target: { route: '/search', query: { q: 'Smartphones' } }
      },
      {
        id: 'popular-sneakers',
        type: 'collection',
        title: this.translate.t('search.popularSneakers'),
        text: this.translate.t('search.popularSneakers'),
        icon: 'trendingUp',
        target: { route: '/search', query: { q: 'Sneakers' } }
      },
      {
        id: 'popular-headphones',
        type: 'collection',
        title: this.translate.t('search.popularHeadphones'),
        text: this.translate.t('search.popularHeadphones'),
        icon: 'trendingUp',
        target: { route: '/search', query: { q: 'Headphones' } }
      },
      {
        id: 'popular-laptops',
        type: 'collection',
        title: this.translate.t('search.popularLaptops'),
        text: this.translate.t('search.popularLaptops'),
        icon: 'trendingUp',
        target: { route: '/search', query: { q: 'Laptops' } }
      },
    ];
  }

  constructor() {
    const history = this.historyService.getSnapshot();
    this.store.setRecentSearches(history.recent);
    this.store.setPopularSearches(this.popularSearches.map(item => item.title));

    this.autocompleteInput$
      .pipe(
        debounceTime(220),
        distinctUntilChanged((a, b) => a.query.trim().toLowerCase() === b.query.trim().toLowerCase()),
        switchMap(({ query, products, categories, limit }) => of(
          this.autocompleteService.createSuggestions(query, products, categories, limit)
        )),
      )
      .subscribe(suggestions => this.store.setSuggestions(suggestions));

    this.trendingService.loadTrending().subscribe(trending => {
      if (!trending || trending.length === 0) {
        this.store.setPopularSearches([]);
        return;
      }

      this.store.setPopularSearches(trending.map(item => item.title));
    });
  }

  search(query: SearchQuery): Observable<ProductListResult> {
    return this.loadCatalog(query);
  }

  suggestions(query: string, products: Product[], categories: Category[] = [], limit = 10): Observable<SearchSuggestion[]> {
    return of(this.autocompleteService.createSuggestions(query, products, categories, limit));
  }

  autocomplete(query: string, products: Product[], categories: Category[] = [], limit = 10): void {
    this.store.setQuery(query);
    if (this.configService.getBootstrapSnapshot()?.catalog?.suggestionsEnabled === false) {
      this.store.setSuggestions([]);
      return;
    }
    this.autocompleteInput$.next({ query, products, categories, limit });
  }

  getSearchHistory(): SearchHistory {
    const snapshot = this.historyService.getSnapshot();
    return {
      items: snapshot.items,
      recent: snapshot.recent,
      popular: this.popularSearches.map(item => item.title),
    };
  }

  pushSearchHistory(term: string, maxHistory = 12): SearchHistory {
    const snapshot = this.historyService.push(term, maxHistory);
    this.store.setRecentSearches(snapshot.recent);
    return {
      ...snapshot,
      popular: this.popularSearches.map(item => item.title),
    };
  }

  clearSearchHistory(): SearchHistory {
    const snapshot = this.historyService.clear();
    this.store.setRecentSearches([]);
    return {
      ...snapshot,
      popular: this.popularSearches.map(item => item.title),
    };
  }

  trending(): Observable<SearchSuggestion[] | null> {
    return this.trendingService.loadTrending();
  }

  loadCatalog(query: SearchQuery): Observable<ProductListResult> {
    const key = createSearchQueryKey(query);
    const cached = this.cacheService.get(key);
    if (cached) {
      return of(cached);
    }

    return this.productFacade.loadCatalog({
      keyword: query.text,
      categoryIDs: query.categoryIds,
      subcategoryIDs: query.subcategoryIds,
      minPrice: query.filters.ranges['price']?.min,
      maxPrice: query.filters.ranges['price']?.max,
      discountOnly: query.filters.toggles['discount'],
      newOnly: query.filters.toggles['new'],
      sort: query.sort,
      page: query.page,
      pageSize: query.pageSize,
    }).pipe(
      tap(result => this.cacheService.set(key, result))
    );
  }

  createSortOptions(availableSorts: string[]): SortOption[] {
    const labels: Record<string, string> = {
      relevance: this.translate.t('catalog.sortRelevance'),
      latest: this.translate.t('catalog.sortLatest'),
      price_asc: this.translate.t('catalog.sortPriceAsc'),
      price_desc: this.translate.t('catalog.sortPriceDesc'),
      rating: this.translate.t('catalog.sortRating'),
      popular: this.translate.t('catalog.sortPopular'),
      discount: this.translate.t('catalog.sortDiscount'),
    };

    return availableSorts.map(id => ({
      id: id as SortOption['id'],
      label: labels[id] ?? id,
      enabled: true,
    }));
  }

  buildFilterGroups(products: Product[], enabledFilters: string[]): FilterGroup[] {
    const enabled = new Set(enabledFilters);
    const key = `${enabledFilters.join('|')}::${products.map(item => item.itemID).join(',')}`;
    const cached = this.metadataMemo.get(key);
    if (cached) {
      return cached;
    }

    const priceValues = products.map(product => product.price).filter(value => Number.isFinite(value));
    const brands = this.collectUnique(products.flatMap(product => product.tags ?? []).filter(tag => !tag.startsWith('new') && !tag.startsWith('sale')));
    const colors = this.collectUnique(products.flatMap(product => [product.colour, ...(product.itemDetails?.map(detail => detail.colour || detail.color) ?? [])]).filter(Boolean) as string[]);
    const sizes = this.collectUnique(products.flatMap(product => [product.size, ...(product.itemDetails?.map(detail => detail.size) ?? [])]).filter(Boolean) as string[]);
    const categories = this.collectUnique(products.map(product => String(product.categoryID)).filter(Boolean));
    const subcategories = this.collectUnique(products.map(product => product.subcategoryId ?? '').filter(Boolean));
    const attributes = this.collectUnique(products.flatMap(product => (product.descriptionFields ?? []).map(field => field.key)));
    const availabilityCounts = products.reduce<Record<string, number>>((acc, product) => {
      const state = product.remainings === 'out'
        ? 'out-of-stock'
        : product.remainings === 'low'
          ? 'low-stock'
          : 'in-stock';
      acc[state] = (acc[state] ?? 0) + 1;
      return acc;
    }, {});
    const availabilityBase = [
      { id: 'in-stock', label: this.translate.t('catalog.filterInStock'), value: 'in-stock', availability: 'in-stock' as const },
      { id: 'low-stock', label: this.translate.t('catalog.filterLowStock'), value: 'low-stock', availability: 'low-stock' as const },
      { id: 'out-of-stock', label: this.translate.t('catalog.filterOutOfStock'), value: 'out-of-stock', availability: 'out-of-stock' as const },
    ];
    const availabilityOptions = availabilityBase
      .filter(option => (availabilityCounts[option.value] ?? 0) > 0)
      .map(option => ({ ...option, count: availabilityCounts[option.value] }));
    const ratingOptions = this.collectUnique(
      products
        .map(product => String(Math.max(1, Math.min(5, Math.floor(product.rating || 0)))))
        .filter(value => Number(value) > 0)
    )
      .map(value => Number(value))
      .sort((a, b) => b - a)
      .map(value => ({
        id: `rating-${value}`,
        label: this.translate.t('catalog.filterStars', { count: value }),
        value: String(value),
        rating: value,
      }));

    const groups: FilterGroup[] = [
      {
        id: 'price',
        label: this.translate.t('catalog.filterPrice'),
        type: 'range',
        min: priceValues.length ? Math.min(...priceValues) : 0,
        max: priceValues.length ? Math.max(...priceValues) : 0,
        step: 1,
        options: [],
        enabled: enabled.has('price'),
      },
      {
        id: 'availability',
        label: this.translate.t('catalog.filterAvailability'),
        type: 'availability',
        options: availabilityOptions,
        enabled: enabled.has('availability'),
      },
      {
        id: 'rating',
        label: this.translate.t('catalog.filterRating'),
        type: 'rating',
        options: ratingOptions,
        enabled: enabled.has('rating'),
      },
      {
        id: 'brand',
        label: this.translate.t('catalog.filterBrand'),
        type: 'checkbox',
        options: brands.map(value => ({ id: `brand-${value}`, label: value, value })),
        enabled: enabled.has('brand'),
      },
      {
        id: 'category',
        label: this.translate.t('catalog.filterCategory'),
        type: 'radio',
        options: categories
          .map(value => ({ id: `cat-${value}`, label: this.resolveCategoryLabel(value), value }))
          .filter((option): option is { id: string; label: string; value: string } => option.label !== null),
        enabled: enabled.has('category'),
      },
      {
        id: 'subcategory',
        label: this.translate.t('catalog.filterSubcategory'),
        type: 'checkbox',
        options: subcategories.map(value => ({ id: `sub-${value}`, label: value, value })),
        enabled: enabled.has('subcategory'),
      },
      {
        id: 'discount',
        label: this.translate.t('catalog.filterDiscount'),
        type: 'toggle',
        options: [],
        enabled: enabled.has('discount'),
      },
      {
        id: 'new',
        label: this.translate.t('catalog.filterNew'),
        type: 'toggle',
        options: [],
        enabled: enabled.has('new'),
      },
      {
        id: 'color',
        label: this.translate.t('catalog.filterColor'),
        type: 'color',
        options: colors.map(value => ({ id: `color-${value}`, label: value, value, colorHex: this.toColorHex(value) })),
        enabled: enabled.has('color'),
      },
      {
        id: 'size',
        label: this.translate.t('catalog.filterSize'),
        type: 'size',
        options: sizes.map(value => ({ id: `size-${value}`, label: value, value })),
        enabled: enabled.has('size'),
      },
      {
        id: 'attributes',
        label: this.translate.t('catalog.filterAttributes'),
        type: 'checkbox',
        options: attributes.map(value => ({ id: `attr-${value}`, label: value, value })),
        enabled: enabled.has('attributes'),
      },
    ];

    this.metadataMemo.set(key, groups);
    if (this.metadataMemo.size > 15) {
      const firstKey = this.metadataMemo.keys().next().value;
      if (firstKey) {
        this.metadataMemo.delete(firstKey);
      }
    }

    return groups;
  }

  buildLiveSuggestions(query: string, products: Product[], limit = 6): SearchSuggestion[] {
    return this.autocompleteService.createSuggestions(query, products, [], limit)
      .map(item => ({ ...item, text: item.title }));
  }

  applyFilters(products: Product[], filterState: SearchFilterState, searchText: string): Product[] {
    const query = searchText.trim().toLowerCase();

    return products.filter(product => {
      if (query.length > 0) {
        const haystack = `${product.name} ${product.simpleDescription ?? ''}`.toLowerCase();
        if (!haystack.includes(query)) {
          return false;
        }
      }

      const priceRange = filterState.ranges['price'];
      if (priceRange?.min != null && product.price < priceRange.min) {
        return false;
      }
      if (priceRange?.max != null && product.price > priceRange.max) {
        return false;
      }

      if (filterState.toggles['discount'] && !(product.discount > 0)) {
        return false;
      }

      if (filterState.toggles['new'] && !(product.badges ?? []).some(badge => badge.toLowerCase().includes('new'))) {
        return false;
      }

      const selectedRatings = filterState.values['rating'] ?? [];
      if (selectedRatings.length > 0 && !selectedRatings.includes(String(Math.floor(product.rating || 0)))) {
        return false;
      }

      const selectedAvailability = filterState.values['availability'] ?? [];
      if (selectedAvailability.length > 0) {
        const availability = product.remainings === 'out'
          ? 'out-of-stock'
          : product.remainings === 'low'
            ? 'low-stock'
            : 'in-stock';

        if (!selectedAvailability.includes(availability)) {
          return false;
        }
      }

      const selectedBrands = filterState.values['brand'] ?? [];
      if (selectedBrands.length > 0) {
        const tags = product.tags ?? [];
        if (!selectedBrands.some(brand => tags.includes(brand))) {
          return false;
        }
      }

      const selectedCategories = filterState.values['category'] ?? [];
      if (selectedCategories.length > 0 && !selectedCategories.includes(String(product.categoryID))) {
        return false;
      }

      const selectedSubcategories = filterState.values['subcategory'] ?? [];
      if (selectedSubcategories.length > 0 && !selectedSubcategories.includes(product.subcategoryId ?? '')) {
        return false;
      }

      const selectedColors = filterState.values['color'] ?? [];
      if (selectedColors.length > 0) {
        const colors = this.collectUnique([product.colour ?? '', ...(product.itemDetails?.map(detail => detail.colour || detail.color || '') ?? [])]);
        if (!selectedColors.some(color => colors.includes(color))) {
          return false;
        }
      }

      const selectedSizes = filterState.values['size'] ?? [];
      if (selectedSizes.length > 0) {
        const sizes = this.collectUnique([product.size ?? '', ...(product.itemDetails?.map(detail => detail.size || '') ?? [])]);
        if (!selectedSizes.some(size => sizes.includes(size))) {
          return false;
        }
      }

      const selectedAttributes = filterState.values['attributes'] ?? [];
      if (selectedAttributes.length > 0) {
        const keys = (product.descriptionFields ?? []).map(field => field.key);
        if (!selectedAttributes.some(attribute => keys.includes(attribute))) {
          return false;
        }
      }

      return true;
    });
  }

  applySort(products: Product[], sort: LegacySearchState['sort']): Product[] {
    const sorted = [...products];

    switch (sort) {
      case 'price_asc':
        return sorted.sort((a, b) => a.price - b.price);
      case 'price_desc':
        return sorted.sort((a, b) => b.price - a.price);
      case 'rating':
        return sorted.sort((a, b) => (b.rating || 0) - (a.rating || 0));
      case 'popular':
        return sorted.sort((a, b) => (b.callbacks?.length ?? 0) - (a.callbacks?.length ?? 0));
      case 'discount':
        return sorted.sort((a, b) => (b.discount || 0) - (a.discount || 0));
      case 'latest':
      case 'relevance':
      default:
        return sorted;
    }
  }

  paginateProducts(products: Product[], page: number, pageSize: number): { items: Product[]; skip: number; page: number; pageSize: number } {
    const safePage = Math.max(1, page);
    const safePageSize = Math.max(1, pageSize);
    const skip = (safePage - 1) * safePageSize;

    return {
      items: products.slice(skip, skip + safePageSize),
      skip,
      page: safePage,
      pageSize: safePageSize,
    };
  }

  buildResult(state: LegacySearchState, total: number, items: Product[]): SearchResult<Product> {
    const result: SearchResult<Product> = {
      query: {
        text: state.text,
        categoryIds: [],
        subcategoryIds: [],
        filters: state.filters,
        sort: state.sort,
        page: state.page,
        pageSize: state.pageSize,
      },
      items,
      total,
      page: state.page,
      pageSize: state.pageSize,
      summary: state.text.trim().length > 0
        ? this.translate.t('catalog.searchResultsSummary', { query: state.text.trim(), total })
        : this.translate.t('catalog.searchProductsFound', { total }),
    };

    this.store.setResults(result);
    return result;
  }

  toQueryParams(state: LegacySearchState): Params {
    return {
      q: state.text || null,
      sort: state.sort || null,
      page: state.page > 1 ? state.page : null,
      layout: state.layout !== 'grid' ? state.layout : null,
      fv: this.serializeObject(state.filters.values),
      fr: this.serializeObject(state.filters.ranges),
      ft: this.serializeObject(state.filters.toggles),
    };
  }

  fromQueryParams(paramMap: ParamMap): Partial<LegacySearchState> {
    const values = this.deserializeObject<Record<string, string[]>>(paramMap.get('fv')) ?? {};
    const ranges = this.deserializeObject<Record<string, { min?: number; max?: number }>>(paramMap.get('fr')) ?? {};
    const toggles = this.deserializeObject<Record<string, boolean>>(paramMap.get('ft')) ?? {};
    const page = Number(paramMap.get('page'));

    return {
      text: (paramMap.get('q') ?? '').trim(),
      sort: (paramMap.get('sort') as LegacySearchState['sort']) ?? 'relevance',
      layout: (paramMap.get('layout') as LegacySearchState['layout']) ?? 'grid',
      page: Number.isFinite(page) && page > 0 ? page : 1,
      filters: {
        values,
        ranges,
        toggles,
      },
    };
  }

  private serializeObject(value: unknown): string | null {
    if (!value || typeof value !== 'object' || Object.keys(value as object).length === 0) {
      return null;
    }

    return encodeURIComponent(JSON.stringify(value));
  }

  private deserializeObject<T>(value: string | null): T | null {
    if (!value) {
      return null;
    }

    try {
      return JSON.parse(decodeURIComponent(value)) as T;
    } catch {
      return null;
    }
  }

  private collectUnique(values: string[]): string[] {
    return [...new Set(values.map(value => value.trim()).filter(Boolean))];
  }

  /**
   * Returns the real category title, or null when the id has no match in the loaded
   * category tree — callers drop unresolved options instead of showing a fabricated
   * "Category {id}" label (see RELEASE-CANDIDATE-AUDIT.md P0-4).
   */
  private resolveCategoryLabel(categoryId: string): string | null {
    const id = Number(categoryId);
    if (!Number.isFinite(id)) {
      return null;
    }
    return this.categoryFacade.allCategories().find(category => category.id === id)?.title ?? null;
  }

  private toColorHex(value: string): string {
    const normalized = value.trim().toLowerCase();

    if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(normalized)) {
      return normalized;
    }

    const palette: Record<string, string> = {
      black: '#111111',
      white: '#ffffff',
      red: '#ef4444',
      blue: '#3b82f6',
      green: '#22c55e',
      yellow: '#eab308',
      orange: '#f97316',
      purple: '#8b5cf6',
      pink: '#ec4899',
      gray: '#6b7280',
      grey: '#6b7280',
      brown: '#92400e',
      beige: '#d6c6a8',
      navy: '#1e3a8a',
      cyan: '#06b6d4',
      teal: '#0d9488',
      lime: '#65a30d',
      olive: '#4d7c0f',
      maroon: '#7f1d1d',
      silver: '#94a3b8',
      gold: '#eab308',
    };

    if (palette[normalized]) {
      return palette[normalized];
    }

    const tokens = normalized.split(/[\s,\-/]+/).filter(Boolean);
    for (const token of tokens) {
      if (palette[token]) {
        return palette[token];
      }
    }

    return '#94a3b8';
  }
}
