import { Injectable, inject } from '@angular/core';
import { Observable, of, map, switchMap, catchError } from 'rxjs';
import { CategoryFacade } from '../../facades/platform/category.facade';
import { ProductFacade } from '../../facades/platform/product.facade';
import { LanguageService } from '../../services/language.service';
import { LocalizedTextContent, SectionConfig, WidgetConfig } from '../../shared/models/config';
import { WidgetManifestEntry, WidgetDataSourceName } from '../contracts/widget-manifest.contract';
import { BannerWidgetData, CategoriesWidgetData, FooterWidgetData, HeroWidgetData, HtmlWidgetData, PartnersWidgetData, ProductCollectionWidgetData } from '../contracts/widget-data.contract';
import { WidgetManifestService } from '../registry/widget-manifest.service';
import { Category } from '../../core/categories/models/category-domain.model';
import { Product } from '../../core/products/models/product-domain.model';

@Injectable({ providedIn: 'root' })
export class DataSourceResolverService {
  private readonly widgetManifest = inject(WidgetManifestService);
  private readonly categoryFacade = inject(CategoryFacade);
  private readonly productFacade = inject(ProductFacade);
  private readonly languageService = inject(LanguageService);

  resolve(widget: WidgetConfig, section: SectionConfig): Observable<unknown> {
    return this.widgetManifest.getWidget(widget.type).pipe(
      switchMap((definition) => this.resolveByDefinition(definition, widget, section)),
      catchError((error) => {
        console.error(`Failed to resolve widget data for ${widget.type}:${widget.id}`, error);
        return of({ section, settings: {} });
      })
    );
  }

  private resolveByDefinition(definition: WidgetManifestEntry | undefined, widget: WidgetConfig, section: SectionConfig): Observable<unknown> {
    const settings = this.resolveSettings(definition, widget);
    const source = this.resolveSource(widget, settings, definition);

    switch (widget.type) {
      case 'hero':
        return of(this.toHeroData(section, settings));
      case 'categories':
        return this.resolveCategories(section, widget, settings, source);
      case 'product-collection':
      case 'product-carousel':
        return this.resolveProductCollection(section, widget, settings, source);
      case 'banner':
        return of(this.toBannerData(section, settings));
      case 'html':
        return of(this.toHtmlData(section, settings));
      case 'partners':
        return of(this.toPartnersData(section, settings));
      case 'footer':
      case 'footer-navigation':
        return of(this.toFooterData(section, settings));
      case 'recently-viewed':
        return of({ section, settings });
      default:
        return of({ section, settings });
    }
  }

  private resolveSettings(definition: WidgetManifestEntry | undefined, widget: WidgetConfig): Record<string, unknown> {
    const metadataSettings: Record<string, unknown> = {};

    if (widget.title != null) {
      metadataSettings['title'] = widget.title;
    }

    if (widget.subtitle != null) {
      metadataSettings['subtitle'] = widget.subtitle;
    }

    if (widget.animation != null) {
      metadataSettings['animation'] = widget.animation;
    }

    if (widget.style != null) {
      metadataSettings['style'] = widget.style;
    }

    if (widget.permissions != null) {
      metadataSettings['permissions'] = widget.permissions;
    }

    return {
      ...(definition?.defaultSettings ?? {}),
      ...metadataSettings,
      ...(widget.props ?? {})
    };
  }

  private resolveSource(widget: WidgetConfig, settings: Record<string, unknown>, definition?: WidgetManifestEntry): WidgetDataSourceName | undefined {
    const source = settings['source'] ?? widget.props?.['source'] ?? definition?.defaultSettings?.['source'];
    return typeof source === 'string' ? source as WidgetDataSourceName : undefined;
  }

  /**
   * Hero text props accept either a plain string (existing tenants, unchanged) or a
   * per-locale map (`{ ru: '...', en: '...' }`, same shape as NavigationLocalizedText)
   * so the hero doesn't render in one hardcoded language on every locale.
   */
  private resolveLocalizedWidgetText(value: unknown): string | undefined {
    if (value == null) {
      return undefined;
    }
    if (typeof value !== 'object') {
      return String(value);
    }
    const lang = this.languageService.currentLanguage();
    const text = value as LocalizedTextContent;
    return text[lang] ?? text['en'] ?? Object.values(text)[0];
  }

  private toHeroData(section: SectionConfig, settings: Record<string, unknown>): HeroWidgetData {
    const rawSlides = settings['slides'];
    const slides = Array.isArray(rawSlides)
      ? rawSlides
          .filter((slide): slide is Record<string, unknown> => !!slide && typeof slide === 'object')
          .map(slide => ({
            title: this.resolveLocalizedWidgetText(slide['title']) ?? '',
            subtitle: this.resolveLocalizedWidgetText(slide['subtitle']),
            ctaLabel: this.resolveLocalizedWidgetText(slide['ctaLabel']),
          }))
          .filter(slide => slide.title)
      : undefined;

    return {
      section,
      settings,
      title: this.resolveLocalizedWidgetText(settings['title']) ?? '',
      subtitle: this.resolveLocalizedWidgetText(settings['subtitle']),
      ctaLabel: this.resolveLocalizedWidgetText(settings['ctaLabel']),
      slides,
      autoplay: settings['autoplay'] === true,
    };
  }

