import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { RemissionListItemComponent } from './remission-list-item.component';
import {
  ReturnItem,
  ReturnSuggestion,
  StockInfo,
  RemissionListType,
  RemissionStore,
} from '@isa/remission/data-access';
import {
  ProductInfoComponent,
  ProductStockInfoComponent,
  ProductShelfMetaInfoComponent,
} from '@isa/remission/shared/product';
import { MockComponent } from 'ng-mocks';
import { RemissionListItemSelectComponent } from './remission-list-item-select.component';
import { RemissionListItemActionsComponent } from './remission-list-item-actions.component';
import { LabelComponent } from '@isa/ui/label';
import { signal } from '@angular/core';

// --- Setup dynamic mocking for injectRemissionListType ---
let remissionListTypeValue: RemissionListType = RemissionListType.Pflicht;
jest.mock('../injects/inject-remission-list-type', () => ({
  injectRemissionListType: () => () => remissionListTypeValue,
}));

// Mock the breakpoint function
jest.mock('@isa/ui/layout', () => ({
  breakpoint: jest.fn(() => jest.fn(() => true)), // Default to desktop
  Breakpoint: {
    DekstopL: 'DekstopL',
    DekstopXL: 'DekstopXL',
  },
}));

// Mock the calculation functions to have predictable behavior
jest.mock('@isa/remission/data-access', () => ({
  ...jest.requireActual('@isa/remission/data-access'),
  getStockToRemit: jest.fn(),
  calculateAvailableStock: jest.fn(),
  calculateTargetStock: jest.fn(),
}));

// Mock the RemissionStore
const mockRemissionStore = {
  selectedQuantity: signal({}),
  removeItem: jest.fn(),
};

