/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.exoplatform.social.rest.impl.identity;

import java.util.List;

import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MultivaluedMap;

import org.apache.commons.lang3.StringUtils;

import org.exoplatform.services.rest.impl.ContainerResponse;
import org.exoplatform.services.rest.impl.MultivaluedMapImpl;
import org.exoplatform.social.core.identity.model.Identity;
import org.exoplatform.social.core.identity.model.Profile;
import org.exoplatform.social.core.identity.provider.OrganizationIdentityProvider;
import org.exoplatform.social.core.manager.IdentityManager;
import org.exoplatform.social.core.manager.RelationshipManager;
import org.exoplatform.social.core.profileproperty.ProfilePropertyService;
import org.exoplatform.social.core.profileproperty.model.ProfilePropertySetting;
import org.exoplatform.social.core.relationship.model.Relationship;
import org.exoplatform.social.core.space.model.Space;
import org.exoplatform.social.core.space.spi.SpaceService;
import org.exoplatform.social.rest.entity.CollectionEntity;
import org.exoplatform.social.rest.entity.DataEntity;
import org.exoplatform.social.rest.entity.IdentityEntity;
import org.exoplatform.social.rest.entity.ProfilePropertySettingEntity;
import org.exoplatform.social.service.rest.api.VersionResources;
import org.exoplatform.social.service.test.AbstractResourceTest;

public class IdentityRestResourcesTest extends AbstractResourceTest {
  private IdentityManager identityManager;
  private RelationshipManager relationshipManager;

  public void setUp() throws Exception {
    super.setUp();
    
    System.setProperty("gatein.email.domain.url", "localhost:8080");

    identityManager = getContainer().getComponentInstanceOfType(IdentityManager.class);
    relationshipManager = getContainer().getComponentInstanceOfType(RelationshipManager.class);
    addResource(IdentityRest.class, null);
    createIdentity("root");
    createIdentity("john");
    createIdentity("mary");
    createIdentity("demo");
  }

  public void tearDown() throws Exception {
    super.tearDown();
    removeResource(IdentityRest.class);
  }

  public void testGetIdentities() throws Exception {
    startSessionAs("root");
    ContainerResponse response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities?limit=5&offset=0", "", null, null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
    CollectionEntity collections = (CollectionEntity) response.getEntity();
    assertEquals(4, collections.getEntities().size());
  }
  
  
  public void testGetIdentityByProviderIdAndRemoteId() throws Exception {
    startSessionAs("root");
    ContainerResponse response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/organization/root", "", null, null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
    DataEntity identityEntity = (DataEntity) response.getEntity();
    assertNotNull(identityEntity);
    assertEquals("root", identityEntity.get("remoteId"));
    assertEquals("organization", identityEntity.get("providerId"));
    assertNotNull(identityEntity.get("id"));
  }

