/**
 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Liferay Enterprise
 * Subscription License ("License"). You may not use this file except in
 * compliance with the License. You can obtain a copy of the License by
 * contacting Liferay, Inc. See the License for the specific language governing
 * permissions and limitations under the License, including but not limited to
 * distribution rights of the Software.
 *
 *
 *
 */

package com.liferay.headless.admin.user.internal.resource.v1_0;

import com.liferay.asset.kernel.model.AssetTag;
import com.liferay.asset.kernel.service.AssetTagLocalService;
import com.liferay.headless.admin.user.dto.v1_0.EmailAddress;
import com.liferay.headless.admin.user.dto.v1_0.OrganizationBrief;
import com.liferay.headless.admin.user.dto.v1_0.Phone;
import com.liferay.headless.admin.user.dto.v1_0.PostalAddress;
import com.liferay.headless.admin.user.dto.v1_0.RoleBrief;
import com.liferay.headless.admin.user.dto.v1_0.SiteBrief;
import com.liferay.headless.admin.user.dto.v1_0.UserAccount;
import com.liferay.headless.admin.user.dto.v1_0.UserAccountContactInformation;
import com.liferay.headless.admin.user.dto.v1_0.WebUrl;
import com.liferay.headless.admin.user.internal.dto.v1_0.util.CustomFieldsUtil;
import com.liferay.headless.admin.user.internal.dto.v1_0.util.EmailAddressUtil;
import com.liferay.headless.admin.user.internal.dto.v1_0.util.PhoneUtil;
import com.liferay.headless.admin.user.internal.dto.v1_0.util.PostalAddressUtil;
import com.liferay.headless.admin.user.internal.dto.v1_0.util.WebUrlUtil;
import com.liferay.headless.admin.user.internal.odata.entity.v1_0.UserAccountEntityModel;
import com.liferay.headless.admin.user.resource.v1_0.UserAccountResource;
import com.liferay.petra.function.UnsafeConsumer;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.language.LanguageUtil;
import com.liferay.portal.kernel.model.Contact;
import com.liferay.portal.kernel.model.Group;
import com.liferay.portal.kernel.model.GroupConstants;
import com.liferay.portal.kernel.model.ListType;
import com.liferay.portal.kernel.model.Organization;
import com.liferay.portal.kernel.model.Role;
import com.liferay.portal.kernel.model.User;
import com.liferay.portal.kernel.model.UserGroup;
import com.liferay.portal.kernel.search.BooleanClauseOccur;
import com.liferay.portal.kernel.search.BooleanQuery;
import com.liferay.portal.kernel.search.Field;
import com.liferay.portal.kernel.search.Sort;
import com.liferay.portal.kernel.search.filter.BooleanFilter;
import com.liferay.portal.kernel.search.filter.Filter;
import com.liferay.portal.kernel.search.filter.TermFilter;
import com.liferay.portal.kernel.security.auth.PrincipalException;
import com.liferay.portal.kernel.security.permission.ActionKeys;
import com.liferay.portal.kernel.security.permission.PermissionChecker;
import com.liferay.portal.kernel.security.permission.PermissionThreadLocal;
import com.liferay.portal.kernel.service.GroupService;
import com.liferay.portal.kernel.service.ListTypeService;
import com.liferay.portal.kernel.service.RoleLocalService;
import com.liferay.portal.kernel.service.RoleService;
import com.liferay.portal.kernel.service.UserService;
import com.liferay.portal.kernel.theme.ThemeDisplay;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.ListUtil;
import com.liferay.portal.kernel.util.Portal;
import com.liferay.portal.odata.entity.EntityModel;
import com.liferay.portal.vulcan.pagination.Page;
import com.liferay.portal.vulcan.pagination.Pagination;
import com.liferay.portal.vulcan.resource.EntityModelResource;
import com.liferay.portal.vulcan.util.LocalizedMapUtil;
import com.liferay.portal.vulcan.util.SearchUtil;
import com.liferay.users.admin.kernel.util.UsersAdminUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.ws.rs.core.MultivaluedMap;