  private resolveCategories(section: SectionConfig, widget: WidgetConfig, settings: Record<string, unknown>, source?: WidgetDataSourceName): Observable<CategoriesWidgetData> {
    const title = String(settings['title'] ?? '');
    const emptyMessage = settings['emptyMessage'] != null ? String(settings['emptyMessage']) : undefined;

    switch (source) {
      case 'parent': {
        const parentId = this.toNumber(settings['parentId']);
        if (parentId == null) {
          return of({ section, settings, title, categories: [], emptyMessage });
        }

        return this.categoryFacade.getChildren(parentId).pipe(
          map((categories) => ({ section, settings, title, categories, emptyMessage }))
        );
      }
      case 'manual': {
        const categories = Array.isArray(settings['categories']) ? settings['categories'] as Category[] : [];
        return of({ section, settings, title, categories, emptyMessage });
      }
      case 'future':
        return of({ section, settings, title, categories: [], emptyMessage });
      case 'root':
      default:
        return this.categoryFacade.getRootCategories().pipe(
          map((categories) => ({ section, settings, title, categories, emptyMessage }))
        );
    }
  }

  private resolveProductCollection(section: SectionConfig, widget: WidgetConfig, settings: Record<string, unknown>, source?: WidgetDataSourceName): Observable<ProductCollectionWidgetData> {
    const title = String(settings['title'] ?? '');
    const actionLabel = settings['actionLabel'] != null ? String(settings['actionLabel']) : undefined;
    const emptyMessage = settings['emptyMessage'] != null ? String(settings['emptyMessage']) : undefined;
    const count = this.toNumber(settings['count']) ?? 8;
    const categoryId = this.toNumber(settings['categoryId']);

    switch (source) {
      case 'latest':
        return this.productFacade.getLatestProducts({ count }).pipe(
          map((result) => this.toProductCollectionData(section, settings, title, result.items, actionLabel, emptyMessage))
        );
      case 'category':
        if (categoryId == null) {
          return of(this.toProductCollectionData(section, settings, title, [], actionLabel, emptyMessage));
        }

        return this.productFacade.getProductsByCategory(categoryId, { count }).pipe(
          map((result) => this.toProductCollectionData(section, settings, title, result.items, actionLabel, emptyMessage))
        );
      case 'related': {
        const productId = this.toNumber(settings['productId']);
        if (productId == null) {
          return of(this.toProductCollectionData(section, settings, title, [], actionLabel, emptyMessage));
        }

        return this.productFacade.getRelatedProducts({
          productID: productId,
          categoryID: categoryId ?? undefined,
          count
        }).pipe(
          map((result) => this.toProductCollectionData(section, settings, title, result.items, actionLabel, emptyMessage))
        );
      }
      case 'manual': {
        const products = Array.isArray(settings['products']) ? settings['products'] as Product[] : [];
        return of(this.toProductCollectionData(section, settings, title, products, actionLabel, emptyMessage));
      }
      case 'future':
        return of(this.toProductCollectionData(section, settings, title, [], actionLabel, emptyMessage));
      case 'featured':
      default:
        return this.productFacade.getFeaturedProducts({ count }).pipe(
          map((result) => this.toProductCollectionData(section, settings, title, result.items, actionLabel, emptyMessage))
        );
    }
  }

  private toProductCollectionData(section: SectionConfig, settings: Record<string, unknown>, title: string, products: Product[], actionLabel?: string, emptyMessage?: string): ProductCollectionWidgetData {
    return {
      section,
      settings,
      title,
      products,
      actionLabel,
      emptyMessage
    };
  }

  private toBannerData(section: SectionConfig, settings: Record<string, unknown>): BannerWidgetData {
    return {
      section,
      settings,
      title: settings['title'] != null ? String(settings['title']) : undefined,
      subtitle: settings['subtitle'] != null ? String(settings['subtitle']) : undefined,
      imageUrl: settings['imageUrl'] != null ? String(settings['imageUrl']) : undefined,
      ctaLabel: settings['ctaLabel'] != null ? String(settings['ctaLabel']) : undefined,
      ctaHref: settings['ctaHref'] != null ? String(settings['ctaHref']) : undefined
    };
  }

  private toHtmlData(section: SectionConfig, settings: Record<string, unknown>): HtmlWidgetData {
    return {
      section,
      settings,
      html: String(settings['html'] ?? '')
    };
  }

  private toPartnersData(section: SectionConfig, settings: Record<string, unknown>): PartnersWidgetData {
    const logos = Array.isArray(settings['logos']) ? settings['logos'] as PartnersWidgetData['logos'] : [];

    return {
      section,
      settings,
      title: settings['title'] != null ? String(settings['title']) : undefined,
      logos
    };
  }

  private toFooterData(section: SectionConfig, settings: Record<string, unknown>): FooterWidgetData {
    const links = Array.isArray(settings['links']) ? settings['links'] as FooterWidgetData['links'] : [];

    return {
      section,
      settings,
      links
    };
  }

  private toNumber(value: unknown): number | null {
    if (typeof value === 'number' && Number.isFinite(value)) {
      return value;
    }

    if (typeof value === 'string' && value.trim().length > 0) {
      const parsed = Number(value);
      return Number.isFinite(parsed) ? parsed : null;
    }

    return null;
  }
}