/**
 * 
 */
package com.microstar.tap3.login;

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

import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.HEAD;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.apache.commons.httpclient.HttpStatus;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.LoggerFactory;

import com.amazonaws.services.cognitoidentity.AmazonCognitoIdentity;
import com.amazonaws.services.cognitoidentity.AmazonCognitoIdentityClientBuilder;
import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProvider;
import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProviderClientBuilder;
import com.amazonaws.services.cognitoidp.model.AdminGetUserRequest;
import com.amazonaws.services.cognitoidp.model.AdminGetUserResult;
import com.amazonaws.services.cognitoidp.model.AdminInitiateAuthRequest;
import com.amazonaws.services.cognitoidp.model.AdminInitiateAuthResult;
import com.amazonaws.services.cognitoidp.model.AdminResetUserPasswordRequest;
import com.amazonaws.services.cognitoidp.model.AdminResetUserPasswordResult;
import com.amazonaws.services.cognitoidp.model.AdminRespondToAuthChallengeRequest;
import com.amazonaws.services.cognitoidp.model.AdminRespondToAuthChallengeResult;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.GetItemOutcome;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.PrimaryKey;
import com.amazonaws.services.dynamodbv2.document.PutItemOutcome;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.TableCollection;
import com.amazonaws.services.dynamodbv2.document.internal.IteratorSupport;
import com.amazonaws.services.dynamodbv2.model.ListTablesResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microstar.tap3.LoginResource;
import com.microstar.tap3.api.BaseResourceImpl;
import com.microstar.tap3.exceptions.InitializationException;
import com.microstar.tap3.login.api.BusinessUnit;
import com.microstar.tap3.login.api.LoginData;
import com.microstar.tap3.user.api.UserObject;
import com.microstar.tap3.util.ConfigSingleton;
import com.microstar.tap3.util.RestUtil;

/**
 * @author dmcdaniel
 *
 */
@Path("/login")
public class LoginResourceImpl extends BaseResourceImpl implements LoginResource
{
	private static Table						users				= null;
	private static ObjectMapper					mapper				= null;

	// Authentication stuff
	private static AWSCognitoIdentityProvider	idpClient			= null;
	private static String						APP_ID				= "";						// "";
	private static String						ACCT_ID				= "";
	private static String						USER_POOL_ID		= "us-west-";							// "us-east-";
	private static String						IDENTITY_POOL_ID	= "us-west-2:----";	// "";

	/**
	 * 
	 */
	public LoginResourceImpl()
	{
		LOG = LoggerFactory.getLogger(LoginResourceImpl.class);
	}

	@Override
	public void initDynamoLocal() throws InitializationException
	{
		LOG.info("Entering initDynamoLocal::creating client...");

		try
		{
			LOG.info("Creating Users instance...");
			users = dynamo.getTable("user");

			LOG.info("Creating Cognito clients...");
			idpClient = AWSCognitoIdentityProviderClientBuilder.defaultClient();

			if (ConfigSingleton.getInstance().getProperty("Cognito_APP_ID") != null)
				APP_ID = ConfigSingleton.getInstance().getProperty("Cognito_APP_ID");
			if (ConfigSingleton.getInstance().getProperty("Cognito_ACCT_ID") != null)
				ACCT_ID = ConfigSingleton.getInstance().getProperty("Cognito_ACCT_ID");
			if (ConfigSingleton.getInstance().getProperty("Cognito_USER_POOL_ID") != null)
				USER_POOL_ID = ConfigSingleton.getInstance().getProperty("Cognito_USER_POOL_ID");
			if (ConfigSingleton.getInstance().getProperty("Cognito_IDENTITY_POOL_ID") != null)
				IDENTITY_POOL_ID = ConfigSingleton.getInstance().getProperty("Cognito_IDENTITY_POOL_ID");

			mapper = new ObjectMapper();

			// noSQLinitialized = true;
		} catch (Exception ex)
		{
			LOG.error("::init:Caught exception: " + ex.getMessage());
			ex.printStackTrace();
			throw new InitializationException(ex);
		}

		LOG.info("Exiting initDynamoLocal");

	}

	@Override
	public void initSQLLocal() throws InitializationException
	{
	}

