/*
 * Copyright (C) 2022 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.
 */
package de.ozgcloud.user;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.ClientMappingsRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.mapstruct.factory.Mappers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;

import de.ozgcloud.user.keycloak.KeycloakApiProperties;

class UserResourceMapperTest {

	static final String ORGANISATIONS_EINHEIT_ID_KEY = "organisationseinheitId";
	static final String ORGANISATIONS_EINHEIT_ID_1 = "0815";
	static final String ORGANISATIONS_EINHEIT_ID_2 = "4711";
	static final String GROUP_1_PATH = "/group1";
	static final String GROUP_2_PATH = "/group2";
	static final String GROUP_3_PATH = "/group3";

	@Spy
	@InjectMocks
	UserResourceMapper mapper = Mappers.getMapper(UserResourceMapper.class);

	@Mock
	KeycloakApiProperties properties;

	@Mock
	RealmResource realm;

	final Set<String> organisationsEinheitIds = Set.of(ORGANISATIONS_EINHEIT_ID_1);

	@DisplayName("To kop user")
	@Nested
	class TestToKopUser {

		@BeforeEach
		void init() {
			when(properties.ldapIdKey()).thenReturn("LDAP_ID");
			when(properties.client()).thenReturn("alfa");
			doReturn(organisationsEinheitIds).when(mapper).mapOrganisationsEinheitIds(any());
		}

		@Test
		void shouldMapToUser() {
			var user = toKopUser();

			assertThat(user).isNotNull();
		}

		@Test
		void shouldMapEmail() {
			var user = toKopUser();

			assertThat(user.getEmail()).isEqualTo(UserRepresentationTestFactory.EMAIL);
		}

		@DisplayName("externalId")
		@Nested
		class TestMapExternalId {

			@Test
			void shouldMap() {
				var user = toKopUser();

				assertThat(user.getExternalId()).isEqualTo(UserRepresentationTestFactory.EXTERNAL_ID);
			}

			@Test
			void shouldMapFallbackOnEmptyAttributes() {
				var user = toKopUser(UserResourceTestFactory.createWithAttributes(Collections.emptyMap()));

				assertThat(user.getExternalId()).isEqualTo(UserRepresentationTestFactory.EXTERNAL_ID_FALLBACK);
			}
		}

		@Test
		void shouldMapFirstName() {
			var user = toKopUser();

			assertThat(user.getFirstName()).isEqualTo(UserRepresentationTestFactory.FIRST_NAME);
		}

		@Test
		void shouldMapLastName() {
			var user = toKopUser();

			assertThat(user.getLastName()).isEqualTo(UserRepresentationTestFactory.LAST_NAME);
		}

		@Test
		void shouldMapUserName() {
			var user = toKopUser();

			assertThat(user.getUsername()).isEqualTo(UserRepresentationTestFactory.USER_NAME);
		}

		@Test
		void shouldMapOrganisationsEinheitIds() {
			var result = toKopUser();

			assertThat(result.getOrganisationsEinheitIds()).hasSameElementsAs(organisationsEinheitIds);
		}

		@Test
		void shouldMapRoles() {
			var user = toKopUser();

			assertThat(user.getRoles()).isNotEmpty().contains(UserRepresentationTestFactory.ROLE_NAME);
		}

		@Test
		void shouldMapKeycloakUserId() {
			var user = toKopUser();

			assertThat(user.getKeycloakUserId()).isEqualTo(UserRepresentationTestFactory.EXTERNAL_ID_FALLBACK);
		}

		@Test
		void shouldMapFullName() {
			var user = toKopUser();

			assertThat(user.getFullName()).isEqualTo(UserRepresentationTestFactory.FIRST_NAME + " " + UserRepresentationTestFactory.LAST_NAME);
		}

		@Test
		void shouldMapFullNameReversed() {
			var user = toKopUser();

			assertThat(user.getFullNameReversed()).isEqualTo(UserRepresentationTestFactory.LAST_NAME + " " + UserRepresentationTestFactory.FIRST_NAME);
		}

		private User toKopUser() {
			return toKopUser(UserResourceTestFactory.create());
		}

		private User toKopUser(UserResource userResource) {
			return mapper.toKopUser(userResource);
		}
	}

	@DisplayName("Map organisations einheit ids")
	@Nested
	class TestMapOrganisationsEinheitIds {
		static final String ORGANISATIONS_EINHEIT_ID_3 = "6287";
		private final GroupRepresentation group1 = GroupRepresentationTestFactory.createByPathAndOrganisationEinheitIds(
				GROUP_1_PATH, ORGANISATIONS_EINHEIT_ID_1, ORGANISATIONS_EINHEIT_ID_3);
		private final GroupRepresentation group2 = GroupRepresentationTestFactory.createByPathAndOrganisationEinheitIds(
				GROUP_2_PATH, ORGANISATIONS_EINHEIT_ID_2);
		private final GroupRepresentation group3 = GroupRepresentationTestFactory.createWithPathAndAttributes(
				GROUP_3_PATH, Map.of());

		@BeforeEach
		void beforeEach() {
			when(properties.organisationsEinheitIdKey()).thenReturn(ORGANISATIONS_EINHEIT_ID_KEY);
		}

