import { ComponentFixture, TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Router } from '@angular/router';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Subject, of } from 'rxjs';
import { RemissionReturnReceiptActionsComponent } from './remission-return-receipt-actions.component';
import {
  Return,
  getReceiptItemQuantityFromReturn,
  RemissionStore,
  RemissionReturnReceiptService,
} from '@isa/remission/data-access';
import { provideLoggerContext } from '@isa/core/logging';
import { injectConfirmationDialog } from '@isa/ui/dialog';
import { injectTabId } from '@isa/core/tabs';

// Mock the helper functions and services
vi.mock('@isa/remission/data-access', async () => {
  const actual = await vi.importActual('@isa/remission/data-access');
  return {
    ...actual,
    getReceiptItemQuantityFromReturn: vi.fn(),
    RemissionStore: vi.fn(),
    RemissionReturnReceiptService: vi.fn(),
  };
});

vi.mock('@angular/router', () => ({
  Router: vi.fn(),
}));

// Mock the dialog injection function
vi.mock('@isa/ui/dialog', () => ({
  injectConfirmationDialog: vi.fn(),
  DialogContentDirective: class MockDialogContentDirective {
    close = vi.fn();
    data: any;
  },
  DialogComponent: class MockDialogComponent {},
}));

// Mock the tab injection function
vi.mock('@isa/core/tabs', () => ({
  injectTabId: vi.fn(),
}));

