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

import { CartService } from '../../../core/services/cart/cart.service';
import { ModalService } from '../../../core/services/modal.service';
import { ToastService } from '../../../core/services/toast.service';
import { CartComponent } from './cart.component';

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

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

  it('shows an empty cart message when there are no items', async () => {
    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            updateItemQuantity: vi.fn(),
            removeItem: vi.fn(),
          },
        },
        {
          provide: ModalService,
          useValue: {},
        },
        {
          provide: ToastService,
          useValue: {
            success: vi.fn(),
            info: vi.fn(),
            danger: vi.fn(),
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    fixture.detectChanges();

    const emptyMessage = fixture.debugElement.query(By.css('.cart-empty-message'));

    expect(emptyMessage).not.toBeNull();
    expect(emptyMessage.nativeElement.textContent.trim()).toBe('El carrito está vacío');
    expect(fixture.debugElement.query(By.css('app-cart-item'))).toBeNull();
    expect(fixture.debugElement.query(By.css('.cart-footer'))).toBeNull();
  });

  it('opens a confirm delete modal before removing an item', async () => {
    const removeItem = vi.fn().mockReturnValue(of({ message: 'Producto eliminado.' }));
    const openConfirmDelete = vi.fn().mockReturnValue(of(true));

    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            updateItemQuantity: vi.fn(),
            removeItem,
          },
        },
        {
          provide: ModalService,
          useValue: {
            openConfirmDelete,
          },
        },
        {
          provide: ToastService,
          useValue: {
            success: vi.fn(),
            info: vi.fn(),
            danger: vi.fn(),
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    fixture.componentRef.setInput('items', [
      {
        cartItemId: 10,
        imageUrl: null,
        product: 'Producto de prueba',
        originalPrice: null,
        discountedPrice: 1000,
        discountPercentage: null,
        attributes: [],
        quantity: 1,
      },
    ]);
    fixture.detectChanges();

    expect(fixture.debugElement.query(By.css('.cart-item-media'))).toBeNull();

    fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('remove');

    expect(openConfirmDelete).toHaveBeenCalledWith({
      title: 'Eliminar producto',
      content: 'Se eliminará “Producto de prueba” del carrito. Esta acción no se puede deshacer.',
      confirmLabel: 'Eliminar',
      cancelLabel: 'Cancelar',
    });
    expect(removeItem).toHaveBeenCalledWith(10);
  });

  it('does not remove the item when the delete confirmation is cancelled', async () => {
    const removeItem = vi.fn().mockReturnValue(of({}));
    const openConfirmDelete = vi.fn().mockReturnValue(of(false));

    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            updateItemQuantity: vi.fn(),
            removeItem,
          },
        },
        {
          provide: ModalService,
          useValue: {
            openConfirmDelete,
          },
        },
        {
          provide: ToastService,
          useValue: {
            success: vi.fn(),
            info: vi.fn(),
            danger: vi.fn(),
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    fixture.componentRef.setInput('items', [
      {
        cartItemId: 10,
        imageUrl: null,
        product: 'Producto de prueba',
        originalPrice: null,
        discountedPrice: 1000,
        discountPercentage: null,
        attributes: [],
        quantity: 1,
      },
    ]);
    fixture.detectChanges();

    fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('remove');

    expect(openConfirmDelete).toHaveBeenCalled();
    expect(removeItem).not.toHaveBeenCalled();
  });

  it('notifies the user and refreshes the cart when a mutation reports expiration', async () => {
    const expirationMessage = 'La reserva de stock venció. Usá el carrito activo para continuar.';
    const removeItem = vi.fn().mockReturnValue(
      throwError(() => ({
        error: {
          code: 'stock_reservation.expired',
          message: expirationMessage,
        },
      })),
    );
    const loadCart = vi.fn().mockReturnValue(of({}));
    const danger = vi.fn();

    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            loadCart,
            updateItemQuantity: vi.fn(),
            removeItem,
          },
        },
        {
          provide: ModalService,
          useValue: { openConfirmDelete: vi.fn().mockReturnValue(of(true)) },
        },
        {
          provide: ToastService,
          useValue: { success: vi.fn(), info: vi.fn(), danger },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    fixture.componentRef.setInput('items', [
      {
        cartItemId: 10,
        imageUrl: null,
        product: 'Producto vencido',
        originalPrice: null,
        discountedPrice: 1000,
        discountPercentage: null,
        attributes: [],
        quantity: 1,
      },
    ]);
    fixture.detectChanges();

    fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('remove');

    expect(danger).toHaveBeenCalledWith(expirationMessage);
    expect(loadCart).toHaveBeenCalledOnce();
  });

  it('optimistically updates quantity and rolls back on error', async () => {
    vi.useFakeTimers();
    const updateItemQuantity = vi.fn().mockReturnValue(throwError(() => new Error('Error')));
    const danger = vi.fn();

    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            updateItemQuantity,
            removeItem: vi.fn(),
          },
        },
        {
          provide: ModalService,
          useValue: {},
        },
        {
          provide: ToastService,
          useValue: {
            success: vi.fn(),
            info: vi.fn(),
            danger,
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    const component = fixture.componentInstance;

    const item = {
      cartItemId: 10,
      imageUrl: null,
      product: 'Producto de prueba',
      originalPrice: null,
      discountedPrice: 1000,
      discountPercentage: null,
      attributes: [],
      quantity: 1,
    };

    fixture.componentRef.setInput('items', [item]);
    fixture.detectChanges();

    // Trigger quantity change to 3
    fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('quantityChange', 3);
    fixture.detectChanges();

    // Optimistic update should be active immediately in local getter
    expect((component as any).getItemQuantity(item)).toBe(3);

    // Wait for the debounce time (1000ms)
    vi.advanceTimersByTime(1000);
    fixture.detectChanges();

    // After failure, it should roll back to original quantity (1)
    expect((component as any).getItemQuantity(item)).toBe(1);
    expect(danger).toHaveBeenCalled();

    vi.useRealTimers();
  });

  it('optimistically updates quantity and clears override on success', async () => {
    vi.useFakeTimers();
    const updateItemQuantity = vi.fn().mockReturnValue(of({ message: 'Success', data: {} }));
    const success = vi.fn();

    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            updateItemQuantity,
            removeItem: vi.fn(),
          },
        },
        {
          provide: ModalService,
          useValue: {},
        },
        {
          provide: ToastService,
          useValue: {
            success,
            info: vi.fn(),
            danger: vi.fn(),
          },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    const component = fixture.componentInstance;

    const item = {
      cartItemId: 10,
      imageUrl: null,
      product: 'Producto de prueba',
      originalPrice: null,
      discountedPrice: 1000,
      discountPercentage: null,
      attributes: [],
      quantity: 1,
    };

    fixture.componentRef.setInput('items', [item]);
    fixture.detectChanges();

    // Trigger quantity change to 3
    fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('quantityChange', 3);
    fixture.detectChanges();

    // Optimistic update should be active immediately in local getter
    expect((component as any).getItemQuantity(item)).toBe(3);

    // Wait for the debounce time (1000ms)
    vi.advanceTimersByTime(1000);
    fixture.detectChanges();

    // After success, it should clear override and use input quantity (which is 1 since we didn't update items input here)
    expect((component as any).getItemQuantity(item)).toBe(1);
    expect(success).toHaveBeenCalled();

    vi.useRealTimers();
  });

  it('enables quantity editing only while Modificar mode is active', async () => {
    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            updateItemQuantity: vi.fn(),
            removeItem: vi.fn(),
          },
        },
        { provide: ModalService, useValue: {} },
        {
          provide: ToastService,
          useValue: { success: vi.fn(), info: vi.fn(), danger: vi.fn() },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    fixture.componentRef.setInput('items', [
      {
        cartItemId: 10,
        imageUrl: null,
        product: 'Producto de prueba',
        originalPrice: null,
        discountedPrice: 1000,
        discountPercentage: null,
        attributes: [],
        quantity: 1,
      },
    ]);
    fixture.componentRef.setInput('requireEditingMode', true);
    const editingChange = vi.fn();
    fixture.componentInstance.editing.subscribe(editingChange);
    fixture.detectChanges();

    const editButton = fixture.debugElement.query(By.css('.cart-edit-btn'));
    expect(editButton.nativeElement.textContent.trim()).toBe('Modificar');
    expect(
      fixture.debugElement.query(By.css('app-cart-item')).componentInstance.quantityDisabled(),
    ).toBe(true);

    editButton.nativeElement.click();
    fixture.detectChanges();

    expect(editButton.nativeElement.textContent.trim()).toBe('Listo');
    expect(
      fixture.debugElement.query(By.css('app-cart-item')).componentInstance.quantityDisabled(),
    ).toBe(false);
    expect(editingChange).toHaveBeenCalledWith(true);

    editButton.nativeElement.click();
    fixture.detectChanges();

    expect(editButton.nativeElement.textContent.trim()).toBe('Modificar');
    expect(editingChange).toHaveBeenLastCalledWith(false);
  });

  it('emits Modificar as an action without toggling to Listo', async () => {
    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            updateItemQuantity: vi.fn(),
            removeItem: vi.fn(),
          },
        },
        { provide: ModalService, useValue: {} },
        {
          provide: ToastService,
          useValue: { success: vi.fn(), info: vi.fn(), danger: vi.fn() },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    fixture.componentRef.setInput('items', [
      {
        cartItemId: 10,
        imageUrl: null,
        product: 'Producto de prueba',
        originalPrice: null,
        discountedPrice: 1000,
        discountPercentage: null,
        attributes: [],
        quantity: 1,
      },
    ]);
    fixture.componentRef.setInput('readonly', true);
    fixture.componentRef.setInput('showModifyWhenReadonly', true);
    fixture.componentRef.setInput('modifyAsAction', true);
    const modify = vi.fn();
    fixture.componentInstance.modify.subscribe(modify);
    fixture.detectChanges();

    const modifyButton = fixture.debugElement.query(By.css('.cart-edit-btn'));
    expect(modifyButton.nativeElement.textContent.trim()).toBe('Modificar');

    modifyButton.nativeElement.click();
    fixture.detectChanges();

    expect(modify).toHaveBeenCalledOnce();
    expect(modifyButton.nativeElement.textContent.trim()).toBe('Modificar');
    expect(fixture.componentInstance.editing()).toBe(false);
  });

  it('allows editing directly when the optional Modificar toggle is disabled', async () => {
    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            updateItemQuantity: vi.fn(),
            removeItem: vi.fn(),
          },
        },
        { provide: ModalService, useValue: {} },
        {
          provide: ToastService,
          useValue: { success: vi.fn(), info: vi.fn(), danger: vi.fn() },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    fixture.componentRef.setInput('items', [
      {
        cartItemId: 10,
        imageUrl: null,
        product: 'Producto de prueba',
        originalPrice: null,
        discountedPrice: 1000,
        discountPercentage: null,
        attributes: [],
        quantity: 1,
      },
    ]);
    fixture.detectChanges();

    expect(fixture.debugElement.query(By.css('.cart-edit-btn'))).toBeNull();
    expect(
      fixture.debugElement.query(By.css('app-cart-item')).componentInstance.quantityDisabled(),
    ).toBe(false);
  });

  it('hides the edit toggle and disables quantity changes when quantity updates are false', async () => {
    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            updateItemQuantity: vi.fn(),
            removeItem: vi.fn(),
          },
        },
        { provide: ModalService, useValue: {} },
        {
          provide: ToastService,
          useValue: { success: vi.fn(), info: vi.fn(), danger: vi.fn() },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    fixture.componentRef.setInput('items', [
      {
        cartItemId: 10,
        imageUrl: null,
        product: 'Producto de prueba',
        originalPrice: null,
        discountedPrice: 1000,
        discountPercentage: null,
        attributes: [],
        quantity: 1,
      },
    ]);
    fixture.componentRef.setInput('requireEditingMode', true);
    fixture.componentRef.setInput('allowModify', false);
    fixture.componentRef.setInput('allowUpdateQuantity', false);
    const quantityChange = vi.fn();
    fixture.componentInstance.itemQuantityChange.subscribe(quantityChange);
    fixture.detectChanges();

    expect(fixture.debugElement.query(By.css('.cart-edit-btn'))).toBeNull();
    const cartItem = fixture.debugElement.query(By.css('app-cart-item'));
    expect(cartItem.componentInstance.quantityDisabled()).toBe(true);

    cartItem.triggerEventHandler('quantityChange', 2);

    expect(quantityChange).not.toHaveBeenCalled();
  });

  it('persists a variant selected from a cart row', async () => {
    const updateItemVariant = vi.fn().mockReturnValue(
      of({
        message: 'Variante actualizada.',
        data: {},
      }),
    );

    await TestBed.configureTestingModule({
      imports: [CartComponent],
      providers: [
        {
          provide: CartService,
          useValue: {
            cart: signal(null).asReadonly(),
            updateItemQuantity: vi.fn(),
            updateItemVariant,
            removeItem: vi.fn(),
          },
        },
        { provide: ModalService, useValue: {} },
        {
          provide: ToastService,
          useValue: { success: vi.fn(), info: vi.fn(), danger: vi.fn() },
        },
      ],
    }).compileComponents();

    const fixture = TestBed.createComponent(CartComponent);
    fixture.componentRef.setInput('items', [
      {
        cartItemId: 10,
        imageUrl: null,
        product: 'Comida',
        originalPrice: null,
        discountedPrice: 1000,
        discountPercentage: null,
        attributes: [{ label: 'Servicio', value: 'Almuerzo' }],
        quantity: 2,
        variantId: 20,
        variants: [
          { id: 20, values: { servicio: 'Almuerzo' } },
          { id: 21, values: { servicio: 'Cena' } },
        ],
      },
    ]);
    fixture.detectChanges();

    fixture.debugElement.query(By.css('app-cart-item')).triggerEventHandler('variantChange', 21);

    expect(updateItemVariant).toHaveBeenCalledWith(10, 2, 21);
  });
});
