import { signal } from '@angular/core';
import { Location } from '@angular/common';
import { RouterLink } from '@angular/router';
import {
  createRoutingFactory,
  mockProvider,
  SpectatorRouting,
} from '@ngneat/spectator/jest';
import { MockComponent } from 'ng-mocks';
import {
  ReturnCanReturnService,
  ReturnProcess,
  ReturnProcessService,
  ReturnProcessStore,
} from '@isa/oms/data-access';

import { ButtonComponent } from '@isa/ui/buttons';
import { NgIconComponent } from '@ng-icons/core';
import { ReturnProcessItemComponent } from './return-process-item/return-process-item.component';
import { ReturnProcessComponent } from './return-process.component';

const mockActivatedProcessIdSignal = signal<number | null>(123);
jest.mock('@isa/core/tabs', () => ({
  injectTabId: jest.fn(() => mockActivatedProcessIdSignal),
}));

jest.mock('scandit-web-datacapture-core', () => ({}));
jest.mock('scandit-web-datacapture-barcode', () => ({}));

describe('ReturnProcessComponent', () => {
  let spectator: SpectatorRouting<ReturnProcessComponent>;
  const mockEntitiesSignal = signal<ReturnProcess[]>([]);
  let mockReturnProcessService: jest.Mocked<ReturnProcessService>;
  const createComponent = createRoutingFactory({
    component: ReturnProcessComponent,
    // Import necessary modules/components used directly in the template
    imports: [RouterLink],
    // Mock child components and directives used in the template
    declarations: [
      MockComponent(ButtonComponent),
      MockComponent(NgIconComponent),
      MockComponent(ReturnProcessItemComponent),
    ],
    providers: [
      // Mock ReturnProcessStore and its entities signal
      mockProvider(ReturnProcessStore, {
        entities: mockEntitiesSignal,
      }),
      mockProvider(ReturnProcessService),
      mockProvider(ReturnCanReturnService, {
        canReturn: jest.fn().mockResolvedValue({ result: true }),
      }),
    ],
    // Disable initial change detection to set signals before first render
    detectChanges: false,
  });
  beforeEach(() => {
    // Reset signals before each test
    mockEntitiesSignal.set([]);
    mockActivatedProcessIdSignal.set(null); // Mock process ID
    // Create the component instance
    spectator = createComponent();

    // Get the mocked service
    mockReturnProcessService = spectator.inject(
      ReturnProcessService,
    ) as jest.Mocked<ReturnProcessService>;
    // Default mock implementation for returnProcessQuestionsProgress
    mockReturnProcessService.returnProcessQuestionsProgress = jest
      .fn()
      .mockReturnValue({ answered: 0, total: 0 });

    // Default mock implementation for eligibleForReturn
    mockReturnProcessService.eligibleForReturn = jest
      .fn()
      .mockReturnValue({ state: 'not-eligible' });
  });

  it('should create the component', () => {
    // Arrange
    mockActivatedProcessIdSignal.set(123); // Set a mock process ID
    spectator.detectChanges(); // Trigger initial render

    // Assert
    expect(spectator.component).toBeTruthy();
    expect(spectator.component.processId()).toBe(123); // Check injected process ID
  });

  it('should filter return processes by the current process ID', () => {
    // Arrange
    const processId = 123;
    mockActivatedProcessIdSignal.set(processId);
    const mockProcesses = [
      { processId: 123, id: 1 },
      { processId: 456, id: 2 },
      { processId: 123, id: 3 },
    ] as ReturnProcess[];
    mockEntitiesSignal.set(mockProcesses);

    // Act
    spectator.detectChanges();

    // Assert
    const filteredProcesses = spectator.component.returnProcesses();
    expect(filteredProcesses.length).toBe(2);
    expect(filteredProcesses.every((p) => p.processId === processId)).toBe(
      true,
    );
  });

  it('should return empty array when no process ID is available', () => {
    // Arrange
    mockActivatedProcessIdSignal.set(null);
    const mockProcesses = [
      { processId: 123, id: 1 },
      { processId: 456, id: 2 },
    ] as ReturnProcess[];
    mockEntitiesSignal.set(mockProcesses);

    // Act
    spectator.detectChanges();

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

  it('should return empty array when entities is empty', () => {
    // Arrange
    mockActivatedProcessIdSignal.set(123);
    mockEntitiesSignal.set([]);

    // Act
    spectator.detectChanges();

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

  it('should render ReturnProcessItemComponent for each return process', () => {
    // Arrange
    const processId = 123;
    mockActivatedProcessIdSignal.set(processId);
    const mockProcesses = [
      { processId: 123, id: 1 },
      { processId: 123, id: 2 },
    ] as ReturnProcess[];
    mockEntitiesSignal.set(mockProcesses);

    // Act
    spectator.detectChanges();

    // Assert
    const returnProcessItems = spectator.queryAll(ReturnProcessItemComponent);
    expect(returnProcessItems.length).toBe(2);
  });

  it('should handle back navigation when Location service is used', () => {
    // Arrange
    mockActivatedProcessIdSignal.set(123);
    spectator.detectChanges();
    const location = spectator.inject(Location);
    jest.spyOn(location, 'back');

    // Act
    const backButton = spectator.query('button[data-what="back-navigation"]');
    if (backButton) {
      spectator.click(backButton); // Simulate button click
    }

    // Assert
    expect(location.back).toHaveBeenCalled();
  });

  describe('summary navigation', () => {
    it('should have canContinueToSummary as false when no return processes exist', () => {
      // Arrange
      mockActivatedProcessIdSignal.set(123);
      mockEntitiesSignal.set([]);

      // Act
      spectator.detectChanges();

      // Assert
      expect(spectator.component.canContinueToSummary()).toBe(false);
    });

    it('should have canContinueToSummary as true when all processes have completed all questions', () => {
      // Arrange
      mockActivatedProcessIdSignal.set(123);
      const mockProcesses = [
        { processId: 123, id: 1 },
        { processId: 123, id: 2 },
      ] as ReturnProcess[];
      mockEntitiesSignal.set(mockProcesses);

      // Mock all processes have answered all questions
      mockReturnProcessService.returnProcessQuestionsProgress.mockImplementation(
        () => ({ answered: 3, total: 3 }),
      );

      // Mock eligibleForReturn to return Eligible state
      mockReturnProcessService.eligibleForReturn.mockReturnValue({
        state: 'eligible', // Use correct enum value
      });

      // Act
      spectator.detectChanges();

      // Assert
      expect(spectator.component.canContinueToSummary()).toBe(true);
    });

    it('should have canContinueToSummary as false when any process has incomplete questions', () => {
      // Arrange
      mockActivatedProcessIdSignal.set(123);
      const mockProcesses = [
        { processId: 123, id: 1 },
        { processId: 123, id: 2 },
      ] as ReturnProcess[];
      mockEntitiesSignal.set(mockProcesses);

      // Mock the first process has complete questions, but the second has incomplete
      mockReturnProcessService.returnProcessQuestionsProgress
        .mockImplementationOnce(() => ({ answered: 3, total: 3 }))
        .mockImplementationOnce(() => ({ answered: 2, total: 3 }));

      // Act
      spectator.detectChanges();

      // Assert
      expect(spectator.component.canContinueToSummary()).toBe(false);
    });

    it('should have canContinueToSummary as false when returnProcessQuestionsProgress returns null', () => {
      // Arrange
      mockActivatedProcessIdSignal.set(123);
      const mockProcesses = [{ processId: 123, id: 1 }] as ReturnProcess[];
      mockEntitiesSignal.set(mockProcesses);

      // Mock the process has null progress
      mockReturnProcessService.returnProcessQuestionsProgress.mockReturnValue(
        undefined,
      );

      // Act
      spectator.detectChanges();

      // Assert
      expect(spectator.component.canContinueToSummary()).toBe(false);
    });

    // Skip this test since it requires complex mocking of signals
    it.skip('should render "Continue to summary" button', () => {
      // Arrange
      // Create a mock process for testing
      mockActivatedProcessIdSignal.set(123);
      const mockProcesses = [{ processId: 123, id: 1 }] as ReturnProcess[];
      mockEntitiesSignal.set(mockProcesses);

      // Mock canContinueToSummary computed signal to return true
      jest
        .spyOn(spectator.component, 'canContinueToSummary')
        .mockReturnValue(true);

      // Need a better way to mock canReturn signal
      // This is challenging because canReturn is updated in the constructor effect
      // which runs immediately when the component is created

      // Act
      spectator.detectChanges();

      // Assert
      const summaryButton = spectator.query(
        'a[data-what="summary-navigation"]',
      );

      expect(summaryButton).toBeTruthy();
    });

    it('should not render "Continue to summary" button when canContinueToSummary is false', () => {
      // Arrange
      jest
        .spyOn(spectator.component, 'canContinueToSummary')
        .mockReturnValue(false);

      // Act
      spectator.detectChanges();

      // Assert
      const summaryButton = spectator.query('[data-what="summary-navigation"]');
      expect(summaryButton).toBeFalsy();
    });

    // it('should navigate to summary route when "Continue to summary" button is clicked and enabled', async () => {
    //   // Arrange
    //   jest
    //     .spyOn(spectator.component, 'canContinueToSummary')
    //     .mockReturnValue(true);
    //   spectator.detectChanges();
    //   expect(spectator.inject(Location).path()).toBe('/');
    //   await spectator.fixture.whenStable();
    //   // Get the router link directive
    //   spectator.click('a[data-what="summary-navigation"]');
    //
    //   await spectator.fixture.whenStable();
    //
    //   // Act & Assert
    //   expect(spectator.inject(Location).path()).toBe('/summary');
    // });
  });
});