describe('RemissionListItemComponent', () => {
  let component: RemissionListItemComponent;
  let fixture: ComponentFixture<RemissionListItemComponent>;
  const setRemissionListType = (type: RemissionListType) => {
    remissionListTypeValue = type;
  };

  const createMockReturnItem = (
    overrides: Partial<ReturnItem> = {},
  ): ReturnItem =>
    ({
      id: 1,
      remainingQuantityInStock: 10,
      ...overrides,
    }) as ReturnItem;

  const createMockReturnSuggestion = (
    overrides: Partial<ReturnSuggestion> = {},
  ): ReturnSuggestion =>
    ({
      id: 1,
      remainingQuantityInStock: 10,
      returnItem: {
        data: {
          id: 1,
        },
      },
      ...overrides,
    }) as ReturnSuggestion;

  const createMockStockInfo = (overrides: Partial<StockInfo> = {}): StockInfo =>
    ({
      id: 1,
      inStock: 100,
      removedFromStock: 0,
      ...overrides,
    }) as StockInfo;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        RemissionListItemComponent,
        MockComponent(ProductInfoComponent),
        MockComponent(ProductStockInfoComponent),
        MockComponent(ProductShelfMetaInfoComponent),
        MockComponent(RemissionListItemSelectComponent),
        MockComponent(RemissionListItemActionsComponent),
        MockComponent(LabelComponent),
      ],
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        { provide: RemissionStore, useValue: mockRemissionStore },
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(RemissionListItemComponent);
    component = fixture.componentInstance;
  });

  beforeEach(() => {
    // Reset mocks before each test
    jest.clearAllMocks();
    mockRemissionStore.selectedQuantity.set({});
    mockRemissionStore.removeItem.mockClear();

    // Reset the mocked functions to return default values
    const {
      getStockToRemit,
      calculateAvailableStock,
      calculateTargetStock,
    } = require('@isa/remission/data-access');
    getStockToRemit.mockReturnValue(0);
    calculateAvailableStock.mockReturnValue(100);
    calculateTargetStock.mockReturnValue(100);
  });

  describe('Component Setup', () => {
    it('should create', () => {
      fixture.componentRef.setInput('item', createMockReturnItem());
      fixture.componentRef.setInput('stock', createMockStockInfo());
      expect(component).toBeTruthy();
    });

    it('should have item as required input', () => {
      fixture.componentRef.setInput('item', createMockReturnItem());
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.detectChanges();
      expect(component.item()).toBeDefined();
    });

    it('should have stock as required input', () => {
      fixture.componentRef.setInput('item', createMockReturnItem());
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.detectChanges();
      expect(component.stock()).toBeDefined();
    });

    it('should have productGroupValue with default empty string', () => {
      fixture.componentRef.setInput('item', createMockReturnItem());
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.detectChanges();
      expect(component.productGroupValue()).toBe('');
    });

    it('should accept productGroupValue input', () => {
      const testValue = 'Test Group';
      fixture.componentRef.setInput('item', createMockReturnItem());
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.componentRef.setInput('productGroupValue', testValue);
      fixture.detectChanges();
      expect(component.productGroupValue()).toBe(testValue);
    });

    it('should have stockFetching input with false default', () => {
      fixture.componentRef.setInput('item', createMockReturnItem());
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.detectChanges();
      expect(component.stockFetching()).toBe(false);
    });

    it('should accept stockFetching input value', () => {
      fixture.componentRef.setInput('item', createMockReturnItem());
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.componentRef.setInput('stockFetching', true);
      fixture.detectChanges();
      expect(component.stockFetching()).toBe(true);
    });

    it('should have removeOrUpdateItem output', () => {
      fixture.componentRef.setInput('item', createMockReturnItem());
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.detectChanges();
      expect(component.removeOrUpdateItem).toBeDefined();
    });
  });

  describe('computed properties', () => {
    describe('desktopBreakpoint', () => {
      it('should be defined and accessible', () => {
        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.desktopBreakpoint).toBeDefined();
        expect(typeof component.desktopBreakpoint()).toBe('boolean');
      });
    });

    describe('remissionListType', () => {
      it('should return injected remission list type', () => {
        setRemissionListType(RemissionListType.Abteilung);
        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.remissionListType()).toBe(RemissionListType.Abteilung);
      });

      it('should update when remission list type changes', () => {
        setRemissionListType(RemissionListType.Pflicht);
        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.remissionListType()).toBe(RemissionListType.Pflicht);

        setRemissionListType(RemissionListType.Abteilung);
        expect(component.remissionListType()).toBe(RemissionListType.Abteilung);
      });
    });

    describe('availableStock', () => {
      it('should calculate available stock correctly', () => {
        const {
          calculateAvailableStock,
        } = require('@isa/remission/data-access');
        calculateAvailableStock.mockReturnValue(90);

        const mockStock = createMockStockInfo({
          inStock: 100,
          removedFromStock: 10,
        });
        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', mockStock);
        fixture.detectChanges();

        expect(component.availableStock()).toBe(90);
        expect(calculateAvailableStock).toHaveBeenCalledWith({
          stock: 100,
          removedFromStock: 10,
        });
      });
    });

    describe('stockToRemit', () => {
      it('should calculate stock to remit correctly', () => {
        const { getStockToRemit } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(25);

        const mockItem = createMockReturnItem();
        const mockStock = createMockStockInfo();
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', mockStock);
        fixture.detectChanges();

        expect(component.stockToRemit()).toBe(25);
        expect(getStockToRemit).toHaveBeenCalledWith({
          remissionItem: mockItem,
          remissionListType: remissionListTypeValue,
          availableStock: 100, // default mock value
        });
      });
    });

    describe('targetStock', () => {
      it('should calculate target stock with remainingQuantityInStock when selected quantity matches stock to remit', () => {
        const {
          calculateTargetStock,
          getStockToRemit,
        } = require('@isa/remission/data-access');
        calculateTargetStock.mockReturnValue(75);
        getStockToRemit.mockReturnValue(25);
        mockRemissionStore.selectedQuantity.set({ 1: 25 }); // Same as stockToRemit

        const mockItem = createMockReturnItem({
          id: 1,
          remainingQuantityInStock: 15,
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.targetStock()).toBe(75);
        expect(calculateTargetStock).toHaveBeenCalledWith({
          availableStock: 100, // default mock value
          stockToRemit: 25,
          remainingQuantityInStock: 15,
        });
      });

      it('should calculate target stock without remainingQuantityInStock when selected quantity differs from stock to remit', () => {
        const {
          calculateTargetStock,
          getStockToRemit,
        } = require('@isa/remission/data-access');
        calculateTargetStock.mockReturnValue(80);
        getStockToRemit.mockReturnValue(25);
        mockRemissionStore.selectedQuantity.set({ 1: 20 }); // Different from stockToRemit

        const mockItem = createMockReturnItem({
          id: 1,
          remainingQuantityInStock: 15,
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.targetStock()).toBe(80);
        expect(calculateTargetStock).toHaveBeenCalledWith({
          availableStock: 100, // default mock value
          stockToRemit: 20, // selected quantity, not calculated stockToRemit
        });
      });

      it('should calculate target stock with remainingQuantityInStock when no selected quantity exists', () => {
        const {
          calculateTargetStock,
          getStockToRemit,
        } = require('@isa/remission/data-access');
        calculateTargetStock.mockReturnValue(75);
        getStockToRemit.mockReturnValue(25);
        mockRemissionStore.selectedQuantity.set({}); // No selected quantity

        const mockItem = createMockReturnItem({
          id: 1,
          remainingQuantityInStock: 15,
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.targetStock()).toBe(75);
        expect(calculateTargetStock).toHaveBeenCalledWith({
          availableStock: 100, // default mock value
          stockToRemit: 25, // calculated stockToRemit
          remainingQuantityInStock: 15,
        });
      });
    });

    describe('remainingQuantityInStock', () => {
      it('should return remainingQuantityInStock from item', () => {
        const mockItem = createMockReturnItem({ remainingQuantityInStock: 42 });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.remainingQuantityInStock()).toBe(42);
      });

      it('should handle undefined remainingQuantityInStock', () => {
        const mockItem = createMockReturnItem({
          remainingQuantityInStock: undefined,
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.remainingQuantityInStock()).toBeUndefined();
      });
    });

    describe('selectedStockToRemit', () => {
      it('should return selected quantity from store', () => {
        mockRemissionStore.selectedQuantity.set({ 1: 15 });

        const mockItem = createMockReturnItem({ id: 1 });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.selectedStockToRemit()).toBe(15);
      });

      it('should return undefined when no selected quantity exists', () => {
        mockRemissionStore.selectedQuantity.set({});

        const mockItem = createMockReturnItem({ id: 1 });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.selectedStockToRemit()).toBeUndefined();
      });
    });

    describe('selectedQuantityDiffersFromStockToRemit', () => {
      it('should return true when selected quantity differs from stock to remit', () => {
        const { getStockToRemit } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(10);
        mockRemissionStore.selectedQuantity.set({ 1: 15 });

        const mockItem = createMockReturnItem({ id: 1 });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.selectedQuantityDiffersFromStockToRemit()).toBe(true);
      });

      it('should return false when selected quantity equals stock to remit', () => {
        const { getStockToRemit } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(15);
        mockRemissionStore.selectedQuantity.set({ 1: 15 });

        const mockItem = createMockReturnItem({ id: 1 });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.selectedQuantityDiffersFromStockToRemit()).toBe(false);
      });

      it('should return false when no selected quantity exists', () => {
        const { getStockToRemit } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(10);
        mockRemissionStore.selectedQuantity.set({});

        const mockItem = createMockReturnItem({ id: 1 });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.selectedQuantityDiffersFromStockToRemit()).toBe(false);
      });
    });

    describe('hasStockToRemit', () => {
      it('should return true when both availableStock > 0 and stockToRemit > 0', () => {
        const {
          getStockToRemit,
          calculateAvailableStock,
        } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(5);
        calculateAvailableStock.mockReturnValue(10);

        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.hasStockToRemit()).toBe(true);
      });

      it('should return false when stockToRemit is 0 even if availableStock > 0', () => {
        const {
          getStockToRemit,
          calculateAvailableStock,
        } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(0);
        calculateAvailableStock.mockReturnValue(10);

        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.hasStockToRemit()).toBe(false);
      });

      it('should return false when stockToRemit is negative even if availableStock > 0', () => {
        const {
          getStockToRemit,
          calculateAvailableStock,
        } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(-1);
        calculateAvailableStock.mockReturnValue(10);

        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.hasStockToRemit()).toBe(false);
      });

      it('should return false when availableStock is 0 even if stockToRemit > 0', () => {
        const {
          getStockToRemit,
          calculateAvailableStock,
        } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(5);
        calculateAvailableStock.mockReturnValue(0);

        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.hasStockToRemit()).toBe(false);
      });

      it('should return false when availableStock is negative even if stockToRemit > 0', () => {
        const {
          getStockToRemit,
          calculateAvailableStock,
        } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(5);
        calculateAvailableStock.mockReturnValue(-1);

        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.hasStockToRemit()).toBe(false);
      });

      it('should return false when both availableStock and stockToRemit are 0', () => {
        const {
          getStockToRemit,
          calculateAvailableStock,
        } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(0);
        calculateAvailableStock.mockReturnValue(0);

        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.hasStockToRemit()).toBe(false);
      });

      it('should return false when both availableStock and stockToRemit are negative', () => {
        const {
          getStockToRemit,
          calculateAvailableStock,
        } = require('@isa/remission/data-access');
        getStockToRemit.mockReturnValue(-1);
        calculateAvailableStock.mockReturnValue(-2);

        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.hasStockToRemit()).toBe(false);
      });
    });

    describe('remiProductInfoOrientation', () => {
      it('should return a valid orientation value', () => {
        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        const orientation = component.remiProductInfoOrientation();
        expect(['horizontal', 'vertical']).toContain(orientation);
      });

      it('should depend on desktop breakpoint', () => {
        fixture.componentRef.setInput('item', createMockReturnItem());
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        // The function should compute based on the breakpoint
        const orientation = component.remiProductInfoOrientation();
        expect(typeof orientation).toBe('string');
        expect(['horizontal', 'vertical']).toContain(orientation);
      });
    });

    describe('displayImpediment', () => {
      it('should return truthy when item has impediment', () => {
        const mockItem = createMockReturnItem({
          impediment: {
            comment: 'Test impediment',
            attempts: 2,
          },
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.displayImpediment()).toBeTruthy();
      });

      it('should return truthy when item is descendant of enabled impediment', () => {
        const mockItem = createMockReturnItem({
          descendantOf: {
            enabled: true,
          },
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.displayImpediment()).toBeTruthy();
      });

      it('should return falsy when item has no impediment and is not descendant of enabled impediment', () => {
        const mockItem = createMockReturnItem({
          impediment: undefined,
          descendantOf: {
            enabled: false,
          },
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.displayImpediment()).toBeFalsy();
      });

      it('should return falsy when item has no impediment and no descendantOf property', () => {
        const mockItem = createMockReturnItem({
          impediment: undefined,
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.displayImpediment()).toBeFalsy();
      });
    });

    describe('impediment', () => {
      it('should return impediment comment when available', () => {
        const mockItem = createMockReturnItem({
          impediment: {
            comment: 'Custom impediment message',
            attempts: 3,
          },
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.impediment()).toBe('Custom impediment message (3)');
      });

      it('should return default "Restmenge" when no comment provided', () => {
        const mockItem = createMockReturnItem({
          impediment: {
            attempts: 2,
          },
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.impediment()).toBe('Restmenge (2)');
      });

      it('should return only comment when no attempts provided', () => {
        const mockItem = createMockReturnItem({
          impediment: {
            comment: 'Custom message',
          },
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.impediment()).toBe('Custom message');
      });

      it('should return default "Restmenge" when impediment is empty object', () => {
        const mockItem = createMockReturnItem({
          impediment: {},
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.impediment()).toBe('Restmenge');
      });

      it('should return "Restmenge" when impediment is undefined', () => {
        const mockItem = createMockReturnItem({
          impediment: undefined,
        });
        fixture.componentRef.setInput('item', mockItem);
        fixture.componentRef.setInput('stock', createMockStockInfo());
        fixture.detectChanges();

        expect(component.impediment()).toBe('Restmenge');
      });
    });
  });

  describe('Component behavior with different remission list types', () => {
    it('should call getStockToRemit with correct parameters for Pflicht type', () => {
      setRemissionListType(RemissionListType.Pflicht);
      const { getStockToRemit } = require('@isa/remission/data-access');

      const mockItem = createMockReturnItem();
      fixture.componentRef.setInput('item', mockItem);
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.detectChanges();

      // Access the computed property to trigger the calculation
      component.stockToRemit();

      expect(getStockToRemit).toHaveBeenCalledWith({
        remissionItem: mockItem,
        remissionListType: RemissionListType.Pflicht,
        availableStock: 100,
      });
    });

    it('should call getStockToRemit with correct parameters for Abteilung type', () => {
      setRemissionListType(RemissionListType.Abteilung);
      const { getStockToRemit } = require('@isa/remission/data-access');

      const mockSuggestion = createMockReturnSuggestion();
      fixture.componentRef.setInput('item', mockSuggestion);
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.detectChanges();

      // Access the computed property to trigger the calculation
      component.stockToRemit();

      expect(getStockToRemit).toHaveBeenCalledWith({
        remissionItem: mockSuggestion,
        remissionListType: RemissionListType.Abteilung,
        availableStock: 100,
      });
    });
  });

  describe('ngOnDestroy', () => {
    it('should remove item from store when component is destroyed', () => {
      // Arrange
      const mockItem = createMockReturnItem({ id: 123 });

      fixture.componentRef.setInput('item', mockItem);
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.detectChanges();

      // Act
      component.ngOnDestroy();

      // Assert
      expect(mockRemissionStore.removeItem).toHaveBeenCalledWith(123);
      expect(mockRemissionStore.removeItem).toHaveBeenCalledTimes(1);
    });

    it('should not call removeItem when item has no id', () => {
      // Arrange
      const mockItem = createMockReturnItem({ id: undefined });

      fixture.componentRef.setInput('item', mockItem);
      fixture.componentRef.setInput('stock', createMockStockInfo());
      fixture.detectChanges();

      // Act
      component.ngOnDestroy();

      // Assert
      expect(mockRemissionStore.removeItem).not.toHaveBeenCalled();
    });
  });
});