import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ServiceScope;

/**
 * @author Javier Gamarra
 */
@Component(
	properties = "OSGI-INF/liferay/rest/v1_0/user-account.properties",
	scope = ServiceScope.PROTOTYPE, service = UserAccountResource.class
)
public class UserAccountResourceImpl
	extends BaseUserAccountResourceImpl implements EntityModelResource {

	@Override
	public EntityModel getEntityModel(MultivaluedMap multivaluedMap) {
		return _entityModel;
	}

	@Override
	public UserAccount getMyUserAccount() throws Exception {
		PermissionChecker permissionChecker =
			PermissionThreadLocal.getPermissionChecker();

		return _toUserAccount(
			_userService.getUserById(permissionChecker.getUserId()));
	}

	@Override
	public Page<UserAccount> getOrganizationUserAccountsPage(
			Long organizationId, String search, Filter filter,
			Pagination pagination, Sort[] sorts)
		throws Exception {

		return _getUserAccountsPage(
			booleanQuery -> {
				BooleanFilter booleanFilter =
					booleanQuery.getPreBooleanFilter();

				booleanFilter.add(
					new TermFilter(
						"organizationIds", String.valueOf(organizationId)),
					BooleanClauseOccur.MUST);
			},
			search, filter, pagination, sorts);
	}

	@Override
	public Page<UserAccount> getSiteUserAccountsPage(
			Long siteId, String search, Filter filter, Pagination pagination,
			Sort[] sorts)
		throws Exception {

		return _getUserAccountsPage(
			booleanQuery -> {
				BooleanFilter booleanFilter =
					booleanQuery.getPreBooleanFilter();

				booleanFilter.add(
					new TermFilter("groupId", String.valueOf(siteId)),
					BooleanClauseOccur.MUST);
			},
			search, filter, pagination, sorts);
	}

	@Override
	public UserAccount getUserAccount(Long userAccountId) throws Exception {
		return _toUserAccount(_userService.getUserById(userAccountId));
	}

	@Override
	public Page<UserAccount> getUserAccountsPage(
			String search, Filter filter, Pagination pagination, Sort[] sorts)
		throws Exception {

		PermissionChecker permissionChecker =
			PermissionThreadLocal.getPermissionChecker();

		if (!permissionChecker.isCompanyAdmin(contextCompany.getCompanyId())) {
			throw new PrincipalException.MustBeCompanyAdmin(permissionChecker);
		}

		return _getUserAccountsPage(
			booleanQuery -> {
				BooleanFilter booleanFilter =
					booleanQuery.getPreBooleanFilter();

				booleanFilter.add(
					new TermFilter("userName", StringPool.BLANK),
					BooleanClauseOccur.MUST_NOT);
			},
			search, filter, pagination, sorts);
	}

	private String _getListTypeMessage(long listTypeId) throws Exception {
		if (listTypeId == 0) {
			return null;
		}

		ListType listType = _listTypeService.getListType(listTypeId);

		return LanguageUtil.get(
			contextAcceptLanguage.getPreferredLocale(), listType.getName());
	}

	private List<Group> _getAllGroups(User user) throws Exception {
		List<Group> allGroups = new ArrayList<>();

		allGroups.addAll(_getGroups(user));
		allGroups.addAll(_getInheritedSiteGroups(user));
		allGroups.addAll(
			groupLocalService.getOrganizationsGroups(_getOrganizations(user)));
		allGroups.addAll(
			groupLocalService.getUserGroupsGroups(_getUserGroups(user)));

		return allGroups;
	}

	private List<Role> _getAllRoles(User user) throws Exception {
		List<Role> roles = _roleService.getUserRoles(user.getUserId());

		List<Group> roleGroups = ListUtil.filter(
			_getAllGroups(user),
			group -> _roleLocalService.hasGroupRoles(group.getGroupId()));

		for (Group group : roleGroups) {
			List<Role> groupRoles = _roleLocalService.getGroupRoles(
				group.getGroupId());

			for (Role role : groupRoles) {
				if (!roles.contains(role)) {
					roles.add(role);
				}
			}
		}

		return roles;
	}

	private List<Group> _getGroups(User user) throws Exception {
		PermissionChecker permissionChecker =
			PermissionThreadLocal.getPermissionChecker();

		if (permissionChecker.isCompanyAdmin()) {
			return user.getGroups();
		}

		return UsersAdminUtil.filterGroups(permissionChecker, user.getGroups());
	}

	private List<Group> _getInheritedSiteGroups(User user) throws Exception {
		SortedSet<Group> inheritedSiteGroupsSet = new TreeSet<>();

		inheritedSiteGroupsSet.addAll(
			groupLocalService.getUserGroupsRelatedGroups(_getUserGroups(user)));
		inheritedSiteGroupsSet.addAll(_getOrganizationRelatedGroups(user));

		return ListUtil.fromCollection(inheritedSiteGroupsSet);
	}

	private List<Group> _getOrganizationRelatedGroups(User user)
		throws Exception {

		List<Organization> organizations = _getOrganizations(user);

		if (organizations.isEmpty()) {
			return Collections.emptyList();
		}

		return groupLocalService.getOrganizationsRelatedGroups(organizations);
	}

	private List<Organization> _getOrganizations(User user) throws Exception {
		PermissionChecker permissionChecker =
			PermissionThreadLocal.getPermissionChecker();

		if (permissionChecker.isCompanyAdmin() ||
			permissionChecker.hasPermission(
				null, Organization.class.getName(),
				Organization.class.getName(), ActionKeys.VIEW)) {

			return user.getOrganizations();
		}

		return UsersAdminUtil.filterOrganizations(
			permissionChecker, user.getOrganizations());
	}

	private ThemeDisplay _getThemeDisplay(Group group) {
		return new ThemeDisplay() {
			{
				setPortalURL(StringPool.BLANK);
				setSiteGroupId(group.getGroupId());
			}
		};
	}

	private Page<UserAccount> _getUserAccountsPage(
			UnsafeConsumer<BooleanQuery, Exception> booleanQueryUnsafeConsumer,
			String search, Filter filter, Pagination pagination, Sort[] sorts)
		throws Exception {

		return SearchUtil.search(
			booleanQueryUnsafeConsumer, filter, User.class, search, pagination,
			queryConfig -> queryConfig.setSelectedFieldNames(
				Field.ENTRY_CLASS_PK),
			searchContext -> searchContext.setCompanyId(
				contextCompany.getCompanyId()),
			document -> _toUserAccount(
				_userService.getUserById(
					GetterUtil.getLong(document.get(Field.ENTRY_CLASS_PK)))),
			sorts);
	}

	private List<UserGroup> _getUserGroups(User user) {
		PermissionChecker permissionChecker =
			PermissionThreadLocal.getPermissionChecker();

		if (permissionChecker.isCompanyAdmin()) {
			return user.getUserGroups();
		}

		return UsersAdminUtil.filterUserGroups(
			permissionChecker, user.getUserGroups());
	}

	private OrganizationBrief _toOrganizationBrief(Organization organization) {
		return new OrganizationBrief() {
			{
				id = organization.getOrganizationId();
				name = organization.getName();
			}
		};
	}

	private RoleBrief _toRoleBrief(Role role) {
		return new RoleBrief() {
			{
				id = role.getRoleId();
				name = role.getTitle(
					contextAcceptLanguage.getPreferredLocale());
				name_i18n = LocalizedMapUtil.getI18nMap(
					contextAcceptLanguage.isAcceptAllLanguages(),
					role.getTitleMap());
			}
		};
	}

	private SiteBrief _toSiteBrief(Group group) {
		return new SiteBrief() {
			{
				id = group.getGroupId();
				name = group.getName(
					contextAcceptLanguage.getPreferredLocale());
				name_i18n = LocalizedMapUtil.getI18nMap(
					contextAcceptLanguage.isAcceptAllLanguages(),
					group.getNameMap());
			}
		};
	}

	private UserAccount _toUserAccount(User user) throws Exception {
		Contact contact = user.getContact();

		return new UserAccount() {
			{
				additionalName = user.getMiddleName();
				alternateName = user.getScreenName();
				birthDate = user.getBirthday();
				customFields = CustomFieldsUtil.toCustomFields(
					contextAcceptLanguage.isAcceptAllLanguages(),
					User.class.getName(), user.getUserId(), user.getCompanyId(),
					contextAcceptLanguage.getPreferredLocale());
				dateCreated = user.getCreateDate();
				dateModified = user.getModifiedDate();
				emailAddress = user.getEmailAddress();
				familyName = user.getLastName();
				givenName = user.getFirstName();
				honorificPrefix = _getListTypeMessage(contact.getPrefixId());
				honorificSuffix = _getListTypeMessage(contact.getSuffixId());
				id = user.getUserId();
				jobTitle = user.getJobTitle();
				keywords = ListUtil.toArray(
					_assetTagLocalService.getTags(
						User.class.getName(), user.getUserId()),
					AssetTag.NAME_ACCESSOR);
				name = user.getFullName();
				organizationBriefs = transformToArray(
					user.getOrganizations(),
					organization -> _toOrganizationBrief(organization),
					OrganizationBrief.class);
				roleBriefs = transformToArray(
					_getAllRoles(user), role -> _toRoleBrief(role),
					RoleBrief.class);
				siteBriefs = transformToArray(
					_groupService.getGroups(
						contextCompany.getCompanyId(),
						GroupConstants.DEFAULT_PARENT_GROUP_ID, true),
					group -> _toSiteBrief(group), SiteBrief.class);
				userAccountContactInformation =
					new UserAccountContactInformation() {
						{
							emailAddresses = transformToArray(
								user.getEmailAddresses(),
								EmailAddressUtil::toEmailAddress,
								EmailAddress.class);
							facebook = contact.getFacebookSn();
							jabber = contact.getJabberSn();
							postalAddresses = transformToArray(
								user.getAddresses(),
								address -> PostalAddressUtil.toPostalAddress(
									contextAcceptLanguage.
										isAcceptAllLanguages(),
									address, contextCompany.getCompanyId(),
									contextAcceptLanguage.getPreferredLocale()),
								PostalAddress.class);
							skype = contact.getSkypeSn();
							sms = contact.getSmsSn();
							telephones = transformToArray(
								user.getPhones(), PhoneUtil::toPhone,
								Phone.class);
							twitter = contact.getTwitterSn();
							webUrls = transformToArray(
								user.getWebsites(), WebUrlUtil::toWebUrl,
								WebUrl.class);
						}
					};

				setDashboardURL(
					() -> {
						Group group = user.getGroup();

						return group.getDisplayURL(
							_getThemeDisplay(group), true);
					});
				setImage(
					() -> {
						if (user.getPortraitId() == 0) {
							return null;
						}

						ThemeDisplay themeDisplay = new ThemeDisplay() {
							{
								setPathImage(_portal.getPathImage());
							}
						};

						return user.getPortraitURL(themeDisplay);
					});
				setProfileURL(
					() -> {
						Group group = user.getGroup();

						return group.getDisplayURL(_getThemeDisplay(group));
					});
			}
		};
	}

	private static final EntityModel _entityModel =
		new UserAccountEntityModel();

	@Reference
	private AssetTagLocalService _assetTagLocalService;

	@Reference
	private GroupService _groupService;

	@Reference
	private ListTypeService _listTypeService;

	@Reference
	private Portal _portal;

	@Reference
	private RoleLocalService _roleLocalService;

	@Reference
	private RoleService _roleService;

	@Reference
	private UserService _userService;

}