import { Injectable, computed, inject, signal } from '@angular/core';
import { take } from 'rxjs/operators';
import { AdminProduct, AdminProductCategoryOption, AdminProductEditorMode, AdminProductListFilters } from '../models/admin-product.model';
import { AdminProductsFormFactory } from '../services/admin-products-form.factory';
import { ADMIN_PRODUCTS_GATEWAY } from '../services/admin-products-gateway.token';
import { ProjectEditorFacade } from '../../../project-editor/facade/project-editor.facade';
import { LocalStorageService } from '../../../../core/storage/local-storage.service';

export type AdminProductsViewMode = 'table' | 'grid';
export type AdminProductsDensity = 'comfortable' | 'compact';

export interface AdminProductHealth {
  hasImages: boolean;
  hasSeo: boolean;
  hasPrice: boolean;
  hasCategory: boolean;
  hasDescription: boolean;
  hasInventory: boolean;
  completionPercent: number;
}

export interface AdminProductsDashboardStats {
  total: number;
  published: number;
  drafts: number;
  outOfStock: number;
  hidden: number;
  missingImages: number;
  missingSeo: number;
  lowQuality: number;
  recentlyEdited: AdminProduct[];
  recommendation: { labelKey: string; productId?: string };
}

const VIEW_MODE_KEY = 'admin-products:view-mode';
const DENSITY_KEY = 'admin-products:density';
const COLUMNS_KEY = 'admin-products:visible-columns';

export const ALL_PRODUCT_COLUMNS = ['sku', 'brand', 'price', 'stock', 'visibility', 'updated', 'visits'] as const;
export type AdminProductColumn = typeof ALL_PRODUCT_COLUMNS[number];

@Injectable({ providedIn: 'root' })
export class AdminProductsFacade {
  private readonly gateway = inject(ADMIN_PRODUCTS_GATEWAY);
  private readonly formFactory = inject(AdminProductsFormFactory);
  private readonly projectEditor = inject(ProjectEditorFacade);
  private readonly localStorage = inject(LocalStorageService);

  readonly viewMode = signal<AdminProductsViewMode>((this.localStorage.getItem(VIEW_MODE_KEY) as AdminProductsViewMode) || 'table');
  readonly density = signal<AdminProductsDensity>((this.localStorage.getItem(DENSITY_KEY) as AdminProductsDensity) || 'comfortable');
  readonly visibleColumns = signal<AdminProductColumn[]>(this.localStorage.getJSON<AdminProductColumn[]>(COLUMNS_KEY) ?? [...ALL_PRODUCT_COLUMNS]);

  setViewMode(mode: AdminProductsViewMode): void {
    this.viewMode.set(mode);
    this.localStorage.setItem(VIEW_MODE_KEY, mode);
  }

  setDensity(density: AdminProductsDensity): void {
    this.density.set(density);
    this.localStorage.setItem(DENSITY_KEY, density);
  }

  setColumnVisible(column: AdminProductColumn, visible: boolean): void {
    const next = visible
      ? [...new Set([...this.visibleColumns(), column])]
      : this.visibleColumns().filter(c => c !== column);
    this.visibleColumns.set(next);
    this.localStorage.setJSON(COLUMNS_KEY, next);
  }

  readonly supportedLocales = computed(() => this.projectEditor.bootstrap()?.localization.supportedLocales ?? ['en']);

  ensureLocalesLoaded(): void {
    if (!this.projectEditor.bootstrap()) {
      this.projectEditor.loadBootstrap();
    }
  }

  readonly filters = signal<AdminProductListFilters>({
    search: '',
    categoryId: null,
    visibility: 'all',
    stock: 'all',
    includeArchived: false,
    sort: (this.localStorage.getItem('admin-products:sort') as AdminProductListFilters['sort']) || 'title',
    page: 1,
    pageSize: 10,
  });
  readonly products = signal<AdminProduct[]>([]);
  readonly total = signal(0);
  readonly infiniteScroll = signal(false);
  readonly categories = signal<AdminProductCategoryOption[]>([]);
  readonly loading = signal(false);
  readonly error = signal<string | null>(null);
  readonly mutationError = signal<string | null>(null);
  readonly selectedIds = signal<string[]>([]);
  readonly draft = signal<AdminProduct | null>(null);
  readonly dirty = signal(false);
  readonly editorMode = signal<AdminProductEditorMode>('create');

  readonly hasSelection = computed(() => this.selectedIds().length > 0);

  loadList(): void {
    this.loading.set(true);
    this.error.set(null);
    this.gateway.loadProducts(this.filters()).pipe(take(1)).subscribe({
      next: result => {
        this.products.set(result.items);
        this.total.set(result.total);
        this.loading.set(false);
      },
      error: () => {
        this.products.set([]);
        this.total.set(0);
        this.loading.set(false);
        this.error.set('common.errorDescription');
      }
    });
  }

