/*
 * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
 * Ministerpräsidenten des Landes Schleswig-Holstein
 * Staatskanzlei
 * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
 *
 * Lizenziert unter der EUPL, Version 1.2 oder - sobald
 * diese von der Europäischen Kommission genehmigt wurden -
 * Folgeversionen der EUPL ("Lizenz");
 * Sie dürfen dieses Werk ausschließlich gemäß
 * dieser Lizenz nutzen.
 * Eine Kopie der Lizenz finden Sie hier:
 *
 * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
 *
 * Sofern nicht durch anwendbare Rechtsvorschriften
 * gefordert oder in schriftlicher Form vereinbart, wird
 * die unter der Lizenz verbreitete Software "so wie sie
 * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
 * ausdrücklich oder stillschweigend - verbreitet.
 * Die sprachspezifischen Genehmigungen und Beschränkungen
 * unter der Lizenz sind dem Lizenztext zu entnehmen.
 */
import { ApiRootLinkRel, ApiRootResource, ApiRootService } from '@alfa-client/api-root-shared';
import { BinaryFileListResource } from '@alfa-client/binary-file-shared';
import { CommandOrder, CommandResource, CommandService, CreateCommandProps } from '@alfa-client/command-shared';
import { Environment } from '@alfa-client/environment-shared';
import { NavigationService } from '@alfa-client/navigation-shared';
import { EMPTY_STRING, StateResource, createEmptyStateResource, createStateResource } from '@alfa-client/tech-shared';
import { Mock, mock, useFromMock } from '@alfa-client/test-utils';
import { HttpErrorResponse } from '@angular/common/http';
import { ResourceUri, getUrl } from '@ngxp/rest';
import { cold, hot } from 'jest-marbles';
import { createApiRootResource } from 'libs/api-root-shared/test/api-root';
import { createBinaryFileListResource } from 'libs/binary-file-shared/test/binary-file';
import { CommandLinkRel } from 'libs/command-shared/src/lib/command.linkrel';
import { createCommandResource } from 'libs/command-shared/test/command';
import { createVorgangWithEingangResource } from 'libs/vorgang-shared/test/vorgang';
import { Observable, of } from 'rxjs';
import { VorgangFacade } from './+state/vorgang.facade';
import { VorgangWithEingangLinkRel } from './vorgang.linkrel';
import { AdditionalActions, VorgangWithEingangResource } from './vorgang.model';
import { VorgangService } from './vorgang.service';

import { faker } from '@faker-js/faker';

import * as VorgangNavigationUtil from './vorgang-navigation.util';

