import { ComponentFixture, TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { RewardOrderConfirmationComponent } from './reward-order-confirmation.component';
import { ActivatedRoute, convertToParamMap, ParamMap, provideRouter } from '@angular/router';
import { TabService } from '@isa/core/tabs';
import { OrderConfiramtionStore } from './reward-order-confirmation.store';
import { signal } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { provideProductImageUrl } from '@isa/shared/product-image';
import { provideProductRouterLinkBuilder } from '@isa/shared/product-router-link';
import { DisplayOrdersResource } from '@isa/oms/data-access';

describe('RewardOrderConfirmationComponent', () => {
  let component: RewardOrderConfirmationComponent;
  let fixture: ComponentFixture<RewardOrderConfirmationComponent>;
  let paramMapSubject: BehaviorSubject<ParamMap>;
  let mockStore: {
    orders: ReturnType<typeof signal>;
    shoppingCart: ReturnType<typeof signal>;
    payers: ReturnType<typeof signal>;
    shippingAddresses: ReturnType<typeof signal>;
    targetBranches: ReturnType<typeof signal>;
    hasDeliveryOrderTypeFeature: ReturnType<typeof signal>;
    hasTargetBranchFeature: ReturnType<typeof signal>;
    patch: ReturnType<typeof vi.fn>;
  };
  let mockTabService: {
    activatedTabId: ReturnType<typeof signal>;
  };
  let mockDisplayOrdersResource: {
    orders: ReturnType<typeof signal>;
    loading: ReturnType<typeof signal>;
    error: ReturnType<typeof signal>;
    loadOrder: ReturnType<typeof vi.fn>;
    loadOrders: ReturnType<typeof vi.fn>;
    refresh: ReturnType<typeof vi.fn>;
  };

  beforeEach(() => {
    // Create mock paramMap subject
    paramMapSubject = new BehaviorSubject<ParamMap>(convertToParamMap({}));

    // Create mock store with all signals from OrderConfiramtionStore
    mockStore = {
      orders: signal([]),
      shoppingCart: signal(null),
      payers: signal([]),
      shippingAddresses: signal([]),
      targetBranches: signal([]),
      hasDeliveryOrderTypeFeature: signal(false),
      hasTargetBranchFeature: signal(false),
      patch: vi.fn(),
    };

    // Create mock DisplayOrdersResource
    mockDisplayOrdersResource = {
      orders: signal([]),
      loading: signal(false),
      error: signal(null),
      loadOrder: vi.fn(),
      loadOrders: vi.fn(),
      refresh: vi.fn(),
    };

    // Create mock TabService with writable signal
    mockTabService = {
      activatedTabId: signal(null),
    };

    TestBed.configureTestingModule({
      imports: [RewardOrderConfirmationComponent],
      providers: [
        {
          provide: ActivatedRoute,
          useValue: {
            paramMap: paramMapSubject.asObservable(),
          },
        },
        { provide: TabService, useValue: mockTabService },
        provideRouter([]),
        provideProductImageUrl('https://test.example.com'),
        provideProductRouterLinkBuilder((ean: string) => `/product/${ean}`),
        provideHttpClient(),
      ],
    });

    // Override component's providers to use our mock store and resource
    TestBed.overrideComponent(RewardOrderConfirmationComponent, {
      set: {
        providers: [
          { provide: OrderConfiramtionStore, useValue: mockStore },
          { provide: DisplayOrdersResource, useValue: mockDisplayOrdersResource },
        ],
      },
    });

    // Don't create fixture here - let each test create it after setting up params
  });

  it('should create', () => {
    // Arrange
    fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
    component = fixture.componentInstance;

    // Act
    fixture.detectChanges();

    // Assert
    expect(component).toBeTruthy();
  });

  describe('orderIds computed signal', () => {
    it('should return empty array when no params', () => {
      // Arrange - recreate subject with correct initial value
      paramMapSubject = new BehaviorSubject<ParamMap>(convertToParamMap({}));
      TestBed.overrideProvider(ActivatedRoute, {
        useValue: { paramMap: paramMapSubject.asObservable() },
      });
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      expect(component.orderIds()).toEqual([]);
    });

    it('should parse single order ID from route params', () => {
      // Arrange - recreate subject with correct initial value
      paramMapSubject = new BehaviorSubject<ParamMap>(convertToParamMap({ displayOrderIds: '123' }));
      TestBed.overrideProvider(ActivatedRoute, {
        useValue: { paramMap: paramMapSubject.asObservable() },
      });
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      expect(component.orderIds()).toEqual([123]);
    });

    it('should parse multiple order IDs from route params', () => {
      // Arrange - recreate subject with correct initial value
      paramMapSubject = new BehaviorSubject<ParamMap>(convertToParamMap({ displayOrderIds: '123,456,789' }));
      TestBed.overrideProvider(ActivatedRoute, {
        useValue: { paramMap: paramMapSubject.asObservable() },
      });
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      expect(component.orderIds()).toEqual([123, 456, 789]);
    });

    it('should handle single digit order IDs', () => {
      // Arrange - recreate subject with correct initial value
      paramMapSubject = new BehaviorSubject<ParamMap>(convertToParamMap({ displayOrderIds: '1,2,3' }));
      TestBed.overrideProvider(ActivatedRoute, {
        useValue: { paramMap: paramMapSubject.asObservable() },
      });
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      expect(component.orderIds()).toEqual([1, 2, 3]);
    });

    it('should return empty array for empty string param', () => {
      // Arrange - recreate subject with correct initial value
      paramMapSubject = new BehaviorSubject<ParamMap>(convertToParamMap({ displayOrderIds: '' }));
      TestBed.overrideProvider(ActivatedRoute, {
        useValue: { paramMap: paramMapSubject.asObservable() },
      });
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      // Empty string is falsy, so the ternary returns [] instead of splitting
      expect(component.orderIds()).toEqual([]);
    });
  });

  describe('store integration', () => {
    it('should call store.patch with tabId and orderIds', () => {
      // Arrange - set up state before creating component
      mockTabService.activatedTabId.set('test-tab-123');
      paramMapSubject = new BehaviorSubject<ParamMap>(convertToParamMap({ displayOrderIds: '456' }));
      TestBed.overrideProvider(ActivatedRoute, {
        useValue: { paramMap: paramMapSubject.asObservable() },
      });
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();
      TestBed.flushEffects();

      // Assert
      expect(mockStore.patch).toHaveBeenCalledWith({
        tabId: 'test-tab-123',
        orderIds: [456],
      });
    });

    it('should call store.patch with undefined tabId when no tab is active', () => {
      // Arrange - set up state before creating component
      mockTabService.activatedTabId.set(null);
      paramMapSubject = new BehaviorSubject<ParamMap>(convertToParamMap({ displayOrderIds: '789' }));
      TestBed.overrideProvider(ActivatedRoute, {
        useValue: { paramMap: paramMapSubject.asObservable() },
      });
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();
      TestBed.flushEffects();

      // Assert
      expect(mockStore.patch).toHaveBeenCalledWith({
        tabId: undefined,
        orderIds: [789],
      });
    });

    it('should update store when route params change', () => {
      // Arrange - create component with initial params
      paramMapSubject = new BehaviorSubject<ParamMap>(convertToParamMap({ displayOrderIds: '111' }));
      TestBed.overrideProvider(ActivatedRoute, {
        useValue: { paramMap: paramMapSubject.asObservable() },
      });
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
      TestBed.flushEffects();

      // Reset mock
      mockStore.patch.mockClear();

      // Act - change route params
      paramMapSubject.next(convertToParamMap({ displayOrderIds: '222,333' }));
      fixture.detectChanges();
      TestBed.flushEffects();

      // Assert
      expect(mockStore.patch).toHaveBeenCalledWith({
        tabId: undefined,
        orderIds: [222, 333],
      });
    });
  });

  describe('template rendering', () => {
    it('should render header component', () => {
      // Arrange
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      const header: DebugElement = fixture.debugElement.query(
        By.css('checkout-order-confirmation-header')
      );
      expect(header).toBeTruthy();
    });

    it('should render addresses component', () => {
      // Arrange
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      const addresses: DebugElement = fixture.debugElement.query(
        By.css('checkout-order-confirmation-addresses')
      );
      expect(addresses).toBeTruthy();
    });

    it('should render order item lists for each order', () => {
      // Arrange
      mockStore.orders.set([
        { id: 1, items: [], features: { orderType: 'Versand' } },
        { id: 2, items: [], features: { orderType: 'Versand' } },
        { id: 3, items: [], features: { orderType: 'Versand' } },
      ] as any);

      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      // There's always exactly one item list component that renders all orders internally
      const itemLists = fixture.debugElement.queryAll(
        By.css('checkout-order-confirmation-item-list')
      );
      expect(itemLists.length).toBe(1);
    });

    it('should not render item lists when orders array is empty', () => {
      // Arrange
      mockStore.orders.set([]);
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      // The item list component always exists, but it won't render any order groups
      const itemLists = fixture.debugElement.queryAll(
        By.css('checkout-order-confirmation-item-list')
      );
      expect(itemLists.length).toBe(1);
    });

    it('should pass order to item list component', () => {
      // Arrange
      const testOrder = {
        id: 1,
        items: [{ id: 1, product: { ean: '123', catalogProductNumber: 'CAT-123' } }],
        features: { orderType: 'Versand' },
      } as any;

      mockStore.orders.set([testOrder]);

      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      const itemList: DebugElement = fixture.debugElement.query(
        By.css('checkout-order-confirmation-item-list')
      );
      expect(itemList).toBeTruthy();
      // The item list component gets all orders from the store, not individual orders
      expect(itemList.componentInstance.orders()).toEqual([testOrder]);
    });
  });

  describe('orders signal', () => {
    it('should expose orders from store', () => {
      // Arrange
      const testOrders = [
        { id: 1, items: [] },
        { id: 2, items: [] },
      ] as any;

      // Update the mock store signal
      mockStore.orders.set(testOrders);
      fixture = TestBed.createComponent(RewardOrderConfirmationComponent);
      component = fixture.componentInstance;

      // Act
      fixture.detectChanges();

      // Assert
      expect(component.orders()).toEqual(testOrders);
    });
  });
});
