import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, forkJoin, map, of, switchMap, from, catchError, EMPTY } from 'rxjs';
import { environment } from '../../../../environments/environment';
import { PosProduct, PosCategory } from '../models/pos.model';
import { PosOfflineService } from './pos-offline.service';

export interface ProductPage {
  data: PosProduct[];
  currentPage: number;
  lastPage: number;
  total: number;
}

@Injectable({ providedIn: 'root' })
export class PosProductService {
  private http = inject(HttpClient);
  private offlineService = inject(PosOfflineService);
  private base = `${environment.apiUrl}/pos`;

  /** Fetch a single page of products (for paginated grid display) */
  getProductsPage(page = 1, warehouseId?: number, categoryId?: number): Observable<ProductPage> {
    const params: Record<string, string> = { page: String(page) };
    if (warehouseId) params['warehouse_id'] = String(warehouseId);
    if (categoryId) params['category_id'] = String(categoryId);
    return this.http.get<any>(`${this.base}/products`, { params }).pipe(
      map((res) => ({
        data: (res.data || []) as PosProduct[],
        currentPage: res.meta?.current_page || page,
        lastPage: res.meta?.last_page || 1,
        total: res.meta?.total || 0,
      })),
      catchError(() =>
        // Offline fallback: return from cache (first "page" of cached products)
        from(this.offlineService.getCachedProducts()).pipe(
          map((products) => ({
            data: products.slice(0, 25),
            currentPage: 1,
            lastPage: Math.ceil(products.length / 25) || 1,
            total: products.length,
          }))
        )
      )
    );
  }

  /**
   * Background sync: fetch all products page by page and cache to IndexedDB.
   * Does NOT block the UI — runs silently in the background.
   * Returns total count when done.
   */
  syncAllToCache(warehouseId?: number): Observable<number> {
    const params: Record<string, string> = {};
    if (warehouseId) params['warehouse_id'] = String(warehouseId);

    return this.http
      .get<any>(`${this.base}/products`, { params: { ...params, page: '1' } })
      .pipe(
        switchMap((res) => {
          const firstPageData: PosProduct[] = res.data || [];
          const lastPage = res.meta?.last_page || 1;
          const total = res.meta?.total || firstPageData.length;

          // Start fresh cache with first page
          this.offlineService.cacheProducts(firstPageData).catch(() => {});

          if (lastPage <= 1) return of(total);

          // Fetch remaining pages in batches of 5 to avoid overwhelming the server
          const batchSize = 5;
          const fetchBatch = (startPage: number): Observable<number> => {
            const endPage = Math.min(startPage + batchSize - 1, lastPage);
            const batch = Array.from({ length: endPage - startPage + 1 }, (_, i) =>
              this.http
                .get<any>(`${this.base}/products`, {
                  params: { ...params, page: String(startPage + i) },
                })
                .pipe(
                  map((r) => (r.data || []) as PosProduct[]),
                  catchError(() => of([] as PosProduct[]))
                )
            );

            return forkJoin(batch).pipe(
              switchMap((results) => {
                const products = results.flat();
                // Append to cache incrementally
                this.offlineService.appendProducts(products).catch(() => {});
                if (endPage >= lastPage) return of(total);
                return fetchBatch(endPage + 1);
              })
            );
          };

          return fetchBatch(2);
        }),
        catchError(() => of(0))
      );
  }

  /** Legacy: fetch ALL products at once (kept for backward compatibility) */
  getProducts(warehouseId?: number): Observable<PosProduct[]> {
    const params: Record<string, string> = {};
    if (warehouseId) params['warehouse_id'] = String(warehouseId);
    return this.listAll(params).pipe(
      switchMap((products) => {
        this.offlineService.cacheProducts(products).catch(() => {});
        return of(products);
      }),
      catchError(() => from(this.offlineService.getCachedProducts()))
    );
  }

  searchProducts(query: string, warehouseId?: number): Observable<PosProduct[]> {
    if (this.offlineService.connectionStatus() === 'offline') {
      return from(this.offlineService.searchCachedProducts(query));
    }

    const params: Record<string, string> = { search: query };
    if (warehouseId) params['warehouse_id'] = String(warehouseId);
    return this.http.get<any>(`${environment.apiUrl}/core/products`, { params }).pipe(
      map((res) =>
        (res.data || []).map(
          (p: any) =>
            ({
              id: p.id,
              code: p.code,
              sku: p.sku,
              barcode: p.barcode,
              name: p.name,
              name_en: p.name_en,
              name_ar: p.name_ar,
              sale_price: p.sale_price,
              min_sale_price: p.min_sale_price,
              image: p.image,
              is_active: p.is_active,
              track_inventory: p.track_inventory,
              category: p.category,
              base_unit: p.base_unit,
              stock: null,
            }) as PosProduct
        )
      ),
      catchError(() => from(this.offlineService.searchCachedProducts(query)))
    );
  }

  getByBarcode(barcode: string): Observable<PosProduct | null> {
    if (this.offlineService.connectionStatus() === 'offline') {
      return from(this.offlineService.getByBarcodeCached(barcode));
    }

    return this.http.get<any>(`${this.base}/products/barcode/${barcode}`).pipe(
      map((res) => res.data || null),
      catchError(() => from(this.offlineService.getByBarcodeCached(barcode)))
    );
  }

  getCategories(): Observable<PosCategory[]> {
    return this.http
      .get<any>(`${environment.apiUrl}/core/product-categories`)
      .pipe(
        switchMap((res) => {
          const data: PosCategory[] = res.data || [];
          const lastPage = res.meta?.last_page || 1;
          if (lastPage <= 1) return of(data);
          const pages = Array.from({ length: lastPage - 1 }, (_, i) =>
            this.http
              .get<any>(`${environment.apiUrl}/core/product-categories`, {
                params: { page: String(i + 2) },
              })
              .pipe(map((r) => r.data || []))
          );
          return forkJoin(pages).pipe(map((results) => [...data, ...results.flat()]));
        }),
        switchMap((cats) => {
          this.offlineService.cacheCategories(cats).catch(() => {});
          return of(cats);
        }),
        catchError(() => from(this.offlineService.getCachedCategories()))
      );
  }

  private listAll(params: Record<string, string>): Observable<PosProduct[]> {
    return this.http
      .get<any>(`${this.base}/products`, { params: { ...params, page: '1' } })
      .pipe(
        switchMap((res) => {
          const data: PosProduct[] = res.data || [];
          const lastPage = res.meta?.last_page || 1;
          if (lastPage <= 1) return of(data);
          const pages = Array.from({ length: lastPage - 1 }, (_, i) =>
            this.http
              .get<any>(`${this.base}/products`, {
                params: { ...params, page: String(i + 2) },
              })
              .pipe(map((r) => r.data || []))
          );
          return forkJoin(pages).pipe(map((results) => [...data, ...results.flat()]));
        })
      );
  }
}
