import { HttpErrorResponse } from '@angular/common/http';
import {
  ChangeDetectionStrategy,
  Component,
  DestroyRef,
  Injector,
  computed,
  inject,
  signal,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {
  catchError,
  combineLatest,
  distinctUntilChanged,
  finalize,
  map,
  of,
  startWith,
  switchMap,
  tap,
} from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

import { CartService } from '../../../../core/services/cart/cart.service';
import { AuthService } from '../../../../core/services/auth/auth.service';
import {
  CheckoutService,
  isExpiredStockReservationResponse,
} from '../../../../core/services/checkout.service';
import {
  CatalogFeaturedItem,
  CatalogFeaturedItems,
  CatalogGroupLayout,
  CatalogProductLayout,
} from '../../../../core/services/catalog/catalog.interface';
import { CatalogService } from '../../../../core/services/catalog/catalog.service';
import { CatalogAvailabilityService } from '../../../../core/services/catalog/catalog-availability.service';
import { ApiPaginatedResponse } from '../../../../core/services/api-paginated-response.interface';
import { TenantService } from '../../../../core/services/tenant.service';
import { ToastService } from '../../../../core/services/toast.service';
import {
  ProductListCartEvent,
  ProductListBuyEvent,
  ProductListComponent,
} from '../../../../shared/components/product-list/product-list.component';

interface SearchRouteState {
  query: string;
  page: number;
}

@Component({
  selector: 'app-search-page',
  imports: [ProductListComponent],
  templateUrl: './search-page.component.html',
  styleUrl: './search-page.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SearchPageComponent {
  private readonly minSearchLength = 3;
  private readonly cartService = inject(CartService);
  private readonly catalogAvailabilityService = inject(CatalogAvailabilityService);
  private readonly catalogService = inject(CatalogService);
  private readonly injector = inject(Injector);
  private readonly destroyRef = inject(DestroyRef);
  private readonly route = inject(ActivatedRoute);
  private readonly router = inject(Router);
  private readonly tenantService = inject(TenantService);
  private readonly toastService = inject(ToastService);

  protected readonly query = signal('');
  protected readonly results = signal<ApiPaginatedResponse<CatalogFeaturedItem[]> | null>(null);
  protected readonly error = signal<string | null>(null);
  protected readonly creatingDirectPurchase = signal(false);
  protected readonly savingProductIds = signal<ReadonlySet<number>>(new Set<number>());

  protected readonly productLayout = computed<CatalogProductLayout>(
    () => this.tenantService.tenant()?.search_product_layout ?? 'column_with_image',
  );
  protected readonly groupLayout = computed<CatalogGroupLayout>(
    () => this.tenantService.tenant()?.search_group_layout ?? 'paginated',
  );
  protected readonly displayItems = computed<CatalogFeaturedItems>(() => {
    const results = this.results();

    if (!results) {
      return [];
    }

    return this.groupLayout() === 'paginated' ? results : results.data;
  });
  protected readonly hasHiddenResults = computed(() => {
    const results = this.results();

    return (
      results !== null &&
      this.groupLayout() !== 'paginated' &&
      results.meta.total > results.data.length
    );
  });

  constructor() {
    const routeState$ = this.route.queryParamMap.pipe(
      map(
        (params): SearchRouteState => ({
          query: params.get('q')?.trim() ?? '',
          page: this.parsePage(params.get('page')),
        }),
      ),
      distinctUntilChanged(
        (previous, current) => previous.query === current.query && previous.page === current.page,
      ),
      tap(({ query }) => {
        this.query.set(query);
        this.results.set(null);
        this.error.set(
          query.length >= this.minSearchLength
            ? null
            : `Ingresá al menos ${this.minSearchLength} caracteres para buscar.`,
        );
      }),
    );

    combineLatest([
      routeState$,
      this.catalogAvailabilityService.availabilityChanged$.pipe(startWith(undefined)),
    ])
      .pipe(
        map(([routeState]) => routeState),
        switchMap(({ query, page }) => {
          if (query.length < this.minSearchLength) {
            return of(null);
          }

          return this.catalogService.searchCatalog({ q: query, page }).pipe(
            catchError(() => {
              this.error.set('No pudimos realizar la búsqueda en este momento.');

              return of(null);
            }),
          );
        }),
        takeUntilDestroyed(this.destroyRef),
      )
      .subscribe((results) => {
        this.results.set(results);
      });
  }

  protected onPageChange(page: number): void {
    void this.router.navigate([], {
      relativeTo: this.route,
      queryParams: { page },
      queryParamsHandling: 'merge',
    });
  }

  protected async onBuyProduct(event: ProductListBuyEvent): Promise<void> {
    if (!event.directPurchase) {
      await this.router.navigate(['/producto', event.product.id]);
      return;
    }

    if (this.creatingDirectPurchase()) {
      return;
    }

    if (!this.injector.get(AuthService).user()) {
      await this.router.navigate(['/login'], {
        queryParams: {
          returnUrl: event.reservedInCart ? this.router.url : `/producto/${event.product.id}`,
        },
      });
      return;
    }

    const tenant = this.tenantService.tenant();
    if (!tenant) {
      this.toastService.danger('No se pudo identificar la tienda.');
      return;
    }

    this.creatingDirectPurchase.set(true);
    try {
      const reservedCartId = event.reservedInCart ? (this.cartService.cart()?.id ?? null) : null;
      if (event.reservedInCart && reservedCartId === null) {
        this.toastService.danger('No se pudo identificar el carrito reservado.');
        return;
      }
      const purchase = await this.injector.get(CheckoutService).startCheckout(
        tenant.codigo,
        reservedCartId !== null
          ? { cart_id: reservedCartId }
          : {
              direct_items: [
                {
                  catalog_item_id: event.product.id,
                  variant_id: event.variant ?? null,
                  cantidad: event.quantity,
                },
              ],
            },
      );
      await this.router.navigate(['/checkout', purchase.id]);
    } catch (error) {
      console.error('Failed to create direct purchase:', error);
      const message =
        error instanceof HttpErrorResponse && typeof error.error?.message === 'string'
          ? error.error.message
          : 'No se pudo iniciar la compra directa.';
      this.toastService.danger(message);

      if (error instanceof HttpErrorResponse && isExpiredStockReservationResponse(error.error)) {
        this.cartService.loadCart(true).subscribe({
          error: (refreshError) => console.error('Error refreshing expired cart', refreshError),
        });
      }
    } finally {
      this.creatingDirectPurchase.set(false);
    }
  }

  protected onAddToCart(event: ProductListCartEvent): void {
    const productId = event.product.id;
    if (this.savingProductIds().has(productId)) {
      return;
    }

    this.setProductSaving(productId, true);
    this.cartService
      .addItem(productId, event.variant ?? null, event.quantity)
      .pipe(finalize(() => this.setProductSaving(productId, false)))
      .subscribe({
        next: (response) => {
          this.toastService.success(response.message || 'Producto agregado al carrito');
        },
        error: (error: HttpErrorResponse) => {
          const message = error.error?.message || 'No se pudo agregar el producto al carrito.';
          this.toastService.danger(message);
        },
      });
  }

  private setProductSaving(productId: number, saving: boolean): void {
    this.savingProductIds.update((current) => {
      const updated = new Set(current);
      saving ? updated.add(productId) : updated.delete(productId);
      return updated;
    });
  }

  private parsePage(value: string | null): number {
    const page = Number(value ?? 1);

    return Number.isInteger(page) && page > 0 ? page : 1;
  }
}
