import { HttpErrorResponse } from '@angular/common/http';
import { signal } from '@angular/core';
import { getTestBed, TestBed } from '@angular/core/testing';
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
import { ActivatedRoute, convertToParamMap, Router } from '@angular/router';
import { of } from 'rxjs';
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';

import { AuthService } from '../../../../core/services/auth/auth.service';
import { CheckoutService } from '../../../../core/services/checkout.service';
import { CatalogService } from '../../../../core/services/catalog/catalog.service';
import { GlobalLoadingService } from '../../../../core/services/global-loading/global-loading.service';
import { TenantService } from '../../../../core/services/tenant.service';
import { ToastService } from '../../../../core/services/toast.service';
import { CartService } from '../../../../core/services/cart/cart.service';
import { CartEditingPolicy } from '../../../../core/services/tenant.interface';
import { CheckoutCountdownService } from '../../../../core/services/checkout-countdown.service';
import { CheckoutPageComponent } from './checkout-page.component';

describe('CheckoutPageComponent payment validation', () => {
  let checkoutServiceStub: {
    startCheckout: ReturnType<typeof vi.fn>;
    updateCustomerData: ReturnType<typeof vi.fn>;
    cancelPurchase: ReturnType<typeof vi.fn>;
    generatePaymentIntent: ReturnType<typeof vi.fn>;
    submitPurchaseForReview: ReturnType<typeof vi.fn>;
    getPurchase: ReturnType<typeof vi.fn>;
    withCustomLoading: ReturnType<typeof vi.fn>;
  };
  let routerStub: { navigate: ReturnType<typeof vi.fn> };
  let toastServiceStub: { danger: ReturnType<typeof vi.fn> };
  let globalLoadingServiceStub: {
    start: ReturnType<typeof vi.fn>;
    stop: ReturnType<typeof vi.fn>;
  };
  let cartServiceStub: { loadCart: ReturnType<typeof vi.fn> };
  let routeParamMap: ReturnType<typeof convertToParamMap>;
  let authUserState: ReturnType<typeof signal>;
  let tenantState: ReturnType<
    typeof signal<{
      codigo: string;
      checkout_editing_policy?: CartEditingPolicy;
    }>
  >;

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

  beforeEach(async () => {
    vi.useFakeTimers();

    checkoutServiceStub = {
      startCheckout: vi.fn().mockResolvedValue({
        id: 25,
        items: [],
        subtotal: '0.00',
        total: '0.00',
      }),
      updateCustomerData: vi.fn(),
      cancelPurchase: vi.fn().mockResolvedValue({ status: 'cancelled' }),
      generatePaymentIntent: vi.fn().mockResolvedValue({
        qr_data: { qr_code: 'qr-value' },
      }),
      submitPurchaseForReview: vi.fn().mockResolvedValue({ status: 'in_review' }),
      getPurchase: vi.fn().mockResolvedValue({ status: 'pending_payment' }),
      withCustomLoading: vi.fn(),
    };
    checkoutServiceStub.withCustomLoading.mockReturnValue(checkoutServiceStub);
    routerStub = { navigate: vi.fn() };
    toastServiceStub = { danger: vi.fn() };
    globalLoadingServiceStub = { start: vi.fn(), stop: vi.fn() };
    cartServiceStub = {
      loadCart: vi.fn().mockReturnValue(of({ id: 30, items: [], subtotal: '0.00' })),
    };
    routeParamMap = convertToParamMap({});
    authUserState = signal(null);
    tenantState = signal({
      codigo: 'tenant-test',
      checkout_editing_policy: {
        code: 'full',
        allow_modify: true,
        allow_delete: true,
        allow_update_quantity: true,
        allow_update_variant: true,
      },
    });

    await TestBed.configureTestingModule({
      imports: [CheckoutPageComponent],
      providers: [
        { provide: CheckoutService, useValue: checkoutServiceStub },
        { provide: CatalogService, useValue: { getCatalogItem: vi.fn() } },
        { provide: TenantService, useValue: { tenant: tenantState } },
        { provide: AuthService, useValue: { user: authUserState } },
        { provide: GlobalLoadingService, useValue: globalLoadingServiceStub },
        { provide: ToastService, useValue: toastServiceStub },
        { provide: CartService, useValue: cartServiceStub },
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              get paramMap() {
                return routeParamMap;
              },
            },
          },
        },
        { provide: Router, useValue: routerStub },
      ],
    })
      .overrideComponent(CheckoutPageComponent, { set: { template: '' } })
      .compileComponents();
  });

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

  function createComponent(): any {
    const fixture = TestBed.createComponent(CheckoutPageComponent);
    fixture.detectChanges();
    fixture.componentInstance['createdPurchaseId'].set(25);
    routerStub.navigate.mockClear();
    return { fixture, component: fixture.componentInstance as any };
  }

  it('does not expose an active countdown until the purchase timing is loaded', async () => {
    const countdown = TestBed.inject(CheckoutCountdownService);
    countdown.synchronize({
      expires_at: null,
      expires_in_seconds: 600,
      server_time: new Date().toISOString(),
    });
    checkoutServiceStub.getPurchase.mockResolvedValue({
      id: 25,
      status: 'created',
      expires_at: '2026-08-27T15:10:00.000Z',
      expires_in_seconds: 600,
      server_time: '2026-08-27T15:00:00.000Z',
      items: [],
      subtotal: '0.00',
      total: '0.00',
    });
    routeParamMap = convertToParamMap({ id: 25 });

    const { component } = createComponent();

    expect(component.checkoutRemainingTime()).toBeNull();

    await Promise.resolve();

    expect(countdown.remainingSeconds()).toBe(600);
    expect(component.checkoutRemainingTime()).toBe('10:00');
  });

  it('polls QR after five seconds and navigates only when payment is paid', async () => {
    checkoutServiceStub.getPurchase
      .mockResolvedValueOnce({ status: 'pending_payment' })
      .mockResolvedValueOnce({ status: 'paid' });
    const { component } = createComponent();

    await component.selectPaymentMethod('qr');
    expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled();

    await vi.advanceTimersByTimeAsync(5_000);
    expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(1);
    expect(routerStub.navigate).not.toHaveBeenCalled();

    await vi.advanceTimersByTimeAsync(5_000);
    expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(2);
    expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25]);
    expect(checkoutServiceStub.generatePaymentIntent).toHaveBeenCalledTimes(1);
  });

  it('shows the QR verification state while each status request is in progress', async () => {
    let resolvePurchase!: (purchase: { status: string }) => void;
    checkoutServiceStub.getPurchase.mockReturnValue(
      new Promise((resolve) => {
        resolvePurchase = resolve;
      }),
    );
    const { fixture, component } = createComponent();

    const statusRequest = component.checkQrPayment(component.qrPollingRunId);
    expect(component.isCheckingQrPayment()).toBe(true);

    resolvePurchase({ status: 'pending_payment' });
    await statusRequest;
    expect(component.isCheckingQrPayment()).toBe(false);
    fixture.destroy();
  });

  it('stops QR polling after 45 seconds and can restart it', async () => {
    const { component } = createComponent();

    await component.selectPaymentMethod('qr');
    await vi.advanceTimersByTimeAsync(45_000);

    expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(9);
    expect(component.qrPaymentStatus()).toBe('timed_out');

    component.retryQrPolling();
    expect(component.qrPaymentStatus()).toBe('waiting');
    await vi.advanceTimersByTimeAsync(5_000);
    expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(10);
  });

  it('cancels QR polling when payment method changes or component is destroyed', async () => {
    const first = createComponent();
    await first.component.selectPaymentMethod('qr');
    await first.component.selectPaymentMethod('transfer');
    await vi.advanceTimersByTimeAsync(10_000);
    expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled();

    const second = createComponent();
    await second.component.selectPaymentMethod('qr');
    second.fixture.destroy();
    await vi.advanceTimersByTimeAsync(10_000);
    expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled();
  });

  it('polls a transfer every three seconds for one minute', async () => {
    checkoutServiceStub.getPurchase.mockResolvedValue({ status: 'pending_payment' });
    const { component } = createComponent();
    component.selectedPaymentMethod.set('transfer');

    await component.onComplete();
    expect(component.transferValidationStatus()).toBe('checking');
    expect(checkoutServiceStub.submitPurchaseForReview).toHaveBeenCalledWith('tenant-test', 25);
    expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled();

    await vi.advanceTimersByTimeAsync(57_000);
    expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(19);
    expect(component.transferValidationStatus()).toBe('checking');

    await vi.advanceTimersByTimeAsync(3_000);
    expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(20);
    expect(checkoutServiceStub.getPurchase).toHaveBeenLastCalledWith('tenant-test', 25);
    expect(component.transferValidationStatus()).toBe('error');
    expect(routerStub.navigate).not.toHaveBeenCalled();
  });

  it('prevents modifying the purchase after Ya transferí is clicked', async () => {
    const { component } = createComponent();
    component.selectedPaymentMethod.set('transfer');

    await component.onComplete();

    expect(component.hasSubmittedTransfer()).toBe(true);
    expect(component.isPurchaseModificationDisabled()).toBe(true);

    await component.selectPaymentMethod('qr');
    await component.onModifyPurchase();

    expect(component.selectedPaymentMethod()).toBe('transfer');
    expect(checkoutServiceStub.generatePaymentIntent).not.toHaveBeenCalled();
    expect(checkoutServiceStub.cancelPurchase).not.toHaveBeenCalled();
    expect(globalLoadingServiceStub.start).not.toHaveBeenCalled();
    expect(routerStub.navigate).not.toHaveBeenCalled();
  });

  it('navigates after a transfer is confirmed as paid', async () => {
    checkoutServiceStub.getPurchase.mockResolvedValue({ status: 'paid' });
    const { component } = createComponent();
    component.selectedPaymentMethod.set('transfer');

    await component.onComplete();
    await vi.advanceTimersByTimeAsync(3_000);

    expect(checkoutServiceStub.getPurchase).toHaveBeenCalledWith('tenant-test', 25);
    expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25]);
  });

  it('keeps polling after transfer validation requests fail', async () => {
    vi.spyOn(console, 'error').mockImplementation(() => undefined);
    checkoutServiceStub.getPurchase.mockRejectedValue(new Error('network error'));
    const { component } = createComponent();
    component.selectedPaymentMethod.set('transfer');

    await component.onComplete();
    await vi.advanceTimersByTimeAsync(60_000);

    expect(checkoutServiceStub.getPurchase).toHaveBeenCalledTimes(20);
    expect(component.transferValidationStatus()).toBe('error');
    expect(routerStub.navigate).not.toHaveBeenCalled();
  });

  it('uses the primary candidate reason when transfer polling times out', async () => {
    checkoutServiceStub.getPurchase.mockResolvedValue({
      status: 'in_review',
      payment_verification: {
        status: 'candidate',
        candidate_count: 1,
        primary: {
          reason: 'exact_amount_near_dni',
          dni_distance: 1,
          payment_amount: '300000.00',
          purchase_amount: '300000.00',
          amount_difference: '0.00',
          confidence: 'medium',
          detected_at: '2026-08-27T18:00:00-03:00',
        },
        reasons: ['exact_amount_near_dni'],
      },
    });
    const { component } = createComponent();
    component.selectedPaymentMethod.set('transfer');

    await component.onComplete();
    await vi.advanceTimersByTimeAsync(60_000);

    expect(component.transferValidationStatus()).toBe('error');
    expect(component.transferVerificationErrorTitle()).toBe(
      'El DNI no corresponde con el de la transferencia',
    );
  });

  it('does not poll when submitting a transfer for review fails', async () => {
    vi.spyOn(console, 'error').mockImplementation(() => undefined);
    checkoutServiceStub.submitPurchaseForReview.mockRejectedValue(new Error('network error'));
    const { component } = createComponent();
    component.selectedPaymentMethod.set('transfer');

    await component.onComplete();
    await vi.advanceTimersByTimeAsync(12_000);

    expect(checkoutServiceStub.submitPurchaseForReview).toHaveBeenCalledWith('tenant-test', 25);
    expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled();
    expect(component.transferValidationStatus()).toBe('error');
  });

  it('cancels transfer polling when the component is destroyed', async () => {
    const checkout = createComponent();
    checkout.component.selectedPaymentMethod.set('transfer');
    await checkout.component.onComplete();
    checkout.fixture.destroy();
    await vi.advanceTimersByTimeAsync(12_000);
    expect(checkoutServiceStub.getPurchase).not.toHaveBeenCalled();
  });

  it('loads purchase items but prefills customer data from the user', async () => {
    const purchase = {
      id: 25,
      cart_id: null,
      status: 'created',
      payment_method: null,
      transfer_payer_dni: null,
      nombre_apellido: 'Datos de la compra',
      email: 'compra@example.com',
      dni: '11111111',
      telefono: '1111111111',
      items: [
        {
          id: 91,
          quantity: 2,
          unit_price: '1250.50',
          line_total: '2501.00',
          source_catalog_item_id: 8,
          source_variant_id: 21,
          item_details: {
            nombre: 'Remera',
            descripcion: null,
            slug: 'remera',
            imagen: 'https://example.com/remera.jpg',
            attributes: [{ name: 'Color', value: 'Negro' }],
          },
        },
      ],
      subtotal: '2501.00',
      total: '2501.00',
    };
    routeParamMap = convertToParamMap({ id: 25 });
    checkoutServiceStub.getPurchase.mockResolvedValue(purchase);
    authUserState.set({
      id: 7,
      nombre_apellido: 'Juan Perez',
      email: 'juan@example.com',
      dni: '12345678',
      telefono: '3415555555',
    });
    const { component } = createComponent();
    await Promise.resolve();

    expect(checkoutServiceStub.getPurchase).toHaveBeenCalledWith('tenant-test', 25);
    expect(component.mappedCartItems()).toEqual([
      {
        cartItemId: 91,
        imageUrl: 'https://example.com/remera.jpg',
        product: 'Remera',
        originalPrice: null,
        discountedPrice: 1250.5,
        discountPercentage: null,
        attributes: [{ label: 'Color', value: 'Negro' }],
        quantity: 2,
      },
    ]);
    expect(component.cartSubtotal()).toBe(2501);
    expect(component.cartTotal()).toBe(2501);
    expect(component.form.getRawValue()).toEqual({
      nombre: 'Juan Perez',
      email: 'juan@example.com',
      dni: '12345678',
      telefono: '3415555555',
    });
  });

  it('keeps the checkout hidden while the purchase is loading', async () => {
    let resolvePurchase!: (purchase: any) => void;
    routeParamMap = convertToParamMap({ id: 25 });
    checkoutServiceStub.getPurchase.mockReturnValue(
      new Promise((resolve) => {
        resolvePurchase = resolve;
      }),
    );
    const { component } = createComponent();

    expect(component.isLoadingPurchase()).toBe(true);

    resolvePurchase({
      id: 25,
      status: 'created',
      payment_method: null,
      transfer_payer_dni: null,
      items: [],
      subtotal: '0.00',
      total: '0.00',
    });
    await Promise.resolve();

    expect(checkoutServiceStub.getPurchase).toHaveBeenCalledWith('tenant-test', 25);
    expect(component.isLoadingPurchase()).toBe(false);
    expect(component.checkoutStepIndex()).toBe(0);
  });

  it('opens a pending purchase on the payment step and restores its payment method', async () => {
    routeParamMap = convertToParamMap({ id: 25 });
    checkoutServiceStub.getPurchase.mockResolvedValue({
      id: 25,
      status: 'pending_payment',
      payment_method: 'transfer',
      transfer_payer_dni: '12345678',
      items: [],
      subtotal: '100.00',
      total: '100.00',
    });
    const { component } = createComponent();
    await Promise.resolve();

    expect(component.checkoutStepIndex()).toBe(1);
    expect(component.selectedPaymentMethod()).toBe('transfer');
    expect(component.transferDni()).toBe('12345678');
    expect(component.isLoadingPurchase()).toBe(false);
  });

  it('generates a new QR when reopening a pending QR purchase', async () => {
    routeParamMap = convertToParamMap({ id: 25 });
    checkoutServiceStub.getPurchase.mockResolvedValue({
      id: 25,
      status: 'pending_payment',
      payment_method: 'qr',
      transfer_payer_dni: null,
      items: [],
      subtotal: '100.00',
      total: '100.00',
    });
    const { component } = createComponent();
    await Promise.resolve();
    await Promise.resolve();

    expect(component.checkoutStepIndex()).toBe(1);
    expect(component.selectedPaymentMethod()).toBe('qr');
    expect(checkoutServiceStub.generatePaymentIntent).toHaveBeenCalledWith('tenant-test', 25, 'qr');
    expect(component.qrData()).toBe('qr-value');
    expect(component.qrPaymentStatus()).toBe('waiting');
  });

  it.each(['paid', 'cancelled', 'rejected', 'expired'])(
    'redirects a %s purchase to its status page',
    async (status) => {
      routeParamMap = convertToParamMap({ id: 25 });
      checkoutServiceStub.getPurchase.mockResolvedValue({
        id: 25,
        status,
        payment_method: null,
        transfer_payer_dni: null,
        items: [],
        subtotal: '100.00',
        total: '100.00',
      });
      createComponent();
      await Promise.resolve();

      expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25]);
    },
  );

  it('redirects a submitted pending payment purchase to its status page', async () => {
    routeParamMap = convertToParamMap({ id: 25 });
    checkoutServiceStub.getPurchase.mockResolvedValue({
      id: 25,
      status: 'pending_payment',
      payment_method: 'transfer',
      expires_at: null,
      transfer_payer_dni: null,
      items: [],
      subtotal: '100.00',
      total: '100.00',
    });

    createComponent();
    await Promise.resolve();

    expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25]);
  });

  it('shows the API error and redirects to status when cancellation finds an expired purchase', async () => {
    const message = 'La compra venció. Iniciá una nueva compra.';
    checkoutServiceStub.cancelPurchase.mockRejectedValue({
      error: { code: 'purchase.expired', message },
    });
    const { component } = createComponent();

    await component.onModifyPurchase();

    expect(toastServiceStub.danger).toHaveBeenCalledWith(message);
    expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25], {
      queryParams: { status: 'expired' },
    });
    expect(globalLoadingServiceStub.start).toHaveBeenCalledOnce();
    expect(globalLoadingServiceStub.stop).toHaveBeenCalledOnce();
    expect(component.isCancellingPurchase()).toBe(false);
  });

  it('cancels the current purchase and opens the active cart when Modificar is clicked', async () => {
    let finishNavigation!: (navigated: boolean) => void;
    routerStub.navigate.mockReturnValue(
      new Promise<boolean>((resolve) => {
        finishNavigation = resolve;
      }),
    );
    const { component } = createComponent();
    component.createdPurchase.set({
      id: 25,
      status: 'created',
      items: [
        {
          id: 91,
          quantity: 2,
          unit_price: '100.00',
          line_total: '200.00',
          source_catalog_item_id: 8,
          source_variant_id: null,
          item_details: {
            nombre: 'Remera',
            descripcion: null,
            slug: 'remera',
            imagen: null,
            attributes: [],
          },
        },
      ],
      subtotal: '200.00',
      total: '200.00',
    });
    const modification = component.onModifyPurchase();
    await Promise.resolve();
    await Promise.resolve();

    expect(checkoutServiceStub.cancelPurchase).toHaveBeenCalledWith('tenant-test', 25);
    expect(cartServiceStub.loadCart).toHaveBeenCalledOnce();
    expect(globalLoadingServiceStub.start).toHaveBeenCalledOnce();
    expect(globalLoadingServiceStub.stop).not.toHaveBeenCalled();
    expect(component.createdPurchaseId()).toBeNull();
    expect(component.createdPurchase()).toBeNull();
    expect(routerStub.navigate).toHaveBeenCalledWith(['/'], {
      queryParams: { openCart: true },
    });

    finishNavigation(true);
    await modification;

    expect(globalLoadingServiceStub.stop).toHaveBeenCalledOnce();

    await component.canDeactivate();
  });

  it('updates customer data on the existing purchase before payment', async () => {
    const updatedPurchase = {
      id: 25,
      cart_id: 10,
      items: [],
      subtotal: '100.00',
      total: '100.00',
    };
    checkoutServiceStub.updateCustomerData.mockResolvedValue(updatedPurchase);
    const { component } = createComponent();
    component.form.setValue({
      nombre: 'Juan Perez',
      email: 'juan@example.com',
      dni: '12345678',
      telefono: '3415555555',
    });
    component.stepper = { next: vi.fn() };

    await component.onStep1Continue();

    expect(checkoutServiceStub.startCheckout).not.toHaveBeenCalled();
    expect(checkoutServiceStub.updateCustomerData).toHaveBeenCalledWith('tenant-test', 25, {
      dni: '12345678',
      telefono: '3415555555',
      email: 'juan@example.com',
      nombre_apellido: 'Juan Perez',
    });
    expect(component.createdPurchase()).toEqual({
      ...updatedPurchase,
      status: 'pending_payment',
    });
    expect(component.stepper.next).toHaveBeenCalledOnce();
  });

  it('cancels the current purchase when navigating away from checkout', async () => {
    const { component } = createComponent();

    await expect(component.canDeactivate()).resolves.toBe(true);

    expect(checkoutServiceStub.cancelPurchase).toHaveBeenCalledWith('tenant-test', 25);
    expect(cartServiceStub.loadCart).toHaveBeenCalledOnce();
    expect(component.createdPurchaseId()).toBeNull();
    expect(globalLoadingServiceStub.start).toHaveBeenCalledOnce();
    expect(globalLoadingServiceStub.stop).toHaveBeenCalledOnce();
  });

  it('prevents leaving checkout when the purchase cannot be cancelled', async () => {
    checkoutServiceStub.cancelPurchase.mockRejectedValue({
      error: { message: 'No se pudo cancelar la compra.' },
    });
    const { component } = createComponent();

    await expect(component.canDeactivate()).resolves.toBe(false);

    expect(toastServiceStub.danger).toHaveBeenCalledWith('No se pudo cancelar la compra.');
    expect(component.createdPurchaseId()).toBe(25);
  });

  it('allows leaving checkout when cancellation finds an expired stock reservation', async () => {
    const message = 'La reserva de stock venció. Usá el carrito activo para continuar.';
    checkoutServiceStub.cancelPurchase.mockRejectedValue({
      error: { code: 'stock_reservation.expired', message },
    });
    const { component } = createComponent();

    await expect(component.canDeactivate()).resolves.toBe(true);

    expect(toastServiceStub.danger).toHaveBeenCalledWith(message);
    expect(cartServiceStub.loadCart).toHaveBeenCalledOnce();
    expect(component.createdPurchaseId()).toBeNull();
    expect(component.createdPurchase()).toBeNull();
    expect(globalLoadingServiceStub.start).toHaveBeenCalledOnce();
    expect(globalLoadingServiceStub.stop).toHaveBeenCalledOnce();
  });

  it('shows the API message and redirects to status when a checkout operation finds an expired purchase', async () => {
    checkoutServiceStub.generatePaymentIntent.mockRejectedValue(
      new HttpErrorResponse({
        status: 422,
        error: {
          code: 'purchase.expired',
          message: 'La compra venci\u00f3. Inici\u00e1 una nueva compra.',
        },
      }),
    );
    const { component } = createComponent();

    await component.selectPaymentMethod('qr');

    expect(toastServiceStub.danger).toHaveBeenCalledWith(
      'La compra venci\u00f3. Inici\u00e1 una nueva compra.',
    );
    expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25], {
      queryParams: { status: 'expired' },
    });
    expect(component.isGeneratingIntent()).toBe(false);
  });

  it('redirects to status when QR polling receives a purchase-expired response', async () => {
    checkoutServiceStub.getPurchase.mockRejectedValue(
      new HttpErrorResponse({
        status: 422,
        error: {
          code: 'purchase.expired',
          message: 'La compra venci\u00f3. Inici\u00e1 una nueva compra.',
        },
      }),
    );
    const { component } = createComponent();

    await component.selectPaymentMethod('qr');
    await vi.advanceTimersByTimeAsync(5_000);

    expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25], {
      queryParams: { status: 'expired' },
    });
    expect(toastServiceStub.danger).toHaveBeenCalledWith(
      'La compra venci\u00f3. Inici\u00e1 una nueva compra.',
    );
  });

  it('treats a generic customer-data error as expired when the local deadline passed', () => {
    const { component } = createComponent();
    component.createdPurchase.set({
      id: 25,
      status: 'created',
      expires_at: new Date(Date.now() - 1_000).toISOString(),
      items: [],
      subtotal: '100.00',
      total: '100.00',
    });

    const handled = component.handleCheckoutError(
      new HttpErrorResponse({
        status: 422,
        error: {
          errors: {
            purchase: ['La compra ya no se puede modificar.'],
          },
        },
      }),
      'No se pudieron actualizar los datos de la compra.',
    );

    expect(handled).toBe(true);
    expect(toastServiceStub.danger).toHaveBeenCalledWith(
      'La compra venci\u00f3. Inici\u00e1 una nueva compra.',
    );
    expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25], {
      queryParams: { status: 'expired' },
    });
  });
});
