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('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', [
      {
        productVariantId: 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).toHaveBeenCalledWith({
      title: 'Eliminar producto',
      content:
        'Se eliminara "Producto de prueba" del carrito. Esta accion 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', [
      {
        productVariantId: 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('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 = {
      productVariantId: 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 = {
      productVariantId: 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();
  });
});