  public void testGetCommonConnectionsWithIdentity() throws Exception {
    startSessionAs("root");
    Identity rootIdentity = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, "root");
    Identity johnIdentity = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, "john");
    Identity maryIdentity = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, "mary");
    relationshipManager.inviteToConnect(rootIdentity, johnIdentity);
    relationshipManager.inviteToConnect(johnIdentity, maryIdentity);
    relationshipManager.confirm(maryIdentity, johnIdentity);
    relationshipManager.confirm(johnIdentity, rootIdentity);
    ContainerResponse response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + maryIdentity.getId() + "/commonConnections?returnSize=true", "", null, null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
    CollectionEntity collections = (CollectionEntity) response.getEntity();
    assertEquals(1, collections.getEntities().size());
  }

  public void testGetIdentityById() throws Exception {
    startSessionAs("root");
    Identity rootIdentity = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, "root");
    Identity johnIdentity = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, "john");

    ContainerResponse response = service("GET",
                                         "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(),
                                         "",
                                         null,
                                         null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
    EntityTag eTag = (EntityTag) response.getHttpHeaders().getFirst("ETAG");
    assertNotNull(eTag);

    MultivaluedMap<String, String> headers = new MultivaluedMapImpl();
    headers.putSingle("If-None-Match", "\"" + eTag.getValue() + "\"");
    response =
             service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(), "", headers, null);
    assertNotNull(response);
    assertEquals(304, response.getStatus());

    // a relationship has added, the cache identity should be cleared
    relationshipManager.inviteToConnect(rootIdentity, johnIdentity);
    relationshipManager.confirm(johnIdentity, rootIdentity);
    headers = new MultivaluedMapImpl();
    headers.putSingle("If-None-Match", "\"" + eTag.getValue() + "\"");
    response =
             service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(), "", headers, null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());

    // a relationship has removed, the cache identity should be cleared
    Relationship relationship = relationshipManager.get(rootIdentity, johnIdentity);
    relationshipManager.delete(relationship);
    headers = new MultivaluedMapImpl();
    headers.putSingle("If-None-Match", "\"" + eTag.getValue() + "\"");
    response =
             service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId()+"?expand=settings", "", headers, null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
  }

  public void testGetIdentityByIdWhenHiddenProperties() throws Exception {
    ProfilePropertyService profileSettingsService = getContainer().getComponentInstanceOfType(ProfilePropertyService.class);

    Identity johnIdentity = identityManager.getOrCreateUserIdentity("john");

    startSessionAs("mary");
    ContainerResponse response = service("GET",
                                         "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId() +
                                             "?expand=settings",
                                         "",
                                         null,
                                         null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
    IdentityEntity identityEntity = (IdentityEntity) response.getEntity();
    assertNotNull(identityEntity.getProfile().getFullname());
    assertNotNull(identityEntity.getProfile().getFirstname());
    assertNotNull(identityEntity.getProfile().getLastname());
    assertNull(identityEntity.getProfile().getCreatedDate());
    assertNotNull(identityEntity.getProfile().getEmail());

    List<ProfilePropertySettingEntity> properties = identityEntity.getProfile().getProperties();
    assertTrue(properties.stream().anyMatch(p -> StringUtils.equals(Profile.EMAIL, p.getPropertyName())));

    ProfilePropertySetting emailPropertySetting = profileSettingsService.getProfileSettingByName(Profile.EMAIL);

    // Hide email property setting for user
    profileSettingsService.hidePropertySetting(Long.parseLong(identityEntity.getId()),
                                               emailPropertySetting.getId());
    response = service("GET",
                       "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId() + "?expand=settings",
                       "",
                       null,
                       null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
    identityEntity = (IdentityEntity) response.getEntity();
    properties = identityEntity.getProfile().getProperties();
    assertTrue(properties.stream().noneMatch(p -> StringUtils.equals(Profile.EMAIL, p.getPropertyName())));

    // Show email property setting for user
    profileSettingsService.showPropertySetting(Long.parseLong(identityEntity.getId()),
                                               emailPropertySetting.getId());
    response = service("GET",
                       "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId() + "?expand=settings",
                       "",
                       null,
                       null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
    identityEntity = (IdentityEntity) response.getEntity();
    properties = identityEntity.getProfile().getProperties();
    assertTrue(properties.stream().anyMatch(p -> StringUtils.equals(Profile.EMAIL, p.getPropertyName())));

    // Hide email property setting globally
    emailPropertySetting.setVisible(false);
    profileSettingsService.updatePropertySetting(emailPropertySetting);

    ProfilePropertySetting profilePropertySetting = profileSettingsService.getProfileSettingByName(Profile.LAST_NAME);
    profilePropertySetting.setVisible(false); // Not hiddenable
    profileSettingsService.updatePropertySetting(profilePropertySetting);
    try {
      response = service("GET",
                         "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId() + "?expand=settings",
                         "",
                         null,
                         null);
      assertNotNull(response);
      assertEquals(200, response.getStatus());
      identityEntity = (IdentityEntity) response.getEntity();
      assertNotNull(identityEntity.getProfile().getFullname());
      assertNotNull(identityEntity.getProfile().getFirstname());
      assertNotNull(identityEntity.getProfile().getLastname());
      assertNull(identityEntity.getProfile().getCreatedDate());
      assertNull(identityEntity.getProfile().getEmail());
      properties = identityEntity.getProfile().getProperties();
      assertTrue(properties.stream().noneMatch(p -> StringUtils.equals(Profile.EMAIL, p.getPropertyName())));

      // Access email with an administrator even when 'email' field is hidden
      startSessionAs("root");
      response = service("GET",
                         "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId() + "?expand=settings",
                         "",
                         null,
                         null);
      assertNotNull(response);
      assertEquals(200, response.getStatus());
      identityEntity = (IdentityEntity) response.getEntity();
      assertNotNull(identityEntity.getProfile().getFullname());
      assertNotNull(identityEntity.getProfile().getFirstname());
      assertNotNull(identityEntity.getProfile().getLastname());
      assertNotNull(identityEntity.getProfile().getCreatedDate());
      assertNotNull(identityEntity.getProfile().getEmail());

      properties = identityEntity.getProfile().getProperties();
      assertTrue(properties.stream().anyMatch(p -> StringUtils.equals(Profile.EMAIL, p.getPropertyName())));
    } finally {
      profilePropertySetting = profileSettingsService.getProfileSettingByName(Profile.EMAIL);
      profilePropertySetting.setVisible(true);
      profileSettingsService.updatePropertySetting(profilePropertySetting);
      profilePropertySetting = profileSettingsService.getProfileSettingByName(Profile.LAST_NAME);
      profilePropertySetting.setVisible(true);
      profileSettingsService.updatePropertySetting(profilePropertySetting);
    }
  }

  public void testGetIdentityCache() throws Exception {
    startSessionAs("root");
    Identity johnIdentity = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, "john");

    ContainerResponse response = service("GET",
                                         "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(),
                                         "",
                                         null,
                                         null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
    EntityTag eTagRoot = (EntityTag) response.getHttpHeaders().getFirst("ETAG");
    assertNotNull(eTagRoot);

    MultivaluedMap<String, String> headers = new MultivaluedMapImpl();
    headers.putSingle("If-None-Match", "\"" + eTagRoot.getValue() + "\"");
    response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(), "", headers, null);
    assertNotNull(response);
    assertEquals(304, response.getStatus());

    startSessionAs("john");

    headers.putSingle("If-None-Match", "\"" + eTagRoot.getValue() + "\"");
    response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(), "", headers, null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());

    EntityTag eTagJohn = (EntityTag) response.getHttpHeaders().getFirst("ETAG");
    assertNotNull(eTagJohn);

    headers.putSingle("If-None-Match", "\"" + eTagJohn.getValue() + "\"");
    response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(), "", headers, null);
    assertNotNull(response);
    assertEquals(304, response.getStatus());

    startSessionAs("root");

    headers.putSingle("If-None-Match", "\"" + eTagJohn.getValue() + "\"");
    response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(), "", headers, null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());

    headers.putSingle("If-None-Match", "\"" + eTagRoot.getValue() + "\"");
    response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(), "", headers, null);
    assertNotNull(response);
    assertEquals(304, response.getStatus());

    headers.putSingle("If-None-Match", "\"" + eTagJohn.getValue() + "\"");
    response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId()+"?expand=settings", "", headers, null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
    eTagJohn = (EntityTag) response.getHttpHeaders().getFirst("ETAG");

    headers.putSingle("If-None-Match", "\"" + eTagJohn.getValue() + "\"");
    response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId()+"?expand=settings", "", headers, null);
    assertNotNull(response);
    assertEquals(304, response.getStatus());

    Profile profile = johnIdentity.getProfile();
    profile.setProperty("position", "Developer");
    identityManager.updateProfile(profile, true);
    response = service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId()+"?expand=settings", "", headers, null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
  }

  public void testCacheWhenUserJoinsSpace() throws Exception {
    startSessionAs("root");
    Identity johnIdentity = identityManager.getOrCreateIdentity(OrganizationIdentityProvider.NAME, "john");

    ContainerResponse response = service("GET",
                                         "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(),
                                         "",
                                         null,
                                         null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
    EntityTag eTag = (EntityTag) response.getHttpHeaders().getFirst("ETAG");
    assertNotNull(eTag);

    MultivaluedMap<String, String> headers = new MultivaluedMapImpl();
    headers.putSingle("If-None-Match", "\"" + eTag.getValue() + "\"");
    response =
             service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(), "", headers, null);
    assertNotNull(response);
    assertEquals(304, response.getStatus());

    // a user has been added to a space, the cache identity should be cleared
    SpaceService spaceService = getContainer().getComponentInstanceOfType(SpaceService.class);

    Space newSpace = new Space();
    newSpace.setDisplayName("space");
    newSpace.setPrettyName("space");
    newSpace.setManagers(new String[] { "root" });
    newSpace.setRegistration(Space.OPEN);
    newSpace.setVisibility(Space.PRIVATE);
    newSpace = spaceService.createSpace(newSpace, "root");

    spaceService.addMember(newSpace, johnIdentity.getRemoteId());

    response =
             service("GET", "/" + VersionResources.VERSION_ONE + "/social/identities/" + johnIdentity.getId(), "", headers, null);
    assertNotNull(response);
    assertEquals(200, response.getStatus());
  }
}
