import { TestBed, getTestBed } from '@angular/core/testing';
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
import { of } from 'rxjs';

import { ApiPaginatedResponse } from '../../../core/services/api-paginated-response.interface';
import { CartService } from '../../../core/services/cart/cart.service';
import { CatalogService } from '../../../core/services/catalog/catalog.service';
import {
  CatalogFeaturedItems,
  CatalogGroupLayout,
} from '../../../core/services/catalog/catalog.interface';
import { ProductListComponent, ProductListItem, ProductListLayout } from './product-list.component';
import { ProductTicketSelectorComponent } from '../product-ticket-selector/product-ticket-selector.component';
import { By } from '@angular/platform-browser';

describe('ProductListComponent', () => {
  const items: ProductListItem[] = [
    {
      id: 1,
      type: 'product',
      nombre: 'Producto uno',
      descripcion: 'Primera descripcion',
      precio: '100.00',
      image: '/images/one.png',
      variants: [],
    },
    {
      id: 2,
      type: 'product',
      nombre: 'Producto dos',
      descripcion: 'Segunda descripcion',
      precio: 200,
      image: null,
      variants: [],
    },
  ];

  beforeAll(() => {
    try {
      getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
    } catch {
      // Test environment may already be initialized by another setup entrypoint.
    }
  });

  afterEach(() => {
    vi.unstubAllGlobals();
    TestBed.resetTestingModule();
  });

  async function render(
    layout: ProductListLayout,
    productItems: CatalogFeaturedItems = paginatedItems(),
    groupLayout: CatalogGroupLayout = 'paginated',
  ) {
    const getVariantOptions = vi.fn().mockReturnValue(
      of({
        selectors: ['tipo', 'sector', 'fila', 'asiento'].map((key, index) => ({
          key,
          label: key,
          options: [{ value: String(index + 1), label: String(index + 1) }],
          enabled: index === 0,
        })),
        selected_values: {},
        resolved_variant: null,
        valid: true,
        available_variant_count: 2,
        matching_variant_count: 2,
        price_range: { minimum: '10000.00', maximum: '12000.00' },
      }),
    );
    await TestBed.configureTestingModule({
      imports: [ProductListComponent],
      providers: [
        { provide: CatalogService, useValue: { getVariantOptions } },
        { provide: CartService, useValue: {} },
      ],
    }).compileComponents();
    const fixture = TestBed.createComponent(ProductListComponent);
    fixture.componentRef.setInput('layout', layout);
    fixture.componentRef.setInput('groupLayout', groupLayout);
    fixture.componentRef.setInput('items', productItems);
    fixture.detectChanges();

    return fixture;
  }

  function paginatedItems(currentPage = 1, lastPage = 2): ApiPaginatedResponse<ProductListItem[]> {
    return {
      data: items,
      meta: {
        current_page: currentPage,
        from: 1,
        last_page: lastPage,
        links: [],
        path: '/catalog/featured-groups/1/items',
        per_page: 12,
        to: 2,
        total: 13,
      },
      links: {
        first: '/catalog/featured-groups/1/items?page=1',
        last: '/catalog/featured-groups/1/items?page=2',
        prev: null,
        next: '/catalog/featured-groups/1/items?page=2',
      },
    };
  }

  it('renders row products in the adaptive grid', async () => {
    const fixture = await render('row');
    const element = fixture.nativeElement as HTMLElement;

    expect(element.querySelector('.product-list--row')).not.toBeNull();
    expect(element.querySelector('.product-list--adaptive')).not.toBeNull();
    expect(element.querySelectorAll('app-product-row-card')).toHaveLength(2);
    expect(element.querySelector('app-product-column-with-image')).toBeNull();
  });

  it('replaces the row layout with column with cart on mobile', async () => {
    const matchMedia = vi.fn().mockReturnValue({
      matches: true,
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
    } as unknown as MediaQueryList);
    vi.stubGlobal('matchMedia', matchMedia);

    const fixture = await render('row');
    await fixture.whenStable();
    fixture.detectChanges();
    const element = fixture.nativeElement as HTMLElement;

    expect(matchMedia).toHaveBeenCalledWith('(max-width: 767.98px)');
    expect(element.querySelector('.product-list--column')).not.toBeNull();
    expect(element.querySelectorAll('app-product-vertical-with-cart-card')).toHaveLength(2);
    expect(element.querySelector('app-product-row-card')).toBeNull();
  });

  it('renders image products next to each other in the column grid', async () => {
    const fixture = await render('column_with_image');
    const element = fixture.nativeElement as HTMLElement;

    expect(element.querySelector('.product-list--column')).not.toBeNull();
    expect(element.querySelectorAll('app-product-column-with-image')).toHaveLength(2);
  });

  it('renders cart products next to each other in the column grid', async () => {
    const fixture = await render('column_with_cart');

    expect(
      (fixture.nativeElement as HTMLElement).querySelectorAll(
        'app-product-vertical-with-cart-card',
      ),
    ).toHaveLength(2);
  });

  it('emits a direct-purchase event with the row quantity and selected variant', async () => {
    const fixture = await render('row');
    const buySpy = vi.fn();
    fixture.componentInstance.buy.subscribe(buySpy);
    const rowCard = fixture.nativeElement.querySelector('app-product-row-card') as HTMLElement;

    (rowCard.querySelector('.btn-primary') as HTMLButtonElement).click();

    expect(buySpy).toHaveBeenCalledWith({
      product: items[0],
      quantity: 1,
      variant: null,
      directPurchase: true,
    });
  });

  it('emits a direct-purchase event with the column quantity', async () => {
    const fixture = await render('column_with_cart');
    const buySpy = vi.fn();
    fixture.componentInstance.buy.subscribe(buySpy);
    const card = fixture.nativeElement.querySelector(
      'app-product-vertical-with-cart-card',
    ) as HTMLElement;

    (card.querySelector('.btn-primary') as HTMLButtonElement).click();

    expect(buySpy).toHaveBeenCalledWith({
      product: items[0],
      quantity: 1,
      variant: null,
      directPurchase: true,
    });
  });

  it('passes column variants through to the purchase event', async () => {
    const itemWithVariants: ProductListItem = {
      ...items[0],
      variants: [
        { id: 91, maximum_addable_quantity: 3, values: { fecha: '10 de octubre' } },
        { id: 92, maximum_addable_quantity: 4, values: { fecha: '11 de octubre' } },
      ],
    };
    const fixture = await render('column_with_cart', [itemWithVariants]);
    const buySpy = vi.fn();
    fixture.componentInstance.buy.subscribe(buySpy);

    const card = fixture.nativeElement.querySelector(
      'app-product-vertical-with-cart-card',
    ) as HTMLElement;
    (card.querySelector('.btn-primary') as HTMLButtonElement).click();

    expect(buySpy).toHaveBeenCalledWith({
      product: itemWithVariants,
      quantity: 1,
      variant: 91,
      directPurchase: true,
    });
  });

  it('caps the quantity at the selected variant maximum', async () => {
    const itemWithVariants: ProductListItem = {
      ...items[0],
      variants: [
        {
          id: 91,
          maximum_addable_quantity: 2,
          values: { fecha: '10 de octubre' },
        },
      ],
    };
    const fixture = await render('column_with_cart', [itemWithVariants]);
    await fixture.whenStable();
    fixture.detectChanges();
    const increase = fixture.nativeElement.querySelector(
      '.quantity-selector__button:last-child',
    ) as HTMLButtonElement;

    increase.click();
    fixture.detectChanges();

    expect(
      (fixture.nativeElement as HTMLElement).querySelector('.quantity-selector__value')
        ?.textContent,
    ).toContain('2');
    expect(increase.disabled).toBe(true);
  });

  it('disables purchase actions when the selected variant maximum is zero', async () => {
    const unavailableItem: ProductListItem = {
      ...items[0],
      variants: [
        {
          id: 91,
          maximum_addable_quantity: 0,
          values: { fecha: '10 de octubre' },
        },
      ],
    };
    const fixture = await render('row', [unavailableItem]);
    await fixture.whenStable();
    fixture.detectChanges();
    const buttons = Array.from(
      (fixture.nativeElement as HTMLElement).querySelectorAll<HTMLButtonElement>(
        '.product-row-card__buttons button',
      ),
    );

    expect(buttons).toHaveLength(2);
    expect(buttons.every((button) => button.disabled)).toBe(true);
  });

  it('renders pagination and emits the requested page', async () => {
    const fixture = await render('row');
    const pageChangeSpy = vi.fn();
    fixture.componentInstance.pageChange.subscribe(pageChangeSpy);

    const nextButton = (fixture.nativeElement as HTMLElement).querySelector(
      '[data-testid="paginator-next"]',
    ) as HTMLButtonElement;
    nextButton.click();

    expect(pageChangeSpy).toHaveBeenCalledWith(2);
  });

  it('renders plain items without pagination', async () => {
    const fixture = await render('row', items, 'simple');
    const element = fixture.nativeElement as HTMLElement;

    expect(element.querySelectorAll('app-product-row-card')).toHaveLength(2);
    expect(element.querySelector('.product-list--adaptive')).not.toBeNull();
    expect(element.querySelector('app-paginator')).toBeNull();
  });

  it('keeps simple vertical groups to one full-width item per row', async () => {
    const fixture = await render('column_with_image', items, 'simple_vertical');
    const element = fixture.nativeElement as HTMLElement;

    expect(element.querySelector('.product-list--simple-vertical')).not.toBeNull();
    expect(element.querySelector('.product-list--adaptive')).toBeNull();
    expect(element.querySelectorAll('app-product-column-with-image')).toHaveLength(2);
  });

  it('renders a single ticket selector and emits its selected variant', async () => {
    const ticket: ProductListItem = {
      ...items[0],
      variants: [
        {
          id: 401,
          precio: '10000.00',
          maximum_addable_quantity: 1,
          values: {
            tipo: { value: 'vip', label: 'VIP' },
            sector: { value: 'a', label: 'Sector A' },
            fila: { value: '3', label: 'Fila 3' },
            asiento: { value: '12', label: 'Asiento 12' },
          },
        },
        {
          id: 402,
          precio: '12000.00',
          maximum_addable_quantity: 1,
          values: {
            tipo: { value: 'vip', label: 'VIP' },
            sector: { value: 'a', label: 'Sector A' },
            fila: { value: '3', label: 'Fila 3' },
            asiento: { value: '13', label: 'Asiento 13' },
          },
        },
      ],
    };
    const fixture = await render('ticket_selector', [ticket], 'single');
    const buySpy = vi.fn();
    fixture.componentInstance.buy.subscribe(buySpy);
    await fixture.whenStable();
    fixture.detectChanges();
    const element = fixture.nativeElement as HTMLElement;

    expect(element.querySelector('.product-list--single')).not.toBeNull();
    expect(element.querySelectorAll('app-product-ticket-selector')).toHaveLength(1);
    expect(element.querySelectorAll('.variant-selector__select')).toHaveLength(4);
    expect(
      (element.querySelector('.ticket-selector__actions .btn-primary') as HTMLButtonElement)
        .disabled,
    ).toBe(true);

    const ticketSelector = fixture.debugElement.query(By.directive(ProductTicketSelectorComponent))
      .componentInstance as ProductTicketSelectorComponent;
    ticketSelector['patchRow'](1, {
      variantId: 401,
      reservedVariantId: 401,
      cartItemId: 1,
      status: 'reserved',
    });
    fixture.detectChanges();

    (element.querySelector('.ticket-selector__add-row-button') as HTMLButtonElement).click();
    fixture.detectChanges();

    expect(element.querySelectorAll('.ticket-selector__row')).toHaveLength(2);
    expect(element.querySelectorAll('.variant-selector__select')).toHaveLength(8);

    ticketSelector['patchRow'](2, {
      variantId: 402,
      reservedVariantId: 402,
      cartItemId: 2,
      status: 'reserved',
    });
    fixture.detectChanges();

    (element.querySelector('.ticket-selector__actions .btn-primary') as HTMLButtonElement).click();

    expect(buySpy).toHaveBeenCalledWith({
      product: ticket,
      quantity: 2,
      variant: 401,
      variantIds: [401, 402],
      directPurchase: true,
      reservedInCart: true,
    });
  });

  it('loads ticket selector availability without requiring catalog variants', async () => {
    const ticket: ProductListItem = {
      ...items[0],
      variants: undefined,
    };
    const fixture = await render('ticket_selector', [ticket], 'single');
    await fixture.whenStable();
    fixture.detectChanges();

    const selector = fixture.debugElement.query(By.directive(ProductTicketSelectorComponent))
      .componentInstance as ProductTicketSelectorComponent;

    expect(selector['availableVariantCount']()).toBe(2);
    expect(fixture.nativeElement.querySelectorAll('.variant-selector__select')).toHaveLength(4);
  });

  it('renders carousel groups with the reusable carousel', async () => {
    const fixture = await render('column_with_image', items, 'carousel');
    const element = fixture.nativeElement as HTMLElement;

    expect(element.querySelector('app-carousel')).not.toBeNull();
    expect(element.querySelector('.product-list')).toBeNull();
    expect(element.querySelectorAll('app-product-column-with-image')).toHaveLength(2);
    expect(element.querySelectorAll('.product-list__item--carousel')).toHaveLength(2);
    expect(element.querySelector('app-paginator')).toBeNull();
  });
});