describe('RemissionReturnReceiptActionsComponent', () => {
  let component: RemissionReturnReceiptActionsComponent;
  let fixture: ComponentFixture<RemissionReturnReceiptActionsComponent>;
  let mockRemissionStore: any;
  let mockRemissionReturnReceiptService: any;
  let mockRouter: any;
  let mockDialogRef: any;
  let mockInjectConfirmationDialog: any;
  let mockInjectTabId: any;

  const createMockReturn = (overrides: Partial<Return> = {}): Return =>
    ({
      id: 1,
      receipts: [
        { id: 101, items: [] },
        { id: 102, items: [] },
      ],
      ...overrides,
    }) as Return;

  const createMockReceipt = (id: number, items: any[] = []) => ({
    id,
    items,
  });

  beforeEach(async () => {
    // Reset all mocks before each test
    vi.clearAllMocks();

    // Create mock services
    mockRemissionStore = {
      receiptId: vi.fn().mockReturnValue(101),
      remissionStarted: vi.fn().mockReturnValue(false),
      isCurrentRemission: vi.fn().mockReturnValue(false),
      startRemission: vi.fn(),
      clearState: vi.fn(),
    };

    mockRemissionReturnReceiptService = {
      cancelReturnReceipt: vi.fn().mockResolvedValue({}),
      cancelReturn: vi.fn().mockResolvedValue({}),
    };

    mockRouter = {
      navigate: vi.fn().mockResolvedValue(true),
    };

    mockDialogRef = {
      closed: new Subject(),
    };

    // Mock the injected functions
    mockInjectConfirmationDialog = vi.fn().mockReturnValue(mockDialogRef);
    mockInjectTabId = vi.fn().mockReturnValue('test-tab-id');

    // Setup the mocks for the import functions
    vi.mocked(injectConfirmationDialog).mockReturnValue(
      mockInjectConfirmationDialog,
    );
    vi.mocked(injectTabId).mockReturnValue(mockInjectTabId);

    await TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule,
        RemissionReturnReceiptActionsComponent,
      ],
      providers: [
        { provide: RemissionStore, useValue: mockRemissionStore },
        {
          provide: RemissionReturnReceiptService,
          useValue: mockRemissionReturnReceiptService,
        },
        { provide: Router, useValue: mockRouter },
        provideLoggerContext({ component: 'Test' }),
      ],
    }).compileComponents();

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

  describe('Component Setup', () => {
    it('should create', () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);

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

    it('should have remissionReturn as required input', () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      expect(component.remissionReturn()).toBeDefined();
      expect(component.remissionReturn()).toEqual(mockReturn);
    });
  });

  describe('Computed signals', () => {
    describe('returnId', () => {
      it('should return the ID from the remission return', () => {
        const mockReturn = createMockReturn({ id: 123 });
        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        expect(component.returnId()).toBe(123);
      });
    });

    describe('receiptIds', () => {
      it('should return array of receipt IDs', () => {
        const mockReturn = createMockReturn({
          receipts: [createMockReceipt(101), createMockReceipt(102)],
        });
        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        expect(component.receiptIds()).toEqual([101, 102]);
      });

      it('should return empty array when no receipts', () => {
        const mockReturn = createMockReturn({ receipts: undefined });
        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        expect(component.receiptIds()).toEqual([]);
      });
    });

    describe('firstReceiptId', () => {
      it('should return first receipt ID when receipts exist', () => {
        const mockReturn = createMockReturn({
          receipts: [createMockReceipt(101), createMockReceipt(102)],
        });
        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        expect(component.firstReceiptId()).toBe(101);
      });

      it('should return undefined when no receipts exist', () => {
        const mockReturn = createMockReturn({ receipts: [] });
        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

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

    describe('itemQuantity', () => {
      it('should return item quantity from helper function', () => {
        const mockReturn = createMockReturn();
        const expectedQuantity = 5;

        vi.mocked(getReceiptItemQuantityFromReturn).mockReturnValue(
          expectedQuantity,
        );

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        expect(component.itemQuantity()).toBe(expectedQuantity);
        expect(getReceiptItemQuantityFromReturn).toHaveBeenCalledWith(
          mockReturn,
        );
      });
    });

    describe('isCurrentRemission', () => {
      it('should return true when return is current remission', () => {
        const mockReturn = createMockReturn({
          id: 123,
          receipts: [createMockReceipt(101)],
        });
        mockRemissionStore.receiptId.mockReturnValue(101);
        mockRemissionStore.isCurrentRemission.mockReturnValue(true);

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        expect(component.isCurrentRemission()).toBe(true);
        expect(mockRemissionStore.isCurrentRemission).toHaveBeenCalledWith({
          returnId: 123,
          receiptId: 101,
        });
      });

      it('should return false when receipt is not found in current receipts', () => {
        const mockReturn = createMockReturn({
          receipts: [createMockReceipt(101)],
        });
        mockRemissionStore.receiptId.mockReturnValue(999); // Different receipt ID

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

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

  describe('Template rendering', () => {
    beforeEach(() => {
      // Setup default mock values for template rendering tests
      vi.mocked(getReceiptItemQuantityFromReturn).mockReturnValue(3);
    });

    it('should render delete button', () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      const deleteButton = fixture.nativeElement.querySelector(
        'button[color="secondary"]',
      );
      expect(deleteButton).toBeTruthy();
      expect(deleteButton.textContent.trim()).toBe('Löschen');
    });

    it('should render continue button', () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      const continueButton = fixture.nativeElement.querySelector(
        'button[color="primary"]',
      );
      expect(continueButton).toBeTruthy();
      expect(continueButton.textContent.trim()).toBe('Befüllen');
    });

    it('should have correct data attributes for E2E testing', () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      const container = fixture.nativeElement.querySelector(
        '[data-which="return-receipt-actions"]',
      );
      expect(container).toBeTruthy();

      const deleteButton = fixture.nativeElement.querySelector(
        '[data-what="return-receipt-delete-button"]',
      );
      expect(deleteButton).toBeTruthy();

      const continueButton = fixture.nativeElement.querySelector(
        '[data-what="return-receipt-continue-button"]',
      );
      expect(continueButton).toBeTruthy();
    });

    it('should disable delete button when itemQuantity is greater than 0', () => {
      const mockReturn = createMockReturn();
      vi.mocked(getReceiptItemQuantityFromReturn).mockReturnValue(5);

      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      const deleteButton = fixture.nativeElement.querySelector(
        'button[color="secondary"]',
      );
      expect(deleteButton.disabled).toBe(true);
    });

    it('should enable delete button when itemQuantity is 0', () => {
      const mockReturn = createMockReturn();
      vi.mocked(getReceiptItemQuantityFromReturn).mockReturnValue(0);

      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      const deleteButton = fixture.nativeElement.querySelector(
        'button[color="secondary"]',
      );
      expect(deleteButton.disabled).toBe(false);
    });

    it('should never disable continue button', () => {
      const mockReturn = createMockReturn();
      vi.mocked(getReceiptItemQuantityFromReturn).mockReturnValue(5);

      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      const continueButton = fixture.nativeElement.querySelector(
        'button[color="primary"]',
      );
      expect(continueButton.disabled).toBe(false);
    });
  });

  describe('Component methods', () => {
    describe('onDelete', () => {
      it('should call service methods and emit reloadData when deleting return', async () => {
        const mockReturn = createMockReturn({
          id: 123,
          receipts: [createMockReceipt(101), createMockReceipt(102)],
        });

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        const reloadDataSpy = vi.fn();
        component.reloadData.subscribe(reloadDataSpy);

        // Mock the isCurrentRemission method to avoid the signal error
        const isCurrentRemissionSpy = vi
          .spyOn(component, 'isCurrentRemission')
          .mockReturnValue(false);

        await component.onDelete();

        // Should cancel each receipt
        expect(
          mockRemissionReturnReceiptService.cancelReturnReceipt,
        ).toHaveBeenCalledTimes(2);
        expect(
          mockRemissionReturnReceiptService.cancelReturnReceipt,
        ).toHaveBeenNthCalledWith(1, {
          returnId: 123,
          receiptId: 101,
        });
        expect(
          mockRemissionReturnReceiptService.cancelReturnReceipt,
        ).toHaveBeenNthCalledWith(2, {
          returnId: 123,
          receiptId: 102,
        });

        // Should cancel the return
        expect(
          mockRemissionReturnReceiptService.cancelReturn,
        ).toHaveBeenCalledWith({
          returnId: 123,
        });

        // Should emit reload data
        expect(reloadDataSpy).toHaveBeenCalled();

        isCurrentRemissionSpy.mockRestore();
      });

      it('should clear state when deleting current remission', async () => {
        const mockReturn = createMockReturn({
          id: 123,
          receipts: [createMockReceipt(101)],
        });

        // Set up the component to think this is the current remission
        const isCurrentRemissionSpy = vi
          .spyOn(component, 'isCurrentRemission')
          .mockReturnValue(true);

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        await component.onDelete();

        expect(mockRemissionStore.clearState).toHaveBeenCalled();

        isCurrentRemissionSpy.mockRestore();
      });

      it('should handle errors during deletion', async () => {
        const mockReturn = createMockReturn();
        const consoleErrorSpy = vi
          .spyOn(console, 'error')
          .mockImplementation(() => {});
        const error = new Error('Network error');

        // Mock the isCurrentRemission method to avoid the signal error
        const isCurrentRemissionSpy = vi
          .spyOn(component, 'isCurrentRemission')
          .mockReturnValue(false);

        mockRemissionReturnReceiptService.cancelReturnReceipt.mockRejectedValue(
          error,
        );

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        const reloadDataSpy = vi.fn();
        component.reloadData.subscribe(reloadDataSpy);

        await component.onDelete();

        expect(consoleErrorSpy).toHaveBeenCalledWith(
          'Error deleting return receipt:',
          error,
        );
        expect(reloadDataSpy).toHaveBeenCalled(); // Should still emit even on error

        consoleErrorSpy.mockRestore();
        isCurrentRemissionSpy.mockRestore();
      });
    });

    describe('onContinueRemission', () => {
      it('should start remission and navigate when no remission is started', async () => {
        const mockReturn = createMockReturn({
          id: 123,
          receipts: [createMockReceipt(101)],
        });
        mockRemissionStore.remissionStarted.mockReturnValue(false);

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        await component.onContinueRemission();

        expect(mockRemissionStore.startRemission).toHaveBeenCalledWith({
          returnId: 123,
          receiptId: 101,
        });
        expect(mockRouter.navigate).toHaveBeenCalledWith([
          '/',
          'test-tab-id',
          'remission',
        ]);
      });

      it('should not start remission when no first receipt ID exists', async () => {
        const mockReturn = createMockReturn({ receipts: [] });
        mockRemissionStore.remissionStarted.mockReturnValue(false);

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        await component.onContinueRemission();

        expect(mockRemissionStore.startRemission).not.toHaveBeenCalled();
        expect(mockRouter.navigate).toHaveBeenCalledWith([
          '/',
          'test-tab-id',
          'remission',
        ]);
      });

      it('should show confirmation dialog when remission is started but not current', async () => {
        const mockReturn = createMockReturn({
          id: 123,
          receipts: [createMockReceipt(101)],
        });
        mockRemissionStore.remissionStarted.mockReturnValue(true);

        // Mock isCurrentRemission to return false
        const isCurrentRemissionSpy = vi
          .spyOn(component, 'isCurrentRemission')
          .mockReturnValue(false);

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        // Mock dialog confirmation result
        const dialogResultSubject = new Subject();
        mockDialogRef.closed = dialogResultSubject.asObservable();

        const continuePromise = component.onContinueRemission();

        // Simulate user confirming the dialog
        dialogResultSubject.next({ confirmed: true });
        dialogResultSubject.complete();

        await continuePromise;

        expect(mockInjectConfirmationDialog).toHaveBeenCalledWith({
          title: 'Bereits geöffneter Warenbegleitschein',
          width: '30rem',
          data: {
            message:
              'Möchten Sie wirklich einen neuen öffnen, oder möchten Sie zuerst den aktuellen abschließen?',
            closeText: 'Aktuellen bearbeiten',
            confirmText: 'Neuen öffnen',
          },
        });

        expect(mockRemissionStore.clearState).toHaveBeenCalled();
        expect(mockRemissionStore.startRemission).toHaveBeenCalledWith({
          returnId: 123,
          receiptId: 101,
        });

        isCurrentRemissionSpy.mockRestore();
      });

      it('should not start new remission when dialog is cancelled', async () => {
        const mockReturn = createMockReturn({
          id: 123,
          receipts: [createMockReceipt(101)],
        });
        mockRemissionStore.remissionStarted.mockReturnValue(true);

        // Mock isCurrentRemission to return false
        const isCurrentRemissionSpy = vi
          .spyOn(component, 'isCurrentRemission')
          .mockReturnValue(false);

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        // Mock dialog cancellation result
        const dialogResultSubject = new Subject();
        mockDialogRef.closed = dialogResultSubject.asObservable();

        const continuePromise = component.onContinueRemission();

        // Simulate user cancelling the dialog
        dialogResultSubject.next({ confirmed: false });
        dialogResultSubject.complete();

        await continuePromise;

        expect(mockRemissionStore.clearState).not.toHaveBeenCalled();
        expect(mockRemissionStore.startRemission).not.toHaveBeenCalled();
        expect(mockRouter.navigate).toHaveBeenCalledWith([
          '/',
          'test-tab-id',
          'remission',
        ]);

        isCurrentRemissionSpy.mockRestore();
      });

      it('should navigate directly when current remission matches', async () => {
        const mockReturn = createMockReturn({
          id: 123,
          receipts: [createMockReceipt(101)],
        });
        mockRemissionStore.remissionStarted.mockReturnValue(true);

        // Mock isCurrentRemission to return true
        const isCurrentRemissionSpy = vi
          .spyOn(component, 'isCurrentRemission')
          .mockReturnValue(true);

        fixture.componentRef.setInput('remissionReturn', mockReturn);
        fixture.detectChanges();

        await component.onContinueRemission();

        expect(mockInjectConfirmationDialog).not.toHaveBeenCalled();
        expect(mockRouter.navigate).toHaveBeenCalledWith([
          '/',
          'test-tab-id',
          'remission',
        ]);

        isCurrentRemissionSpy.mockRestore();
      });
    });

    describe('navigateToRemissionList', () => {
      it('should navigate to correct remission route', async () => {
        await component.navigateToRemissionList();

        expect(mockRouter.navigate).toHaveBeenCalledWith([
          '/',
          'test-tab-id',
          'remission',
        ]);
      });
    });
  });

  describe('Component reactivity', () => {
    it('should update computed signals when input changes', () => {
      const initialReturn = createMockReturn({ id: 1 });
      const updatedReturn = createMockReturn({ id: 2 });

      // Mock return values for initial state
      vi.mocked(getReceiptItemQuantityFromReturn).mockReturnValue(3);

      fixture.componentRef.setInput('remissionReturn', initialReturn);
      fixture.detectChanges();

      expect(component.returnId()).toBe(1);
      expect(component.itemQuantity()).toBe(3);

      // Mock return values for updated state
      vi.mocked(getReceiptItemQuantityFromReturn).mockReturnValue(7);

      fixture.componentRef.setInput('remissionReturn', updatedReturn);
      fixture.detectChanges();

      expect(component.returnId()).toBe(2);
      expect(component.itemQuantity()).toBe(7);
    });
  });

  describe('User interactions', () => {
    beforeEach(() => {
      vi.mocked(getReceiptItemQuantityFromReturn).mockReturnValue(0);
    });

    it('should call onDelete when delete button is clicked', async () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      const onDeleteSpy = vi.spyOn(component, 'onDelete').mockResolvedValue();

      const deleteButton = fixture.nativeElement.querySelector(
        'button[color="secondary"]',
      );
      deleteButton.click();

      expect(onDeleteSpy).toHaveBeenCalled();
    });

    it('should call onContinueRemission when continue button is clicked', async () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      const onContinueRemissionSpy = vi
        .spyOn(component, 'onContinueRemission')
        .mockResolvedValue();

      const continueButton = fixture.nativeElement.querySelector(
        'button[color="primary"]',
      );
      continueButton.click();

      expect(onContinueRemissionSpy).toHaveBeenCalled();
    });

    it('should prevent event propagation and default behavior on delete click', () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      vi.spyOn(component, 'onDelete').mockResolvedValue();

      const deleteButton = fixture.nativeElement.querySelector(
        'button[color="secondary"]',
      );

      const event = new MouseEvent('click', {
        bubbles: true,
        cancelable: true,
      });
      const stopPropagationSpy = vi.spyOn(event, 'stopPropagation');
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      deleteButton.dispatchEvent(event);

      expect(stopPropagationSpy).toHaveBeenCalled();
      expect(preventDefaultSpy).toHaveBeenCalled();
    });

    it('should prevent event propagation and default behavior on continue click', () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      vi.spyOn(component, 'onContinueRemission').mockResolvedValue();

      const continueButton = fixture.nativeElement.querySelector(
        'button[color="primary"]',
      );

      const event = new MouseEvent('click', {
        bubbles: true,
        cancelable: true,
      });
      const stopPropagationSpy = vi.spyOn(event, 'stopPropagation');
      const preventDefaultSpy = vi.spyOn(event, 'preventDefault');

      continueButton.dispatchEvent(event);

      expect(stopPropagationSpy).toHaveBeenCalled();
      expect(preventDefaultSpy).toHaveBeenCalled();
    });
  });

  describe('Output events', () => {
    it('should emit reloadData on successful deletion', async () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      const reloadDataSpy = vi.fn();
      component.reloadData.subscribe(reloadDataSpy);

      await component.onDelete();

      expect(reloadDataSpy).toHaveBeenCalled();
    });

    it('should emit reloadData even on deletion error', async () => {
      const mockReturn = createMockReturn();
      fixture.componentRef.setInput('remissionReturn', mockReturn);
      fixture.detectChanges();

      const consoleErrorSpy = vi
        .spyOn(console, 'error')
        .mockImplementation(() => {});

      mockRemissionReturnReceiptService.cancelReturnReceipt.mockRejectedValue(
        new Error('Network error'),
      );

      const reloadDataSpy = vi.fn();
      component.reloadData.subscribe(reloadDataSpy);

      await component.onDelete();

      expect(reloadDataSpy).toHaveBeenCalled();
      consoleErrorSpy.mockRestore();
    });
  });
});