		@DisplayName("should map no organisations einheit id if group has none")
		@Test
		void shouldMapNoOrganisationsEinheitIdIfGroupHasNone() {
			when(realm.getGroupByPath(GROUP_3_PATH)).thenReturn(group3);
			var userResource = UserResourceTestFactory.createWithGroups(group3);

			var result = mapper.mapOrganisationsEinheitIds(userResource);

			assertThat(result).isEmpty();
		}

		@Test
		void shouldMapOrganisationsEinheitIdsFromSingleGroup() {
			var userResource = UserResourceTestFactory.createWithGroups(group1);
			when(realm.getGroupByPath(GROUP_1_PATH)).thenReturn(group1);

			var result = mapper.mapOrganisationsEinheitIds(userResource);

			assertThat(result).containsExactlyInAnyOrder(ORGANISATIONS_EINHEIT_ID_1, ORGANISATIONS_EINHEIT_ID_3);
		}

		@Test
		void shouldMapOrganisationsEinheitIdsFromGroups() {
			var userResource = UserResourceTestFactory.createWithGroups(group1, group2, group3);
			when(realm.getGroupByPath(GROUP_1_PATH)).thenReturn(group1);
			when(realm.getGroupByPath(GROUP_2_PATH)).thenReturn(group2);
			when(realm.getGroupByPath(GROUP_3_PATH)).thenReturn(group3);

			var result = mapper.mapOrganisationsEinheitIds(userResource);

			assertThat(result).containsExactlyInAnyOrder(
					ORGANISATIONS_EINHEIT_ID_1,
					ORGANISATIONS_EINHEIT_ID_2,
					ORGANISATIONS_EINHEIT_ID_3
			);
		}

		@Test
		void shouldMapOrganisationsEinheitIdsFromUser() {
			var attributes = Map.of(ORGANISATIONS_EINHEIT_ID_KEY, List.of(ORGANISATIONS_EINHEIT_ID_1, ORGANISATIONS_EINHEIT_ID_2));
			var userResource = UserResourceTestFactory.createWithAttributes(attributes);

			var result = mapper.mapOrganisationsEinheitIds(userResource);

			assertThat(result).containsExactlyInAnyOrder(ORGANISATIONS_EINHEIT_ID_1, ORGANISATIONS_EINHEIT_ID_2);
		}

		@Test
		void shouldReturnEmptyIfNoOrganisationsEinheitIds() {
			var userResource = UserResourceTestFactory.create();

			var result = mapper.mapOrganisationsEinheitIds(userResource);

			assertThat(result).isEmpty();
		}
	}

	@DisplayName("Get client roles")
	@Nested
	class TestGetClientRoles {

		@Mock
		UserResource userResource;

		@Mock
		RoleMappingResource roleMappingResource;
		@Mock
		MappingsRepresentation mappingsRepresentation;
		@Mock
		Map<String, ClientMappingsRepresentation> clientMappingsRepresentation;
		@Mock
		ClientMappingsRepresentation clientMappingRepresentation;

		@BeforeEach
		void init() {
			when(userResource.roles()).thenReturn(roleMappingResource);
			when(roleMappingResource.getAll()).thenReturn(mappingsRepresentation);
		}

		@DisplayName("on existing roles")
		@Nested
		class TestOnAssignedRoles {

			@BeforeEach
			void init() {
				when(properties.client()).thenReturn(UserRepresentationTestFactory.CLIENT_KEY);

				when(mappingsRepresentation.getClientMappings()).thenReturn(clientMappingsRepresentation);
				when(clientMappingsRepresentation.containsKey(UserRepresentationTestFactory.CLIENT_KEY)).thenReturn(true);
				when(clientMappingsRepresentation.get(UserRepresentationTestFactory.CLIENT_KEY)).thenReturn(clientMappingRepresentation);
				when(clientMappingRepresentation.getMappings()).thenReturn(List.of(createRoleRepresentation()));
			}

			private RoleRepresentation createRoleRepresentation() {
				var roleRepresentation = new RoleRepresentation();
				roleRepresentation.setName(UserRepresentationTestFactory.ROLE_NAME);
				return roleRepresentation;
			}

			@Test
			void shouldReturnRolesIfExists() {
				var roles = mapper.mapRoles(userResource);

				assertThat(roles).isNotEmpty();
				assertThat(roles.get(0)).isEqualTo(UserRepresentationTestFactory.ROLE_NAME);
			}
		}

		@Nested
		class TestOnNonExistingClient {

			@BeforeEach
			void init() {
				when(properties.client()).thenReturn(UserRepresentationTestFactory.CLIENT_KEY);

				when(mappingsRepresentation.getClientMappings()).thenReturn(Collections.emptyMap());
			}

			@Test
			void shouldReturnEmptyListIfNoRolesAttached() {
				var roles = mapper.mapRoles(userResource);

				assertThat(roles).isEmpty();
			}
		}

		@Nested
		class TestNullClientMappings {

			@BeforeEach
			void init() {
				when(mappingsRepresentation.getClientMappings()).thenReturn(null);
			}

			@Test
			void shouldReturnEmptyListIfNoRolesAttached() {
				var roles = mapper.mapRoles(userResource);

				assertThat(roles).isEmpty();
			}
		}
	}
}