/****************************************************************************
* Copyright Avaya Inc., All Rights Reserved.
* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF Avaya Inc.
* The copyright notice above does not evidence any actual or intended publication of such source code.
* Some third-party source code components may have been modified from their original versions by Avaya Inc.
* The modifications are Copyright Avaya Inc., All Rights Reserved.
* Avaya Confidential & Restricted. May not be distributed further without written permission of
* the Avaya owner.
****************************************************************************/
package com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.resource;

import static com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.util.Feature.CREATE_STANDARD;
import static com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.util.Feature.DELETE_STANDARD;
import static com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.util.Feature.LISTALL_STANDARD;
import static com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.util.Feature.LIST_STANDARD;
import static com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.util.Feature.UPDATE_STANDARD;
import static com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.validator.ValidatorProvider.CREATE_REQUEST_VALIDATOR;
import static com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.validator.ValidatorProvider.DELETE_REQUEST_VALIDATOR;
import static com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.validator.ValidatorProvider.UPDATE_REQUEST_VALIDATOR;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
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.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.lang3.StringUtils;

import com.avaya.collaboration.authorization.AuthorizationHelperException;
import com.avaya.collaboration.authorization.resource.AuthorizationData;
import com.avaya.collaboration.authorization.resource.AuthorizationResourceHelper;
import com.avaya.collaboration.authorization.resource.AuthorizationScope;
import com.avaya.collaboration.util.logger.Logger;
import com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.exception.InvalidRequestException;
import com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.holder.TaskHolder;
import com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.json.ResponseJson;
import com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.json.Task;
import com.avaya.zephyr.services.sample_services.Authorization.TaskRepository.util.RequestData;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
 * This is the entry point for the REST requests from Authorization Client
 * applications.
 *
 */
@Path("/sample")
public final class TaskResource
{
    private static final String BEARER_TOKEN = "bearerToken";
    private final TaskHolder taskHolder;
    private final Gson gson;
    private final Logger logger;

    public TaskResource()
    {
        this(TaskHolder.getInstance(), new GsonBuilder().setPrettyPrinting().create(),
                Logger.getLogger(TaskResource.class));
    }

    TaskResource(final TaskHolder taskHolder, final Gson gson, final Logger logger)
    {
        this.taskHolder = taskHolder;
        this.gson = gson;
        this.logger = logger;
    }

    /*
     * Returns tasks of all users. This will serve requests which may or may not
     * have a bearer token. For requests with bearer token, the client should
     * have "listall" feature with value "standard" granted to it. For requests
     * without bearer token, it will not do any authorization check. This type
     * of requests contain "X-Av-HTTP-Security-Policy" header value as "passed"
     * or "com.avaya.authorization.enableTokenlessAccess" attribute value is set
     * to true.
     */
    @Path("/tasks")
    @GET
    @Produces(
    { MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN })
    public Response getTasks(@Context final HttpServletRequest request)
    {
        final String bearerToken = (String) request.getAttribute(BEARER_TOKEN);
        if (StringUtils.isEmpty(bearerToken))
        {
            return Response.ok(gson.toJson(new ResponseJson(null, taskHolder.getAllTasks())),
                    MediaType.APPLICATION_JSON).build();
        }
        try
        {
            if (isAuthorized(bearerToken, LISTALL_STANDARD.getFeatureName(),
                    LISTALL_STANDARD.getFeatureValue()))
            {
                return Response.ok(gson.toJson(new ResponseJson(null, taskHolder.getAllTasks())),
                        MediaType.APPLICATION_JSON).build();
            }
        }
        catch (final AuthorizationHelperException e)
        {
            logger.error("TaskResource getTasks : Error while fetching AuthorizationData", e);
            return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
                    .entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
        }
        logger.warn("TaskResource getTasks : Not authorized to access resource");
        return Response.status(HttpServletResponse.SC_FORBIDDEN)
                .entity("Not authorized to access resource")
                .type(MediaType.TEXT_PLAIN).build();
    }

