package org.infinispan.server.test.query;

import static org.infinispan.server.test.client.hotrod.security.HotRodSaslAuthTestBase.ADMIN_LOGIN;
import static org.infinispan.server.test.client.hotrod.security.HotRodSaslAuthTestBase.ADMIN_PASSWD;
import static org.infinispan.server.test.client.hotrod.security.HotRodSaslAuthTestBase.READER_LOGIN;
import static org.infinispan.server.test.client.hotrod.security.HotRodSaslAuthTestBase.READER_PASSWD;
import static org.infinispan.server.test.client.hotrod.security.HotRodSaslAuthTestBase.SUPERVISOR_LOGIN;
import static org.infinispan.server.test.client.hotrod.security.HotRodSaslAuthTestBase.SUPERVISOR_PASSWD;
import static org.infinispan.server.test.client.hotrod.security.HotRodSaslAuthTestBase.WRITER_LOGIN;
import static org.infinispan.server.test.client.hotrod.security.HotRodSaslAuthTestBase.WRITER_PASSWD;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;

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

import org.infinispan.arquillian.core.InfinispanResource;
import org.infinispan.arquillian.core.RemoteInfinispanServer;
import org.infinispan.arquillian.core.RunningServer;
import org.infinispan.arquillian.core.WithRunningServer;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
import org.infinispan.client.hotrod.Search;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.client.hotrod.exceptions.HotRodClientException;
import org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller;
import org.infinispan.commons.util.Util;
import org.infinispan.protostream.sampledomain.User;
import org.infinispan.protostream.sampledomain.marshallers.MarshallerRegistration;
import org.infinispan.query.dsl.Query;
import org.infinispan.query.dsl.QueryFactory;
import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants;
import org.infinispan.server.test.category.Queries;
import org.infinispan.server.test.util.RemoteCacheManagerFactory;
import org.infinispan.server.test.util.security.SecurityConfigurationHelper;
import org.jboss.arquillian.junit.Arquillian;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

/**
 * Tests for remote queries over HotRod with security on a DIST indexed/non-indexed cache.
 *
 * @author Adrian Nistor
 * @since 7.2
 */
@Category(Queries.class)
@RunWith(Arquillian.class)
@WithRunningServer({@RunningServer(name = "remote-query-security")})
public class RemoteQuerySecurityIT {

   @InfinispanResource("remote-query-security")
   protected RemoteInfinispanServer server;

   private RemoteCacheManagerFactory rcmFactory;
   private final Map<String, RemoteCacheManager> remoteCacheManagers = new HashMap<>();

   private static final String TEST_CACHE_INDEXED = "test_cache_indexed";
   private static final String TEST_CACHE_NOT_INDEXED = "test_cache_not_indexed";

   private static final String TEST_SERVER_NAME = "node2";
   private static final String SASL_MECH = "PLAIN";

   @Rule
   public ExpectedException expectedException = ExpectedException.none();

