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

import { ProductDetail } from '../../../../core/services/catalog/catalog.interface';
import { CatalogService } from '../../../../core/services/catalog/catalog.service';
import { CartService } from '../../../../core/services/cart/cart.service';
import { ToastService } from '../../../../core/services/toast.service';
import { ProductDetailPageComponent } from './product-detail-page.component';

describe('ProductDetailPageComponent', () => {
  const mockProduct: ProductDetail = {
    id: 1,
    category_id: 10,
    brand_id: null,
    slug: 'auriculares-bluetooth',
    nombre: 'Auriculares Bluetooth',
    descripcion: 'Auriculares bluetooth de prueba',
    precio: '24999',
    category: 'Tecnología',
    brand: 'Sony',
    images: ['https://example.com/image.png'],
    attributes: [],
    variants_map: [],
    variant: null
  };

  let paramMapSubject: BehaviorSubject<any>;
  let catalogServiceStub: any;
  let routerStub: any;
  let cartServiceStub: any;
  let toastServiceStub: any;

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

  beforeEach(() => {
    vi.restoreAllMocks();
    paramMapSubject = new BehaviorSubject(convertToParamMap({ id: '1' }));
    catalogServiceStub = {
      getProducto: vi.fn().mockReturnValue(of(mockProduct))
    };
    routerStub = {
      navigate: vi.fn()
    };
    cartServiceStub = {
      addItem: vi.fn().mockReturnValue(of({ message: 'Producto agregado al carrito' }))
    };
    toastServiceStub = {
      success: vi.fn(),
      danger: vi.fn(),
      info: vi.fn()
    };
  });

  async function configureTestingModule() {
    await TestBed.configureTestingModule({
      imports: [ProductDetailPageComponent],
      providers: [
        {
          provide: ActivatedRoute,
          useValue: {
            paramMap: paramMapSubject.asObservable()
          }
        },
        {
          provide: CatalogService,
          useValue: catalogServiceStub
        },
        {
          provide: Router,
          useValue: routerStub
        },
        {
          provide: CartService,
          useValue: cartServiceStub
        },
        {
          provide: ToastService,
          useValue: toastServiceStub
        }
      ]
    }).compileComponents();
  }

  it('loads the product on init and embeds the carousel', async () => {
    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    const element = fixture.nativeElement as HTMLElement;

    expect(catalogServiceStub.getProducto).toHaveBeenCalledWith(1);

    expect(element.querySelector('app-product-carousel')).not.toBeNull();
    expect(element.querySelector('.product-detail__title')?.textContent).toContain('Auriculares Bluetooth');
    expect(element.querySelector('.product-detail__price-current')?.textContent).toContain('$24.999');
    expect(element.querySelector('.product-detail__price-previous')).toBeNull();
    expect(element.querySelector('.product-carousel__discount-badge')).toBeNull();
  });

  it('shows error message if load fails', async () => {
    catalogServiceStub.getProducto.mockReturnValue(throwError(() => new Error('load failed')));
    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    const element = fixture.nativeElement as HTMLElement;

    expect(element.textContent).toContain('No pudimos cargar los detalles del producto.');
  });


  it('should use default variant images if present', async () => {
    const detailProduct: ProductDetail = {
      ...mockProduct,
      images: ['https://example.com/product.png'],
      variant: {
        id: 123,
        cantidad_maxima: 10,
        images: ['https://example.com/variant1.png', 'https://example.com/variant2.png'],
        definitions: {}
      }
    };
    catalogServiceStub.getProducto.mockReturnValue(of(detailProduct));

    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    const carousel = fixture.debugElement.query(By.css('app-product-carousel')).componentInstance;
    expect(carousel.images()).toEqual(['https://example.com/variant1.png', 'https://example.com/variant2.png']);
  });

  it('should fallback to product images if default variant images are not present', async () => {
    const detailProduct: ProductDetail = {
      ...mockProduct,
      images: ['https://example.com/product.png'],
      variant: null
    };
    catalogServiceStub.getProducto.mockReturnValue(of(detailProduct));

    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    const carousel = fixture.debugElement.query(By.css('app-product-carousel')).componentInstance;
    expect(carousel.images()).toEqual(['https://example.com/product.png']);
  });

  it('should fallback to empty array if no images are present at all', async () => {
    const detailProduct: ProductDetail = {
      ...mockProduct,
      images: [],
      variant: null
    };
    catalogServiceStub.getProducto.mockReturnValue(of(detailProduct));

    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    const carousel = fixture.debugElement.query(By.css('app-product-carousel')).componentInstance;
    expect(carousel.images()).toEqual([]);
  });

  it('renders generic attributes and preselects default variant options', async () => {
    const detailProduct: ProductDetail = {
      ...mockProduct,
      attributes: [
        {
          id: 1,
          codigo: 'color',
          nombre: 'Color',
          is_required: true,
          metadata_schema: null,
          type: 'select',
          options: [
            {
              id: 10,
              value: 'beige',
              label: 'Beige',
              sort_order: 1,
              metadata: { hex: '#D8D1C7' }
            },
            {
              id: 11,
              value: 'brown',
              label: 'Marrón',
              sort_order: 2,
              metadata: { palette: { primary: '#7E6460' } }
            }
          ]
        },
        {
          id: 2,
          codigo: 'material',
          nombre: 'Material',
          is_required: true,
          metadata_schema: null,
          type: 'select',
          options: [
            {
              id: 20,
              value: 'mesh',
              label: 'Mesh',
              sort_order: 1,
              metadata: null
            },
            {
              id: 21,
              value: 'cuero',
              label: 'Cuero',
              sort_order: 2,
              metadata: null
            }
          ]
        }
      ],
      variant: {
        id: 123,
        cantidad_maxima: 10,
        images: ['https://example.com/variant1.png'],
        definitions: {
          color: 'beige',
          material: 'Cuero'
        }
      }
    };
    catalogServiceStub.getProducto.mockReturnValue(of(detailProduct));

    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    const element = fixture.nativeElement as HTMLElement;
    const swatches = element.querySelectorAll('.attribute-selector__swatch');
    const textOptions = element.querySelectorAll('.attribute-selector__text-option');
    const labels = Array.from(element.querySelectorAll('.attribute-selector__label')).map((label) =>
      label.textContent?.trim()
    );

    expect(labels).toEqual(['Color:', 'Material:']);
    expect(swatches).toHaveLength(2);
    expect(textOptions).toHaveLength(2);
    expect(swatches[0].classList.contains('attribute-selector__swatch--selected')).toBe(true);
    expect(textOptions[1].classList.contains('attribute-selector__text-option--selected')).toBe(true);
    expect((swatches[0] as HTMLElement).style.backgroundColor).not.toBe('');
  });

  it('hides the attributes block and its extra divider when no attributes are present', async () => {
    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    const element = fixture.nativeElement as HTMLElement;

    expect(element.querySelector('.product-detail__section--attributes')).toBeNull();
    expect(element.querySelectorAll('.product-detail__divider')).toHaveLength(2);
  });

  it('keeps quantity at a minimum of one and increments locally', async () => {
    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    fixture.componentInstance['selectedVariant'].set({
      variant_id: 1,
      cantidad_maxima: 10,
      attributes: {}
    });
    fixture.detectChanges();

    const element = fixture.nativeElement as HTMLElement;
    const decreaseButton = element.querySelector(
      '[aria-label="Disminuir cantidad"]'
    ) as HTMLButtonElement;
    const increaseButton = element.querySelector(
      '[aria-label="Aumentar cantidad"]'
    ) as HTMLButtonElement;

    decreaseButton.click();
    fixture.detectChanges();

    expect(element.querySelector('.quantity-selector__value')?.textContent?.trim()).toBe('1');

    increaseButton.click();
    increaseButton.click();
    fixture.detectChanges();

    expect(element.querySelector('.quantity-selector__value')?.textContent?.trim()).toBe('3');
  });

  it('toggles the full description state when overflow is available', async () => {
    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    (fixture.componentInstance as any).descriptionHasOverflow.set(true);
    fixture.detectChanges();

    const element = fixture.nativeElement as HTMLElement;
    const toggleButton = element.querySelector(
      '.product-detail__description-toggle'
    ) as HTMLButtonElement;

    expect(toggleButton.textContent?.trim()).toBe('Mostrar más');

    toggleButton.click();
    fixture.detectChanges();

    expect(
      element
        .querySelector('.product-detail__description-body')
        ?.classList.contains('product-detail__description-body--expanded')
    ).toBe(true);
    expect(toggleButton.textContent?.trim()).toBe('Mostrar menos');
  });

  it('renders purchase actions without side effects', async () => {
    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    const buttons = Array.from(
      fixture.nativeElement.querySelectorAll('app-button button')
    ) as HTMLButtonElement[];

    expect(buttons.map((button) => button.textContent?.trim())).toEqual([
      'Agregar al carrito',
      'Comprar'
    ]);

    buttons[0].click();
    buttons[1].click();

    expect(routerStub.navigate).not.toHaveBeenCalled();
  });

  it('calls CartService.addItem when variant is selected and Add to Cart is clicked', async () => {
    const detailProduct: ProductDetail = {
      ...mockProduct,
      variant: {
        id: 123,
        cantidad_maxima: 5,
        images: [],
        definitions: {}
      },
      variants_map: [
        {
          variant_id: 123,
          cantidad_maxima: 5,
          attributes: {}
        }
      ]
    };
    catalogServiceStub.getProducto.mockReturnValue(of(detailProduct));

    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    // Select the variant
    fixture.componentInstance['selectedVariant'].set(detailProduct.variants_map[0]);
    fixture.componentInstance['quantity'].set(3);
    fixture.detectChanges();

    const element = fixture.nativeElement as HTMLElement;
    const addToCartButton = Array.from(element.querySelectorAll('app-button button')).find(
      (btn) => btn.textContent?.trim() === 'Agregar al carrito'
    ) as HTMLButtonElement;

    expect(addToCartButton).toBeDefined();
    addToCartButton.click();
    fixture.detectChanges();

    expect(cartServiceStub.addItem).toHaveBeenCalledWith(123, 3);
    expect(toastServiceStub.success).toHaveBeenCalledWith('Producto agregado al carrito');
  });

  it('shows error toast when CartService.addItem fails', async () => {
    const detailProduct: ProductDetail = {
      ...mockProduct,
      variant: {
        id: 123,
        cantidad_maxima: 5,
        images: [],
        definitions: {}
      },
      variants_map: [
        {
          variant_id: 123,
          cantidad_maxima: 5,
          attributes: {}
        }
      ]
    };
    catalogServiceStub.getProducto.mockReturnValue(of(detailProduct));
    cartServiceStub.addItem.mockReturnValue(throwError(() => new Error('Failed to add')));

    await configureTestingModule();
    const fixture = TestBed.createComponent(ProductDetailPageComponent);
    fixture.detectChanges();

    fixture.componentInstance['selectedVariant'].set(detailProduct.variants_map[0]);
    fixture.detectChanges();

    const element = fixture.nativeElement as HTMLElement;
    const addToCartButton = Array.from(element.querySelectorAll('app-button button')).find(
      (btn) => btn.textContent?.trim() === 'Agregar al carrito'
    ) as HTMLButtonElement;

    addToCartButton.click();
    fixture.detectChanges();

    expect(cartServiceStub.addItem).toHaveBeenCalled();
    expect(toastServiceStub.danger).toHaveBeenCalledWith('No se pudo agregar el producto al carrito.');
  });
});