    /*
     * Returns tasks related to a particular user. The client accessing this
     * end-point should have "list" feature with value "standard" granted to it.
     * Also the subject of AuthorizationData should be same as the "user-id"
     * path parameter.
     */
    @Path("/tasks/{userId}")
    @GET
    @Produces(
    { MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN })
    public Response getTaskByUserId(@Context final HttpServletRequest request,
            @PathParam("userId") final String userId)
    {
        final String bearerToken = (String) request.getAttribute(BEARER_TOKEN);
        try
        {
            if (StringUtils.isEmpty(bearerToken) ||
                    !isAuthorized(bearerToken, LIST_STANDARD.getFeatureName(),
                            LIST_STANDARD.getFeatureValue()))
            {
                logger.warn("TaskResource getTaskByUserId : Not authorized to read task");
                return Response.status(HttpServletResponse.SC_FORBIDDEN)
                        .entity("Not authorized to read task").type(MediaType.TEXT_PLAIN).build();
            }
            final AuthorizationData authorizationData =
                    AuthorizationResourceHelper.getAuthorizationData(bearerToken);
            if (authorizationData.getSubject().equals(userId))
            {
                return Response
                        .ok(gson.toJson(taskHolder.getTasksByUserId(userId)), MediaType.APPLICATION_JSON)
                        .build();
            }
            else
            {
                logger.warn("TaskResource getTaskByUserId : Cannot read task for a different/empty user");
                return Response.status(HttpServletResponse.SC_FORBIDDEN)
                        .entity("Cannot read task for a different/empty user").type(MediaType.TEXT_PLAIN)
                        .build();
            }
        }
        catch (final AuthorizationHelperException e)
        {
            logger.error("TaskResource getTaskByUserId : Error while fetching AuthorizationData", e);
            return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
                    .entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
        }
    }

    /*
     * Creates task for a particular user. The client accessing this end-point
     * should have "create" feature with value "standard" granted to it. Also
     * the subject of AuthorizationData should be same as the "user-id" path
     * parameter.
     */
    @Path("/tasks/{userId}")
    @POST
    @Produces(
    { MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON })
    @Consumes(MediaType.APPLICATION_JSON)
    public Response create(@Context final HttpServletRequest request,
            @PathParam("userId") final String userId, final Task inputTask)
    {
        final String bearerToken = (String) request.getAttribute(BEARER_TOKEN);
        try
        {
            CREATE_REQUEST_VALIDATOR.getValidator()
                    .validate(new RequestData(bearerToken, userId, null, inputTask));
            if (!isAuthorized(bearerToken, CREATE_STANDARD.getFeatureName(),
                    CREATE_STANDARD.getFeatureValue()))
            {
                logger.warn("TaskResource create : Not authorized to create task");
                return Response.status(HttpServletResponse.SC_FORBIDDEN)
                        .entity("Not authorized to create task").type(MediaType.TEXT_PLAIN).build();
            }
            final AuthorizationData authorizationData =
                    AuthorizationResourceHelper.getAuthorizationData(bearerToken);
            if (authorizationData.getSubject().equals(userId))
            {
                final String title = inputTask.getTitle();
                final String description = inputTask.getDescription();
                if (StringUtils.isEmpty(title))
                {
                    logger.warn("TaskResource create : Task title cannot be empty");
                    return Response.status(HttpServletResponse.SC_BAD_REQUEST)
                            .entity("Task title cannot be empty").type(MediaType.TEXT_PLAIN).build();
                }
                final DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
                final Task task = new Task(Long.toString(taskHolder.getNextTaskId()),
                        userId, title, description, false, dateFormat.format(new Date()));
                taskHolder.addTask(task);
                if (logger.isFinestEnabled())
                {
                    logger.finest("TaskResource create : Task created");
                }
                return Response.ok(gson.toJson(task), MediaType.APPLICATION_JSON)
                        .build();
            }
            else
            {
                logger.warn("TaskResource create : Cannot create task for a different/empty user");
                return Response.status(HttpServletResponse.SC_FORBIDDEN)
                        .entity("Cannot create task for a different/empty user").type(MediaType.TEXT_PLAIN)
                        .build();
            }
        }
        catch (final InvalidRequestException e)
        {
            logger.error("TaskResource create : InvalidRequestException", e);
            return Response.status(e.getHttpErrorCode())
                    .entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
        }
        catch (final AuthorizationHelperException e)
        {
            logger.error("TaskResource create : Error while fetching AuthorizationData", e);
            return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
                    .entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
        }
    }