   @Before
   public void setUp() throws Exception {
      rcmFactory = new RemoteCacheManagerFactory();
      remoteCacheManagers.put(ADMIN_LOGIN, rcmFactory.createManager(getClientConfigBuilderForUser(ADMIN_LOGIN, ADMIN_PASSWD)));
      remoteCacheManagers.put(WRITER_LOGIN, rcmFactory.createManager(getClientConfigBuilderForUser(WRITER_LOGIN, WRITER_PASSWD)));
      remoteCacheManagers.put(READER_LOGIN, rcmFactory.createManager(getClientConfigBuilderForUser(READER_LOGIN, READER_PASSWD)));
      remoteCacheManagers.put(SUPERVISOR_LOGIN, rcmFactory.createManager(getClientConfigBuilderForUser(SUPERVISOR_LOGIN, SUPERVISOR_PASSWD)));

      //initialize server-side serialization context
      RemoteCache<String, String> metadataCache = remoteCacheManagers.get(ADMIN_LOGIN).getCache(ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME);
      String proto = Util.getResourceAsString("/sample_bank_account/bank.proto", getClass().getClassLoader());
      metadataCache.put("sample_bank_account/bank.proto", proto);
      assertFalse(metadataCache.containsKey(ProtobufMetadataManagerConstants.ERRORS_KEY_SUFFIX));

      //initialize client-side serialization context
      MarshallerRegistration.registerMarshallers(ProtoStreamMarshaller.getSerializationContext(remoteCacheManagers.get(ADMIN_LOGIN)));
      MarshallerRegistration.registerMarshallers(ProtoStreamMarshaller.getSerializationContext(remoteCacheManagers.get(READER_LOGIN)));
      MarshallerRegistration.registerMarshallers(ProtoStreamMarshaller.getSerializationContext(remoteCacheManagers.get(WRITER_LOGIN)));
      MarshallerRegistration.registerMarshallers(ProtoStreamMarshaller.getSerializationContext(remoteCacheManagers.get(SUPERVISOR_LOGIN)));

      User user = new User();
      user.setId(1);
      user.setName("Tom");
      user.setSurname("Cat");
      user.setGender(User.Gender.MALE);
      user.setAccountIds(Collections.singleton(12));

      remoteCacheManagers.get(ADMIN_LOGIN).getCache(TEST_CACHE_INDEXED).put(1, user);
      remoteCacheManagers.get(ADMIN_LOGIN).getCache(TEST_CACHE_NOT_INDEXED).put(1, user);
   }

   private ConfigurationBuilder getClientConfigBuilderForUser(String login, String password) {
      return new SecurityConfigurationHelper(SASL_MECH)
            .forIspnServer(server)
            .withServerName(TEST_SERVER_NAME)
            .forCredentials(login, password)
            .marshaller(new ProtoStreamMarshaller());
   }

   @After
   public void tearDown() {
      if (rcmFactory != null) {
         rcmFactory.stopManagers();
      }
      rcmFactory = null;
   }

   @Test
   public void testReaderQueryIndexed() {
      expectedException.expect(HotRodClientException.class);
      expectedException.expectMessage("Unauthorized access");

      execQuery(READER_LOGIN, TEST_CACHE_INDEXED);
   }

   @Test
   public void testReaderQueryNotIndexed() {
      expectedException.expect(HotRodClientException.class);
      expectedException.expectMessage("Unauthorized access");

      execQuery(READER_LOGIN, TEST_CACHE_NOT_INDEXED);
   }

   @Test
   public void testWriterQueryIndexed() {
      expectedException.expect(HotRodClientException.class);
      expectedException.expectMessage("Unauthorized access");

      execQuery(WRITER_LOGIN, TEST_CACHE_INDEXED);
   }

   @Test
   public void testWriterQueryNotIndexed() {
      expectedException.expect(HotRodClientException.class);
      expectedException.expectMessage("Unauthorized access");

      execQuery(WRITER_LOGIN, TEST_CACHE_NOT_INDEXED);
   }

   @Test
   public void testSupervisorQueryIndexed() {
      execQuery(SUPERVISOR_LOGIN, TEST_CACHE_INDEXED);
   }

   @Test
   public void testSupervisorQueryNotIndexed() {
      execQuery(SUPERVISOR_LOGIN, TEST_CACHE_NOT_INDEXED);
   }

   @Test
   public void testAdminQueryIndexed() {
      execQuery(ADMIN_LOGIN, TEST_CACHE_INDEXED);
   }

   @Test
   public void testAdminQueryNotIndexed() {
      execQuery(ADMIN_LOGIN, TEST_CACHE_NOT_INDEXED);
   }

   private void execQuery(String userLogin, String cacheName) {
      RemoteCache<Object, Object> cache = remoteCacheManagers.get(userLogin).getCache(cacheName);
      QueryFactory qf = Search.getQueryFactory(cache);
      Query query = qf.from(User.class)
            .having("name").eq("Tom")
            .build();
      List<User> list = query.list();
      assertNotNull(list);
      assertEquals(1, list.size());
      assertEquals(User.class, list.get(0).getClass());
   }
}