  setInfiniteScroll(enabled: boolean): void {
    this.infiniteScroll.set(enabled);
    this.updateFilters({ page: 1 });
  }

  loadMore(): void {
    if (!this.infiniteScroll()) return;
    const nextPage = this.filters().page + 1;
    this.loading.set(true);
    const nextFilters = { ...this.filters(), page: nextPage };
    this.gateway.loadProducts(nextFilters).pipe(take(1)).subscribe({
      next: result => {
        this.filters.set(nextFilters);
        this.products.update(current => [...current, ...result.items]);
        this.total.set(result.total);
        this.loading.set(false);
      },
      error: () => this.loading.set(false)
    });
  }

  archiveOne(id: string): void {
    this.gateway.archiveProduct(id).pipe(take(1)).subscribe({ next: () => { this.loadList(); this.loadDashboardStats(); } });
  }

  restoreOne(id: string): void {
    this.gateway.restoreProduct(id).pipe(take(1)).subscribe({ next: () => { this.loadList(); this.loadDashboardStats(); } });
  }

  loadCategories(): void {
    this.gateway.loadCategories().pipe(take(1)).subscribe({ next: categories => this.categories.set(categories) });
  }

  updateFilters(patch: Partial<AdminProductListFilters>): void {
    if (patch.sort) {
      this.localStorage.setItem('admin-products:sort', patch.sort);
    }
    this.filters.update(current => ({ ...current, ...patch, page: patch.page ?? 1 }));
    this.loadList();
  }

  toggleSelection(id: string, checked: boolean): void {
    this.selectedIds.update(current => checked ? [...new Set([...current, id])] : current.filter(item => item !== id));
  }

  toggleAll(checked: boolean): void {
    this.selectedIds.set(checked ? this.products().map(product => product.id) : []);
  }

  applyBulkVisibility(visible: boolean): void {
    const selected = new Set(this.selectedIds());
    const updates = this.products().filter(product => selected.has(product.id)).map(product => ({ ...product, visible, updatedAt: new Date().toISOString() }));
    updates.forEach(product => this.gateway.updateProduct(product).pipe(take(1)).subscribe());
    this.selectedIds.set([]);
    this.loadList();
    this.loadDashboardStats();
  }

  applyBulkDelete(): void {
    const ids = [...this.selectedIds()];
    ids.forEach(id => this.gateway.deleteProduct(id).pipe(take(1)).subscribe());
    this.selectedIds.set([]);
    this.loadList();
    this.loadDashboardStats();
  }

  applyBulkAssignCategory(categoryId: string): void {
    const selected = new Set(this.selectedIds());
    const updates = this.products().filter(product => selected.has(product.id)).map(product => ({ ...product, categoryId, updatedAt: new Date().toISOString() }));
    updates.forEach(product => this.gateway.updateProduct(product).pipe(take(1)).subscribe());
    this.selectedIds.set([]);
    this.loadList();
    this.loadDashboardStats();
  }

  applyBulkAssignTags(tags: string[]): void {
    const selected = new Set(this.selectedIds());
    const updates = this.products().filter(product => selected.has(product.id)).map(product => ({
      ...product,
      badges: [...new Set([...product.badges, ...tags])],
      updatedAt: new Date().toISOString(),
    }));
    updates.forEach(product => this.gateway.updateProduct(product).pipe(take(1)).subscribe());
    this.selectedIds.set([]);
    this.loadList();
    this.loadDashboardStats();
  }

  applyBulkDuplicate(): void {
    const ids = [...this.selectedIds()];
    ids.forEach(id => this.gateway.duplicateProduct(id).pipe(take(1)).subscribe());
    this.selectedIds.set([]);
    this.loadList();
    this.loadDashboardStats();
  }