	@Override
	@HEAD
	@Path("/ping")
	public Response ping(@QueryParam("additionalDelay") @DefaultValue("0") int additionalDelay)
	{
        LOG.info("Entering ping::additionalDelay="+ additionalDelay);
        if ( !noSQLinitialized )
        {
                try {
                        initDynamo();
                } catch (InitializationException e) {
                        return Response.serverError()
                                        .entity(RestUtil.errorMsg("::init:Caught exception:" , e.getMessage()))
                                        .build();
                }
        }
        else
                RestUtil.sleep(additionalDelay);

        LOG.info("Exiting ping");
        return Response.ok().build();	}

	/*
	 * Options method for web app to be able to do the preflight calls and
	 * satisfy CORS requirements. (non-Javadoc)
	 * 
	 * @see com.microstar.tap3.api.BaseResourceImpl#options()
	 */
	@Override
	@OPTIONS
	public Response options()
	{
		return super.options();
	}

	public static void setup()
	{
		System.out.println("setup::Entering");
		try
		{
			AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient();

			DynamoDB dynamo = new DynamoDB(client);

			Map<String, Object> infoMap = new HashMap<String, Object>();
		} catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.microstar.tap3.LoginResource#doLogin(com.microstar.tap3.login.api.
	 * LoginData)
	 */
	@Override
	@POST
	@Consumes({ MediaType.APPLICATION_JSON })
	@Produces({ MediaType.APPLICATION_JSON })
	public Response doLogin(LoginData ld)
	{
		LOG.info("doLogin::Entering:" + ld);

		if (!noSQLinitialized)
		{
			try
			{
				initDynamo();
			} catch (InitializationException e)
			{
				return Response.serverError().entity(RestUtil.errorMsg("::init:Caught exception:", e.getMessage()))
						.build();
			}
		}

		//
		// To be done:
		// 1. Check DynamoDB for username
		// 2. If not found, return 404
		// 3. Start Login Step Function which will:
		// a. Perform Cognito authentication
		// b. Retrieve User Profile information from Aurora/DynamoDB
		String userId = null;

		try
		{
			// Check DynamoDB
			Item u = users.getItem(new PrimaryKey("username", ld.getUsername()));
			GetItemOutcome outcome = users.getItemOutcome(new PrimaryKey("username", ld.getUsername()));
			Item item = outcome.getItem();
			if (item == null)
			{
				LOG.info("doLogin::Dynamo user " + ld.getUsername() + " not found.");
				return Response.status(Status.NOT_FOUND).build();
			}

			// Get the userId field from Dynamo
			Map<String, Object> x = item.asMap();
			Map<String, Object> y = (Map<String, Object>) x.get("profile");
			userId = y.get("sub").toString();
		} catch (Exception ex)
		{
			LOG.debug("doLogin::Exception: " + ex.getMessage());
			ex.printStackTrace();
			return Response.status(Status.NOT_FOUND).build();
		}

		// Call cognito directly
		try
		{
			HashMap<String, String> authParams = new HashMap<>();
			authParams.put("USERNAME", ld.getUsername());
			authParams.put("PASSWORD", ld.getPassword());
			AdminInitiateAuthRequest req = new AdminInitiateAuthRequest().withAuthFlow("ADMIN_NO_SRP_AUTH")
					.withClientId(APP_ID).withAuthParameters(authParams).withUserPoolId(USER_POOL_ID);
			AdminInitiateAuthResult iaRes = idpClient.adminInitiateAuth(req);
			if (iaRes.getSdkHttpMetadata().getHttpStatusCode() != 200)
			{
				LOG.info("InitiateAuth failed::" + iaRes.getSdkHttpMetadata().getHttpStatusCode() + "/"
						+ iaRes.getChallengeName());
				return RestUtil.response(Status.NOT_FOUND, "InitiateAuth failed");
				// Specifically return NOT_FOUND even though the user isn't NOT
				// found. This doesn't let
				// hackers know that the user WAS found.
			}
			if (iaRes.getChallengeName().equals("NEW_PASSWORD_REQUIRED"))
			{
				LOG.info("InitiateAuth requires new password");
				return RestUtil.response(Status.RESET_CONTENT, RestUtil.errorMsg("Password must be changed",null));
			}
			ld.setUserToken(iaRes.getAuthenticationResult().getIdToken());

		} catch (Exception ex)
		{
			LOG.info("authenticate::Caught exception: " + ex.getMessage());
			ex.printStackTrace();

			return RestUtil.response(Status.NOT_FOUND, RestUtil.errorMsg("User Not Found",null));
		}

		try
		{
			// Call GET user directly
			String url = ConfigSingleton.getInstance().getProperty("apiURL") + "/users/" + userId;
			LOG.info("Calling user: " + url);
			HttpClient client = HttpClientBuilder.create().build();
			HttpGet request = new HttpGet(url);

			// request.addHeader("Authentication", "auth");
			request.addHeader("Accept", "application/json");
			request.addHeader("Authentication", ld.getUserToken());

			HttpResponse response = client.execute(request);

			if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)
			{
				return Response.status(HttpStatus.SC_NOT_FOUND).entity(RestUtil.errorMsg(
						"Call to user for User (" + userId + ") was: " + response.getStatusLine().getStatusCode(),
						null)).build();
			}

			UserObject uo = mapper.readValue(response.getEntity().getContent(), UserObject.class);

			ld.setPassword(null);
			ld.setUserId(userId);
			ld.setUsername(uo.getUsername());
			ld.setActive(uo.isActive());
			ld.setFirstName(uo.getFirstName());
			ld.setLastName(uo.getLastName());
			ld.setCellPhoneNumber(uo.getCellPhoneNumber());
			ld.setDeskPhoneNumber(uo.getDeskPhoneNumber());
			ld.setEmailAddress(uo.getEmailAddress());
			ld.setFaxNumber(uo.getFaxNumber());
			ld.setRootCustomerId(uo.getRootCustomerId());

			// Copy the permissions from the UserObject
			ld.setPermissions(uo.getPermissions());
			ld.setPerCustomerPermissions(uo.getPerCustomerPermissions());

			List<BusinessUnit> bus = new ArrayList<BusinessUnit>();

			if (uo.getBusinessUnitIds() != null)
			{
				for (String bu : uo.getBusinessUnitIds())
				{
					BusinessUnit newbu = new BusinessUnit();
					newbu.setId("id");
					newbu.setName(bu);
				}
				ld.setBusinessUnits(bus);
			}

			ld.setUserRole(uo.getUserRoleId());

			// Get the user's permissions

		} catch (Exception ex)
		{
			LOG.debug("doLogin::Exception: " + ex.getMessage());
			ex.printStackTrace();
			return Response.status(Status.NOT_FOUND).build();
		}

