/*
 * Copyright 2019 Arcus Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.iris.client.server.rest;

import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import org.easymock.Capture;
import org.easymock.CaptureType;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;

import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.Futures;
import com.google.inject.Inject;
import com.iris.bridge.metrics.BridgeMetrics;
import com.iris.bridge.server.client.Client;
import com.iris.bridge.server.client.ClientFactory;
import com.iris.bridge.server.netty.Authenticator;
import com.iris.core.dao.PersonDAO;
import com.iris.core.dao.PersonPlaceAssocDAO;
import com.iris.core.platform.PlatformMessageBus;
import com.iris.io.json.JSON;
import com.iris.messages.ClientMessage;
import com.iris.messages.ErrorEvent;
import com.iris.messages.MessageBody;
import com.iris.messages.MessageConstants;
import com.iris.messages.PlatformMessage;
import com.iris.messages.address.Address;
import com.iris.messages.capability.Capability;
import com.iris.messages.capability.PersonCapability;
import com.iris.messages.model.Fixtures;
import com.iris.messages.model.Person;
import com.iris.messages.services.PlatformConstants;
import com.iris.messages.type.Population;
import com.iris.population.PlacePopulationCacheManager;
import com.iris.test.IrisMockTestCase;
import com.iris.test.Mocks;

import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.local.LocalChannel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.util.CharsetUtil;


@Mocks({ PersonDAO.class, 
	PersonPlaceAssocDAO.class,
	FullHttpRequest.class, 
	ChannelHandlerContext.class, 
	PlatformMessageBus.class, 
	ClientFactory.class, 
	Client.class, 
	Authenticator.class,	
	BridgeMetrics.class,
	PlacePopulationCacheManager.class
})
public class TestVerifyEmailRESTHandler extends IrisMockTestCase {

	@Inject protected PlacePopulationCacheManager mockPopulationCacheMgr;
   @Inject
   private PersonDAO personDao;
   @Inject
   private PersonPlaceAssocDAO personPlaceAssocDao;
   
   @Inject
   private ClientFactory clientFactory;
   private Channel channel = new LocalChannel();
   @Inject
   private Client client;
   
   @Inject
   private Authenticator authenticator;

   @Inject
   private VerifyEmailRESTHandler handler;

   @Inject
   private FullHttpRequest request;

   @Inject
   private ChannelHandlerContext ctx;
   
   @Inject
   private PlatformMessageBus platformBus;

   private Capture<PlatformMessage> platformMsgCapture;

   @Before
	public void setupMocks() {
   	EasyMock.expect(mockPopulationCacheMgr.getPopulationByPlaceId(EasyMock.anyObject(UUID.class))).andReturn(Population.NAME_GENERAL).anyTimes();
   	EasyMock.expect(mockPopulationCacheMgr.getPopulationByPlaceId(EasyMock.anyObject(String.class))).andReturn(Population.NAME_GENERAL).anyTimes();
   }	

   @Test
   public void testRequestEmailVerification_Success() throws Exception {
   	Person curPerson = createPerson();
   	String token = "t123545";
      curPerson.setEmailVerificationToken(token);
      
      mockSetup(curPerson.getId(), token);		
   	
   	EasyMock.expect(personDao.findById(curPerson.getId())).andReturn(curPerson);
   	Capture<Person> personUpdatedCapture = EasyMock.newCapture(CaptureType.LAST);
   	EasyMock.expect(personDao.update(EasyMock.capture(personUpdatedCapture))).andReturn(curPerson);
   	
   	Set<UUID> placeIds = ImmutableSet.<UUID>of(curPerson.getCurrPlace(), UUID.randomUUID());
   	EasyMock.expect(personPlaceAssocDao.findPlaceIdsByPerson(curPerson.getId())).andReturn(placeIds);
   	
      replay();      
      
      FullHttpResponse response = handler.respond(request, ctx);   
           
      //Should send a value change for each place this person belongs to
   	List<PlatformMessage> msgs = platformMsgCapture.getValues();
   	Iterator<UUID> placeIdsIt = placeIds.iterator();
   	msgs.forEach((cur) -> {
   		assertValueChangeSent(cur, placeIdsIt.next());
   	});
   	
   	Person personUpdated = personUpdatedCapture.getValue();
   	assertNotNull(personUpdated.getEmailVerificationToken());
   	assertNotNull(personUpdated.getEmailVerified());
      
      MessageBody mb = toClientRequest(response);
      assertEquals(MessageConstants.MSG_EMPTY_MESSAGE, mb.getMessageType());
      
   }
   
   @Test
   public void testRequestEmailVerification_PersonNotExist() throws Exception {
      Person curPerson = createPerson();
      String token = "t123545";
      curPerson.setEmailVerificationToken(token);
      
      mockSetup(curPerson.getId(), token);		
   	
   	EasyMock.expect(personDao.findById(curPerson.getId())).andReturn(null);
      replay();      
      
      FullHttpResponse response = handler.respond(request, ctx);   
      
      MessageBody mb = toClientRequest(response);
      assertEquals(ErrorEvent.MESSAGE_TYPE, mb.getMessageType());     
      
   }
   
   @Test
   public void testRequestEmailVerification_TokenNotMatch() throws Exception {
      Person curPerson = createPerson();
      String token = "t123545";
      String token2 = "z123545";
      curPerson.setEmailVerificationToken(token);
      
      mockSetup(curPerson.getId(), token2);		
   	
   	EasyMock.expect(personDao.findById(curPerson.getId())).andReturn(curPerson).anyTimes();
      replay();      
      
      FullHttpResponse response = handler.respond(request, ctx);   
      
      MessageBody mb = toClientRequest(response);
      assertEquals(ErrorEvent.MESSAGE_TYPE, mb.getMessageType());     
      
   }
   
   @Test
   public void testRequestEmailVerification_TokenEmpty() throws Exception {
      Person curPerson = createPerson();
      String token = "t123545";
      curPerson.setEmailVerificationToken(token);
      
      mockSetup(curPerson.getId(), null);		//null token in request
   	
   	EasyMock.expect(personDao.findById(curPerson.getId())).andReturn(curPerson).anyTimes();
      replay();      
      
      FullHttpResponse response = handler.respond(request, ctx);   
      
      MessageBody mb = toClientRequest(response);
      assertEquals(ErrorEvent.MESSAGE_TYPE, mb.getMessageType());     
      
   }
   
   @Test
   public void testRequestEmailVerification_EmailAlreadyVerified() throws Exception {
      Person curPerson = createPerson();
      String token = "t123545";
      curPerson.setEmailVerificationToken(token);
      curPerson.setEmailVerified(new Date()); //already verified.
      
      mockSetup(curPerson.getId(), token);		
   	
   	EasyMock.expect(personDao.findById(curPerson.getId())).andReturn(curPerson).anyTimes();
      replay();      
      
      FullHttpResponse response = handler.respond(request, ctx);   
      
      MessageBody mb = toClientRequest(response);
      assertEquals(MessageConstants.MSG_EMPTY_MESSAGE, mb.getMessageType());     
      
   }
   
   private void mockSetup(UUID personId, String token) {
   	EasyMock.expect(client.getPrincipalId()).andReturn(personId).anyTimes();
      EasyMock.expect(request.content()).andReturn(Unpooled.copiedBuffer(generateClientMessage(personId, generateRequest(token)).getBytes()));
      EasyMock.expect(ctx.channel()).andReturn(channel).anyTimes();

      EasyMock.expect(clientFactory.get(channel)).andReturn(client).anyTimes();
      platformMsgCapture = EasyMock.newCapture(CaptureType.ALL);     
      EasyMock.expect(platformBus.send(EasyMock.capture(platformMsgCapture))).andAnswer(
         () -> {
            return Futures.immediateFuture(null);
         }
      ).anyTimes();
      
      
      //logout always needs to be called
      client.logout();
   	EasyMock.expectLastCall();   	  	
   	EasyMock.expect(authenticator.expireCookie()).andReturn(new DefaultCookie("test", "test"));
   }
   
   private void assertValueChangeSent(PlatformMessage msg, UUID placeId) {
   	assertNotNull(msg.getValue());
   	assertEquals(Capability.EVENT_VALUE_CHANGE, msg.getMessageType());
   	assertEquals(placeId.toString(), msg.getPlaceId());
		
	}

	private MessageBody toClientRequest(FullHttpResponse response) {
      String json = response.content().toString(CharsetUtil.UTF_8);
      ClientMessage clientMessage = JSON.fromJson(json, ClientMessage.class);
      return clientMessage.getPayload();
   }

   private MessageBody generateRequest(String token) {
      return PersonCapability.VerifyEmailRequest.builder().withToken(token).build();
   }

   private String generateClientMessage(UUID personId, MessageBody body) {
      ClientMessage.Builder messageBuilder = ClientMessage.builder()
      		.withCorrelationId("")
      		.withSource(Address.platformService(PlatformConstants.SERVICE_PEOPLE).getRepresentation())
      		.withDestination(Address.platformService(personId, PlatformConstants.SERVICE_PEOPLE).getRepresentation())
      		.withPayload(body);
      return JSON.toJson(messageBuilder.create());
   }
   
   private Person createPerson() {
   	Person curPerson = Fixtures.createPerson();
      curPerson.setId(UUID.randomUUID());
      curPerson.setEmailVerificationToken(null);
      curPerson.setEmailVerified(null);
      return curPerson;
   }

}