  /** Client-side CSV of the currently selected rows - no backend export endpoint exists, so this never claims a server-generated file. */
  exportSelectedAsCsv(): void {
    const selected = new Set(this.selectedIds());
    const rows = this.products().filter(product => selected.has(product.id));
    const header = ['id', 'name', 'sku', 'brand', 'price', 'currency', 'quantity', 'stockStatus', 'visible', 'categoryId'];
    const lines = rows.map(product => [product.id, product.name, product.sku, product.brand, product.price, product.currency, product.quantity, product.stockStatus, product.visible, product.categoryId]
      .map(value => `"${String(value).replace(/"/g, '""')}"`).join(','));
    const csv = [header.join(','), ...lines].join('\n');
    const blob = new Blob([csv], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = 'products-export.csv';
    link.click();
    URL.revokeObjectURL(url);
  }

  health(product: AdminProduct): AdminProductHealth {
    const hasImages = product.media.images.length > 0 || product.media.gallery.length > 0;
    const hasSeo = !!(product.seo.metaTitle.trim() && product.seo.metaDescription.trim());
    const hasPrice = product.price > 0;
    const hasCategory = !!product.categoryId;
    const hasDescription = product.shortDescription.trim().length > 0;
    const hasInventory = product.quantity > 0 || product.availability.trim().length > 0;
    const checks = [hasImages, hasSeo, hasPrice, hasCategory, hasDescription, hasInventory];
    const completionPercent = Math.round((checks.filter(Boolean).length / checks.length) * 100);
    return { hasImages, hasSeo, hasPrice, hasCategory, hasDescription, hasInventory, completionPercent };
  }

  readonly dashboardStats = signal<AdminProductsDashboardStats | null>(null);

  /** Dashboard stats must reflect the whole catalog, not just the current page - loads with a page size large enough to cover the mock catalog in one call. */
  loadDashboardStats(): void {
    this.gateway.loadProducts({ ...this.filters(), page: 1, pageSize: 100000, includeArchived: true, search: '', categoryId: null, visibility: 'all', stock: 'all' })
      .pipe(take(1))
      .subscribe({ next: result => this.dashboardStats.set(this.computeDashboardStats(result.items)) });
  }

  private computeDashboardStats(products: AdminProduct[]): AdminProductsDashboardStats {
    const withHealth = products.map(product => ({ product, health: this.health(product) }));

    const missingImages = withHealth.filter(entry => !entry.health.hasImages);
    const missingSeo = withHealth.filter(entry => !entry.health.hasSeo);
    const lowQuality = withHealth.filter(entry => entry.health.completionPercent < 50);
    const recentlyEdited = [...products].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).slice(0, 5);

    let recommendation: AdminProductsDashboardStats['recommendation'];
    if (missingImages[0]) {
      recommendation = { labelKey: 'adminProducts.recommendAddImages', productId: missingImages[0].product.id };
    } else if (missingSeo[0]) {
      recommendation = { labelKey: 'adminProducts.recommendAddSeo', productId: missingSeo[0].product.id };
    } else if (products.some(p => p.stockStatus === 'out_of_stock')) {
      recommendation = { labelKey: 'adminProducts.recommendRestock', productId: products.find(p => p.stockStatus === 'out_of_stock')?.id };
    } else {
      recommendation = { labelKey: 'adminProducts.recommendNone' };
    }

    return {
      total: products.length,
      published: products.filter(p => p.visible && !p.archived).length,
      drafts: products.filter(p => !p.visible && !p.archived).length,
      outOfStock: products.filter(p => p.stockStatus === 'out_of_stock').length,
      hidden: products.filter(p => !p.visible).length,
      missingImages: missingImages.length,
      missingSeo: missingSeo.length,
      lowQuality: lowQuality.length,
      recentlyEdited,
      recommendation,
    };
  }

  startCreate(): void {
    this.editorMode.set('create');
    this.draft.set(this.formFactory.createEmpty());
    this.dirty.set(false);
  }

  loadForEdit(id: string, mode: AdminProductEditorMode = 'edit'): void {
    this.editorMode.set(mode);
    if (mode === 'duplicate') {
      this.gateway.duplicateProduct(id).pipe(take(1)).subscribe({ next: product => { this.draft.set(product); this.dirty.set(false); } });
      return;
    }

    this.gateway.loadProduct(id).pipe(take(1)).subscribe({ next: product => { this.draft.set(product ? { ...product } : null); this.dirty.set(false); } });
  }

  updateDraft(patch: Partial<AdminProduct>): void {
    this.draft.update(current => current ? ({ ...current, ...patch, updatedAt: new Date().toISOString() }) : current);
    this.dirty.set(true);
  }

  saveDraft(onSuccess?: () => void): void {
    const draft = this.draft();
    if (!draft) return;

    this.mutationError.set(null);
    const request = this.editorMode() === 'create'
      ? this.gateway.createProduct(draft)
      : this.gateway.updateProduct(draft);

    request.pipe(take(1)).subscribe({
      next: () => { this.dirty.set(false); this.loadList(); onSuccess?.(); },
      error: () => this.mutationError.set('common.errorDescription')
    });
  }

  deleteOne(id: string): void {
    this.mutationError.set(null);
    this.gateway.deleteProduct(id).pipe(take(1)).subscribe({
      next: () => this.loadList(),
      error: () => this.mutationError.set('common.errorDescription')
    });
  }
}