		return RestUtil.response(Status.OK, ld, null);
	}

	@Override
	@PUT
	@Path("/{id}")
	@Consumes({MediaType.APPLICATION_JSON})
	@Produces({MediaType.APPLICATION_JSON})
	public Response changePassword(@PathParam("id") String id, LoginData ld)
	{
		LOG.info("changePassword::Entering:" + ld);

		if (!noSQLinitialized)
		{
			try
			{
				initDynamo();
			} catch (InitializationException e)
			{
				return Response.serverError().entity(RestUtil.errorMsg("::init:Caught exception:", e.getMessage()))
						.build();
			}
		}

		try
		{
		// Setup to change a password
		Map<String, String> challengeResponses = new HashMap<String, String>();
		
		ld.setUsername("mac.test");
		challengeResponses.put("NEW_PASSWORD", ld.getPassword());
		challengeResponses.put("USERNAME", ld.getUsername());

//		AdminResetUserPasswordRequest arupReq = new AdminResetUserPasswordRequest()
//				.withUsername(ld.getUsername())
//				.withUserPoolId(USER_POOL_ID);
//		AdminResetUserPasswordResult arupRes = idpClient.adminResetUserPassword(arupReq);

//		idpClient.
		AdminRespondToAuthChallengeRequest artaReq = new AdminRespondToAuthChallengeRequest()
				.withChallengeName("NEW_PASSWORD_REQUIRED")
				.withClientId(APP_ID)
				.withUserPoolId(USER_POOL_ID)
//				.withSession(iaResp.getSession())
				.withChallengeResponses(challengeResponses);
		AdminRespondToAuthChallengeResult artaResp = idpClient.adminRespondToAuthChallenge(artaReq);
		if ( artaResp.getSdkHttpMetadata().getHttpStatusCode() != Status.OK.ordinal() )
		{
			return RestUtil.response(Status.INTERNAL_SERVER_ERROR, "Change PW returned " + 
						artaResp.getSdkHttpMetadata().getHttpStatusCode());
		}
		
		return RestUtil.response(Status.OK, null);
		}
		catch ( Exception ex )
		{
			LOG.error("::changePassword:Exception: " + ex.getMessage());
			ex.printStackTrace();
			return RestUtil.response(Status.INTERNAL_SERVER_ERROR, null);
		}
	}

}
