/*
 * 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 java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.ClientMappingsRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;

import de.ozgcloud.user.keycloak.KeycloakApiProperties;

@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN)
public abstract class UserResourceMapper {

	@Inject
	KeycloakApiProperties properties;

	@Inject
	RealmResource realm;

	@Mapping(target = "createdAt", expression = "java(mapCreatedAt(userRes))")
	@Mapping(target = "email", expression = "java(mapEmail(userRes))")
	@Mapping(target = "firstName", expression = "java(mapFirstName(userRes))")
	@Mapping(target = "lastName", expression = "java(mapLastName(userRes))")
	@Mapping(target = "fullName", expression = "java(mapFullName(userRes))")
	@Mapping(target = "fullNameReversed", expression = "java(mapFullNameReversed(userRes))")
	@Mapping(target = "username", expression = "java(mapUsername(userRes))")
	@Mapping(target = "externalId", expression = "java(mapId(userRes))")
	@Mapping(target = "keycloakUserId", expression = "java(mapKeycloakUserId(userRes))")
	@Mapping(target = "organisationsEinheitIds", expression = "java(mapOrganisationsEinheitIds(userRes))")
	@Mapping(target = "roles", expression = "java(mapRoles(userRes))")
	@Mapping(target = "lastSyncTimestamp", ignore = true)
	@Mapping(target = "deleted", ignore = true)
	public abstract User toKopUser(UserResource userRes);

	Date mapCreatedAt(UserResource userRes) {
		var createdAt = userRes.toRepresentation().getCreatedTimestamp();

		return Optional.ofNullable(createdAt).map(Date::new).orElse(new Date());
	}

	Set<String> mapOrganisationsEinheitIds(UserResource userRes) {
		var groups = userRes.groups();
		return Stream.concat(
						getOrganisationsEinheitIdsFromGroups(groups),
						getOrganisationsEinheitIdsFromUser(userRes)
				)
				.filter(StringUtils::isNotBlank)
				.collect(Collectors.toSet());
	}

	private Stream<String> getOrganisationsEinheitIdsFromGroups(List<GroupRepresentation> groups) {
		return groups.stream()
				.map(this::mapGroup)
				.filter(Objects::nonNull)
				.map(attributeMap -> attributeMap.get(properties.organisationsEinheitIdKey()))
				.filter(Objects::nonNull)
				.flatMap(Collection::stream);
	}

	private Stream<String> getOrganisationsEinheitIdsFromUser(UserResource userRes) {
		return getUserAttributes(userRes)
				.map(attributes -> attributes.get(properties.organisationsEinheitIdKey()))
				.orElse(Collections.emptyList())
				.stream();
	}

	private Map<String, List<String>> mapGroup(GroupRepresentation group) {
		var groupFromRealm = realm.getGroupByPath(group.getPath());

		return Optional.ofNullable(groupFromRealm).map(GroupRepresentation::getAttributes).orElse(null);
	}

	List<String> mapRoles(UserResource userRes) {
		var roleRepresentation = Optional.ofNullable(userRes.roles().getAll().getClientMappings())
				.filter(map -> map.containsKey(properties.client()))
				.map(map -> map.get(properties.client()))
				.map(ClientMappingsRepresentation::getMappings)
				.orElseGet(Collections::emptyList);

		return roleRepresentation.stream().map(RoleRepresentation::getName).toList();
	}

	String mapId(UserResource userRes) {
		var userRepresentation = userRes.toRepresentation();

		return Optional.ofNullable(userRepresentation.getAttributes())
				.map(attributes -> attributes.get(properties.ldapIdKey()))
				.map(List::getFirst)
				.orElseGet(userRepresentation::getId);

	}

	String mapKeycloakUserId(UserResource userRes) {
		return userRes.toRepresentation().getId();
	}

	String mapEmail(UserResource userRes) {
		return userRes.toRepresentation().getEmail();
	}

	String mapFirstName(UserResource userRes) {
		return userRes.toRepresentation().getFirstName();
	}

	String mapLastName(UserResource userRes) {
		return userRes.toRepresentation().getLastName();
	}

	String mapUsername(UserResource userRes) {
		return userRes.toRepresentation().getUsername();
	}

	String mapFullName(UserResource userRes) {
		return String.join(" ", Stream.of(userRes.toRepresentation().getFirstName(), userRes.toRepresentation().getLastName())
				.filter(Objects::nonNull).toArray(String[]::new));
	}

	String mapFullNameReversed(UserResource userRes) {
		return String.join(" ", Stream.of(userRes.toRepresentation().getLastName(), userRes.toRepresentation().getFirstName())
				.filter(Objects::nonNull).toArray(String[]::new));
	}

	private Optional<Map<String, List<String>>> getUserAttributes(UserResource userResource) {
		return Optional.ofNullable(userResource.toRepresentation().getAttributes());
	}
}