describe('VorgangService', () => {
  let service: VorgangService;
  let navigationService: Mock<NavigationService>;
  let facade: Mock<VorgangFacade>;
  let apiRootService: Mock<ApiRootService>;
  let commandService: Mock<CommandService>;
  let envConfig: Environment = <any>{ processorNames: ['dummyProcessorName'] };

  beforeEach(() => {
    navigationService = { ...mock(NavigationService) };
    facade = mock(VorgangFacade);
    apiRootService = mock(ApiRootService);
    commandService = mock(CommandService);
    navigationService.urlChanged = jest.fn();
    navigationService.urlChanged.mockReturnValue(of({}));

    service = new VorgangService(
      useFromMock(navigationService),
      useFromMock(facade),
      useFromMock(apiRootService),
      useFromMock(commandService),
      envConfig,
    );
  });

  const vorgangWithEingang: VorgangWithEingangResource = createVorgangWithEingangResource();
  const vorgangWithEingangStateResource: StateResource<VorgangWithEingangResource> = createStateResource(vorgangWithEingang);

  describe('getVorgangWithEingang', () => {
    const apiRootStateResource: StateResource<ApiRootResource> = createStateResource(createApiRootResource());

    beforeEach(() => {
      apiRootService.getApiRoot.mockReturnValue(of(apiRootStateResource));
    });

    it('should get vorgangWithEingang', () => {
      service.getVorgangWithEingang();

      expect(facade.getVorgangWithEingang).toBeCalled();
    });

    it('should get getApiRoot', () => {
      service.getVorgangWithEingang();

      expect(apiRootService.getApiRoot).toBeCalled();
    });

    describe('initial', () => {
      beforeEach(() => {
        apiRootService.getApiRoot.mockReturnValue(hot('-a', { a: apiRootStateResource }));
        facade.getVorgangWithEingang.mockReturnValue(hot('-a', { a: vorgangWithEingangStateResource }));
      });

      it('should return value', () => {
        const vorgangList = service.getVorgangWithEingang();

        expect(vorgangList).toBeObservable(cold('ab', { a: createEmptyStateResource(true), b: vorgangWithEingangStateResource }));
      });
    });

    describe('loadVorgangWithEingang', () => {
      it('should be called if loading required', () => {
        facade.getVorgangWithEingang.mockReturnValue(of(createEmptyStateResource()));

        service.getVorgangWithEingang().subscribe();

        expect(facade.loadVorgangWithEingang).toHaveBeenCalled();
      });

      it('should NOT be called if already loaded', () => {
        facade.getVorgangWithEingang.mockReturnValue(of(vorgangWithEingangStateResource));

        service.getVorgangWithEingang().subscribe();

        expect(facade.loadVorgangWithEingang).not.toHaveBeenCalled();
      });
    });
  });

  describe('getVorgangWithEingangUri', () => {
    it('should call facade', () => {
      service.getVorgangWithEingangUri();

      expect(navigationService.getDecodedParam).toHaveBeenCalledWith(VorgangService.VORGANG_WITH_EINGANG_URL);
    });
  });

  describe('getAttachments', () => {
    const binaryFile: BinaryFileListResource = createBinaryFileListResource();
    const binaryFileStateResource: StateResource<BinaryFileListResource> = createStateResource(binaryFile);

    beforeEach(() => {
      facade.getAttachmentList.mockReturnValue(of(binaryFileStateResource));
      facade.getVorgangWithEingang.mockReturnValue(of(vorgangWithEingangStateResource));
    });

    it('should get AttachmentList by facade', () => {
      service.getAttachments();

      expect(facade.getAttachmentList).toHaveBeenCalled();
    });

    it('should get vorgangWithEingang by facade', () => {
      service.getAttachments();

      expect(facade.getVorgangWithEingang).toHaveBeenCalled();
    });

    describe('initial', () => {
      beforeEach(() => {
        facade.getAttachmentList.mockReturnValue(hot('-a', { a: binaryFileStateResource }));
        facade.getVorgangWithEingang.mockReturnValue(of(vorgangWithEingangStateResource));
      });

      it('should return value', () => {
        const vorgangList = service.getAttachments();

        expect(vorgangList).toBeObservable(cold('ab', { a: createEmptyStateResource(true), b: binaryFileStateResource }));
      });
    });

    describe('loadAttachments', () => {
      it('should be called if loading required', () => {
        facade.getAttachmentList.mockReturnValue(of(createEmptyStateResource()));

        service.getAttachments().subscribe();

        expect(facade.loadAttachmentList).toHaveBeenCalledWith(vorgangWithEingang);
      });

      it('should NOT be called if already loaded', () => {
        service.getAttachments().subscribe();

        expect(facade.loadAttachmentList).not.toHaveBeenCalled();
      });

      it('should NOT be called if vorgangWithEingang is not loaded', () => {
        facade.getVorgangWithEingang.mockReturnValue(of(createEmptyStateResource()));

        service.getAttachments().subscribe();

        expect(facade.loadAttachmentList).not.toHaveBeenCalled();
      });
    });
  });

  describe('getRepresentations', () => {
    const binaryFile: BinaryFileListResource = createBinaryFileListResource();
    const binaryFileStateResource: StateResource<BinaryFileListResource> = createStateResource(binaryFile);

    beforeEach(() => {
      facade.getRepresentationList.mockReturnValue(of(binaryFileStateResource));
      facade.getVorgangWithEingang.mockReturnValue(of(vorgangWithEingangStateResource));
    });

    it('should get RepresentationList by facade', () => {
      service.getRepresentations();

      expect(facade.getRepresentationList).toHaveBeenCalled();
    });

    it('should get vorgangWithEingang by facade', () => {
      service.getRepresentations();

      expect(facade.getVorgangWithEingang).toHaveBeenCalled();
    });

    describe('initial', () => {
      beforeEach(() => {
        facade.getRepresentationList.mockReturnValue(hot('-a', { a: binaryFileStateResource }));
        facade.getVorgangWithEingang.mockReturnValue(of(vorgangWithEingangStateResource));
      });

      it('should return value', () => {
        const vorgangList = service.getRepresentations();

        expect(vorgangList).toBeObservable(cold('ab', { a: createEmptyStateResource(true), b: binaryFileStateResource }));
      });
    });

    describe('loadRepresentationList', () => {
      it('should be called if loading required', () => {
        facade.getRepresentationList.mockReturnValue(of(createEmptyStateResource()));

        service.getRepresentations().subscribe();

        expect(facade.loadRepresentationList).toHaveBeenCalledWith(vorgangWithEingang);
      });

      it('should NOT be called if already loaded', () => {
        service.getRepresentations().subscribe();

        expect(facade.loadRepresentationList).not.toHaveBeenCalled();
      });

      it('should NOT be called if vorgangWithEingang is not loaded', () => {
        facade.getVorgangWithEingang.mockReturnValue(of(createEmptyStateResource()));

        service.getRepresentations().subscribe();

        expect(facade.loadRepresentationList).not.toHaveBeenCalled();
      });
    });
  });

  describe('clearVorgang', () => {
    it('should call facade', () => {
      service.clearVorgang();

      expect(facade.clearVorgangWithEingang).toHaveBeenCalled();
    });
  });

  describe('setPendingForwardSingleCommandLoading', () => {
    it('should call facade', () => {
      service.setPendingForwardSingleCommandLoading();

      expect(facade.setForwardSingleCommandLoading).toHaveBeenCalled();
    });
  });

  describe('getForwardPendingCommand', () => {
    beforeEach(() => {
      facade.getVorgangWithEingang.mockReturnValue(of(vorgangWithEingangStateResource));
      facade.getForwardPendingCommand.mockReturnValue(of(createEmptyStateResource()));
    });

    it('should call facade', () => {
      service.getPendingForwardCommand();

      expect(facade.getForwardPendingCommand).toHaveBeenCalled();
    });
  });

  describe('setPendingForwardSingleCommand', () => {
    const commandStateResource: StateResource<CommandResource> = createStateResource(createCommandResource());

    it('should call facade', () => {
      service.setPendingForwardSingleCommand(commandStateResource);

      expect(facade.setForwardSingleCommand).toHaveBeenCalledWith(commandStateResource);
    });
  });

  describe('setPendingForwardSingleCommandLoading', () => {
    it('should call facade', () => {
      service.setPendingForwardSingleCommandLoading();

      expect(facade.setForwardSingleCommandLoading).toHaveBeenCalled();
    });
  });

  describe('getPendingSendPostfachMailCommand', () => {
    beforeEach(() => {
      facade.getVorgangWithEingang.mockReturnValue(of(vorgangWithEingangStateResource));
      facade.getSendPostfachNachrichtPendingCommand.mockReturnValue(of(createEmptyStateResource()));
    });

    it('should call facade', () => {
      service.getPendingSendPostfachMailCommand();

      expect(facade.getSendPostfachNachrichtPendingCommand).toHaveBeenCalled();
    });
  });

  describe('setPendingForwardSingleCommand', () => {
    const commandStateResource: StateResource<CommandResource> = createStateResource(createCommandResource());

    it('should call facade', () => {
      service.setPendingForwardSingleCommand(commandStateResource);

      expect(facade.setForwardSingleCommand).toHaveBeenCalledWith(commandStateResource);
    });
  });

  describe('reloadCurrentVorgang', () => {
    beforeEach(() => {
      navigationService.getDecodedParam.mockReturnValue('decodedParameterUrlForTest');
    });

    it('should call navigation service', () => {
      service.reloadCurrentVorgang();

      expect(navigationService.getDecodedParam).toHaveBeenCalledWith(VorgangService.VORGANG_WITH_EINGANG_URL);
    });

    it('should call loadVorgangWithEingang', () => {
      service.reloadCurrentVorgang();

      expect(facade.loadVorgangWithEingang).toHaveBeenCalled();
    });
  });

  describe('reloadCurrentVorgangWithAddtionalActions', () => {
    const additionalActions: AdditionalActions = {
      additionalSuccessAction: () => null,
      additionalFailureAction: (error: HttpErrorResponse) => null,
    };

    beforeEach(() => {
      navigationService.getDecodedParam.mockReturnValue('decodedParameterUrlForTest');
    });

    it('should call navigation service', () => {
      service.reloadCurrentVorgangWithAddtionalActions(additionalActions);

      expect(navigationService.getDecodedParam).toHaveBeenCalledWith(VorgangService.VORGANG_WITH_EINGANG_URL);
    });

    it('should call loadVorgangWithEingangWithAdditionalActions', () => {
      const vorgangUri: string = faker.internet.url();
      service.getVorgangWithEingangUri = jest.fn().mockReturnValue(vorgangUri);

      service.reloadCurrentVorgangWithAddtionalActions(additionalActions);

      expect(facade.loadVorgangWithEingangWithAdditionalActions).toHaveBeenCalledWith(vorgangUri, additionalActions);
    });
  });

  describe('getAssignUserCommand', () => {
    it('should call facade', () => {
      service.getAssignUserCommand();

      expect(facade.getAssignUserCommand).toHaveBeenCalled();
    });
  });

  describe('assignUser', () => {
    const vorgangWithEingang: VorgangWithEingangResource = createVorgangWithEingangResource();
    const assignedTo: ResourceUri = faker.internet.url();

    it.skip('should call facade', () => {
      // facade.assignUser = jest.fn();
      // service.assignUser(vorgangWithEingang, assignedTo);
      // expect(facade.assignUser).toHaveBeenCalledWith(vorgangWithEingang, <CreateCommand>{ order: CommandOrder.ASSIGN_USER, body: { assignedTo }});
    });
  });

  describe('reloadVorgang', () => {
    const command: CommandResource = createCommandResource([CommandLinkRel.EFFECTED_RESOURCE]);

    it('should call facade', () => {
      service.reloadVorgang(command);

      expect(facade.loadVorgangWithEingang).toHaveBeenCalledWith(getUrl(command, CommandLinkRel.EFFECTED_RESOURCE));
    });
  });

  describe('getBackButtonUrl', () => {
    it('should call facade', () => {
      service.getBackButtonUrl();

      expect(facade.getBackButtonUrl).toHaveBeenCalled();
    });
  });

  describe('canNavigateToPathSegements', () => {
    const apiRootStateResource: StateResource<ApiRootResource> = createStateResource(
      createApiRootResource([ApiRootLinkRel.ALLE_VORGAENGE_ABGESCHLOSSEN]),
    );

    beforeEach(() => {
      apiRootService.getApiRoot.mockReturnValue(hot('a', { a: apiRootStateResource }));
    });

    it('should apiRoot', () => {
      service.canNavigateToPathSegements(EMPTY_STRING);

      expect(apiRootService.getApiRoot).toHaveBeenCalled();
    });

    it('should call buildLinkRelFromPathSegments', () => {
      const spy = jest.spyOn(VorgangNavigationUtil, 'buildLinkRelFromPathSegments');

      service.canNavigateToPathSegements(EMPTY_STRING);

      expect(spy).toHaveBeenCalled();
    });

    it('should return false as Observable if linkRel not available', () => {
      const path: string = '/alle/angenommen';

      const result: Observable<boolean> = service.canNavigateToPathSegements(path);

      expect(result).toBeObservable(cold('a', { a: false }));
    });

    it('should return true as Observable if linkRel available', () => {
      const path: string = '/alle/abgeschlossen';

      const result: Observable<boolean> = service.canNavigateToPathSegements(path);

      expect(result).toBeObservable(cold('a', { a: true }));
    });
  });

  describe('getVorgangExport', () => {
    it('should call facade', () => {
      service.getVorgangExport();

      expect(facade.getVorgangExport).toHaveBeenCalled();
    });
  });

  describe('export', () => {
    it('should call facade', () => {
      service.export(vorgangWithEingang);

      expect(facade.export).toHaveBeenCalledWith(vorgangWithEingang);
    });
  });

  describe('archive', () => {
    it('should call commandService createCommandByProps', () => {
      service.archive(vorgangWithEingang);

      const expectedProps = {
        resource: vorgangWithEingang,
        linkRel: VorgangWithEingangLinkRel.ARCHIVE,
        command: { order: CommandOrder.ARCHIVE_VORGANG, body: {} },
        snackBarMessage: 'Vorgang in Archivierung.',
      };
      expect(commandService.createCommandByProps).toHaveBeenCalledWith(expectedProps);
    });
  });

  describe('processVorgang', () => {
    const vorgangWithEingang: VorgangWithEingangResource = createVorgangWithEingangResource();
    const command: CommandResource = createCommandResource();
    const commandStateResource: StateResource<CommandResource> = createStateResource(command);

    beforeEach(() => {
      commandService.createCommandByProps.mockReturnValue(hot('a', { a: commandStateResource }));
    });

    it('should call commandService', () => {
      service.processVorgang(vorgangWithEingang);

      const expectedProps: CreateCommandProps = {
        resource: vorgangWithEingang,
        linkRel: VorgangWithEingangLinkRel.PROCESS_VORGANG,
        command: {
          order: CommandOrder.PROCESS_VORGANG,
          body: { processorNames: ['dummyProcessorName'] },
        },
        snackBarMessage: 'Vorgang vorprüfen erfolgreich.',
      };
      expect(commandService.createCommandByProps).toHaveBeenCalledWith(expectedProps);
    });

    it('should return command', () => {
      const processVorgangCommand: Observable<StateResource<CommandResource>> = service.processVorgang(vorgangWithEingang);

      expect(processVorgangCommand).toBeObservable(cold('a', { a: commandStateResource }));
    });
  });

  describe('setAktenzeichen', () => {
    it('should call command service', () => {
      const aktenzeichen = '12345';

      service.setAktenzeichen(vorgangWithEingang, aktenzeichen);

      expect(commandService.createCommand).toHaveBeenCalledWith(vorgangWithEingang, VorgangWithEingangLinkRel.SET_AKTENZEICHEN, {
        order: CommandOrder.SET_AKTENZEICHEN,
        body: { aktenzeichen },
      });
    });
  });
});