    /*
     * Deletes task related to a particular user. The client accessing this
     * end-point should have "delete" feature with value "standard" granted to
     * it. Also the subject of AuthorizationData should be same as the "user-id"
     * path parameter.
     */
    @Path("/tasks/{userId}/task/{taskId}")
    @DELETE
    @Produces(
    { MediaType.TEXT_PLAIN })
    public Response delete(@Context final HttpServletRequest request,
            @PathParam("userId") final String userId,
            @PathParam("taskId") final String taskId)
    {
        final String bearerToken = (String) request.getAttribute(BEARER_TOKEN);
        try
        {
            DELETE_REQUEST_VALIDATOR.getValidator()
                    .validate(new RequestData(bearerToken, userId, taskId, null));
            if (isAuthorized(bearerToken, DELETE_STANDARD.getFeatureName(),
                    DELETE_STANDARD.getFeatureValue()) &&
                    AuthorizationResourceHelper.getAuthorizationData(bearerToken).getSubject().equals(userId))
            {
                if (!taskHolder.deleteTask(userId, taskId))
                {
                    return Response.status(HttpServletResponse.SC_FORBIDDEN)
                            .entity("No task found to delete").type(MediaType.TEXT_PLAIN).build();
                }
            }
            else
            {
                return Response.status(HttpServletResponse.SC_FORBIDDEN)
                        .entity("Not authorizaed to delete task").type(MediaType.TEXT_PLAIN).build();
            }
        }
        catch (final InvalidRequestException e)
        {
            logger.error("TaskResource delete : InvalidRequestException", e);
            return Response.status(e.getHttpErrorCode())
                    .entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
        }
        catch (final AuthorizationHelperException e)
        {
            logger.error("TaskResource delete : Error while fetching AuthorizationData", e);
            return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
                    .entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
        }
        return Response.status(HttpServletResponse.SC_OK)
                .entity("Task has been deleted").type(MediaType.TEXT_PLAIN).build();
    }

    /*
     * Updates task related to a particular user. The client accessing this
     * end-point should have "update" feature with value "standard" granted to
     * it. Also the subject of AuthorizationData should be same as the "user-id"
     * path parameter.
     */
    @Path("/tasks/{userId}/task/{taskId}")
    @PUT
    @Produces(
    { MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON })
    @Consumes(MediaType.APPLICATION_JSON)
    public Response update(@Context final HttpServletRequest request,
            @PathParam("userId") final String userId,
            @PathParam("taskId") final String taskId, final Task inputTask)
    {
        final String bearerToken = (String) request.getAttribute(BEARER_TOKEN);
        try
        {
            UPDATE_REQUEST_VALIDATOR.getValidator()
                    .validate(new RequestData(bearerToken, userId, taskId, inputTask));
            if (isAuthorized(bearerToken, UPDATE_STANDARD.getFeatureName(),
                    UPDATE_STANDARD.getFeatureValue()) &&
                    AuthorizationResourceHelper.getAuthorizationData(bearerToken).getSubject().equals(userId))
            {
                final Task updatedTask = taskHolder.updateTask(inputTask);
                if (updatedTask != null)
                {
                    return Response.ok(gson.toJson(updatedTask), MediaType.APPLICATION_JSON)
                            .build();
                }
            }
            else
            {
                return Response.status(HttpServletResponse.SC_FORBIDDEN)
                        .entity("Not authorizaed to update task").type(MediaType.TEXT_PLAIN).build();
            }
        }
        catch (final InvalidRequestException e)
        {
            logger.error("TaskResource update : InvalidRequestException", e);
            return Response.status(e.getHttpErrorCode())
                    .entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
        }
        catch (final AuthorizationHelperException e)
        {
            logger.error("TaskResource update : Error while fetching AuthorizationData", e);
            return Response.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
                    .entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build();
        }
        return Response.status(HttpServletResponse.SC_FORBIDDEN)
                .entity("No task found for update").type(MediaType.TEXT_PLAIN).build();
    }

    /*
     * Authorizes access to resource end-points. It first checks whether the
     * needed scope name presents in the client scope list or not. Then it
     * compares the feature value, and returns true if authorization succeeds
     * else returns false.
     */
    private boolean isAuthorized(final String bearerToken, final String featureName,
            final String featureValue) throws AuthorizationHelperException
    {
        final AuthorizationData authorizationData =
                AuthorizationResourceHelper.getAuthorizationData(bearerToken);
        final List<AuthorizationScope> clientScopeList = authorizationData.getClientScopeList();
        for (final AuthorizationScope scope : clientScopeList)
        {
            if (scope.getFeatureName().equals(featureName))
            {
                for (final String value : scope.getFeatureValues())
                {
                    if (value.equals(featureValue))
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}
