/*
 * Copyright (C) 2015 Collaboratory
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package io.dockstore.webservice.resources;

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

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.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.codahale.metrics.annotation.Timed;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;

import io.dockstore.webservice.CustomWebApplicationException;
import io.dockstore.webservice.Helper;
import io.dockstore.webservice.core.Container;
import io.dockstore.webservice.core.Group;
import io.dockstore.webservice.core.Token;
import io.dockstore.webservice.core.TokenType;
import io.dockstore.webservice.core.User;
import io.dockstore.webservice.jdbi.ContainerDAO;
import io.dockstore.webservice.jdbi.FileDAO;
import io.dockstore.webservice.jdbi.GroupDAO;
import io.dockstore.webservice.jdbi.TagDAO;
import io.dockstore.webservice.jdbi.TokenDAO;
import io.dockstore.webservice.jdbi.UserDAO;
import io.dropwizard.auth.Auth;
import io.dropwizard.hibernate.UnitOfWork;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;

/**
 *
 * @author xliu
 */
@Path("/users")
@Api("/users")
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {
    private final HttpClient client;
    private final UserDAO userDAO;
    private final GroupDAO groupDAO;
    private final TokenDAO tokenDAO;
    private final ContainerDAO containerDAO;
    private final TagDAO tagDAO;
    private final FileDAO fileDAO;
    private final String bitbucketClientID;
    private final String bitbucketClientSecret;

    private final ObjectMapper objectMapper;

    private static final Logger LOG = LoggerFactory.getLogger(UserResource.class);

    @SuppressWarnings("checkstyle:parameternumber")
    public UserResource(ObjectMapper mapper, HttpClient client, TokenDAO tokenDAO, UserDAO userDAO, GroupDAO groupDAO,
            ContainerDAO containerDAO, TagDAO tagDAO, FileDAO fileDAO, String bitbucketClientID, String bitbucketClientSecret) {
        objectMapper = mapper;
        this.client = client;
        this.userDAO = userDAO;
        this.groupDAO = groupDAO;
        this.tokenDAO = tokenDAO;
        this.containerDAO = containerDAO;
        this.tagDAO = tagDAO;
        this.fileDAO = fileDAO;
        this.bitbucketClientID = bitbucketClientID;
        this.bitbucketClientSecret = bitbucketClientSecret;
    }

    @POST
    @Timed
    @UnitOfWork
    @Path("/groups")
    @ApiOperation(value = "Create user group", response = Group.class)
    public Group createGroup(@ApiParam(hidden = true) @Auth Token authToken, @QueryParam("group_name") String name) {
        Group group = new Group();
        group.setName(name);
        long create = groupDAO.create(group);
        return groupDAO.findById(create);
    }

    @DELETE
    @Timed
    @UnitOfWork
    @Path("/groups/{groupId}")
    @ApiOperation(value = "Deletes a group", response = Response.class)
    @ApiResponses(@ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = "Invalid groupId value"))
    public Response deleteGroup(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam(value = "Group id to delete", required = true) @PathParam("groupId") Long groupId) {
        User user = userDAO.findById(authToken.getUserId());
        Group group = groupDAO.findById(groupId);
        Helper.checkUser(user);

        groupDAO.delete(group);

        group = groupDAO.findById(groupId);
        if (group == null) {
            return Response.ok().build();
        } else {
            return Response.serverError().build();
        }
    }

    @GET
    @Timed
    @UnitOfWork
    @ApiOperation(value = "List all known users", notes = "List all users. Admin only.", response = User.class, responseContainer = "List")
    public List<User> listUsers(@ApiParam(hidden = true) @Auth Token authToken) {
        User user = userDAO.findById(authToken.getUserId());
        Helper.checkUser(user);

        return userDAO.findAll();
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/username/{username}")
    @ApiOperation(value = "Get user", response = User.class)
    public User listUser(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam("Username of user to return") @PathParam("username") String username) {
        User authUser = userDAO.findById(authToken.getUserId());
        User user = userDAO.findByUsername(username);
        Helper.checkUser(authUser, user.getId());

        return user;
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/{userId}")
    @ApiOperation(value = "Get user with id", response = User.class)
    public User getUser(@ApiParam(hidden = true) @Auth Token authToken, @ApiParam("User to return") @PathParam("userId") long userId) {
        User authUser = userDAO.findById(authToken.getUserId());
        Helper.checkUser(authUser, userId);

        User user = userDAO.findById(userId);
        if (user == null) {
            throw new CustomWebApplicationException("User not found.", HttpStatus.SC_BAD_REQUEST);
        }
        return user;
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/{userId}/tokens")
    @ApiOperation(value = "Get tokens with user id", response = Token.class, responseContainer = "List")
    public List<Token> getUserTokens(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam("User to return") @PathParam("userId") long userId) {
        User user = userDAO.findById(authToken.getUserId());
        Helper.checkUser(user, userId);

        return tokenDAO.findByUserId(userId);
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/{userId}/tokens/github.com")
    @ApiOperation(value = "Get Github tokens with user id", response = Token.class)
    public List<Token> getGithubUserTokens(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam("User to return") @PathParam("userId") long userId) {
        User user = userDAO.findById(authToken.getUserId());
        Helper.checkUser(user, userId);

        return tokenDAO.findGithubByUserId(userId);
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/{userId}/tokens/quay.io")
    @ApiOperation(value = "Get Quay tokens with user id", response = Token.class)
    public List<Token> getQuayUserTokens(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam("User to return") @PathParam("userId") long userId) {
        User user = userDAO.findById(authToken.getUserId());
        Helper.checkUser(user, userId);

        return tokenDAO.findQuayByUserId(userId);
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/{userId}/tokens/dockstore")
    @ApiOperation(value = "Get Dockstore tokens with user id", response = Token.class)
    public List<Token> getDockstoreUserTokens(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam("User to return") @PathParam("userId") long userId) {
        User user = userDAO.findById(authToken.getUserId());
        Helper.checkUser(user, userId);

        return tokenDAO.findQuayByUserId(userId);
    }

    @POST
    @Timed
    @UnitOfWork
    @ApiOperation(value = "Add new user", notes = "Register a new user", response = User.class)
    public User registerUser(@QueryParam("username") String username, @QueryParam("is_admin") boolean isAdmin) {
        final Random random = new Random();
        final int bufferLength = 1024;
        final byte[] buffer = new byte[bufferLength];
        random.nextBytes(buffer);
        String randomString = BaseEncoding.base64Url().omitPadding().encode(buffer);
        final String accessToken = Hashing.sha256().hashString(username + randomString, Charsets.UTF_8).toString();

        User user = new User();
        user.setUsername(username);
        user.setIsAdmin(isAdmin);
        long userId = userDAO.create(user);

        Token token = new Token();
        token.setTokenSource(TokenType.DOCKSTORE.toString());
        token.setContent(accessToken);
        token.setUsername(username);
        token.setUserId(userId);
        tokenDAO.create(token);

        return userDAO.findById(userId);
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/{userId}/groups")
    @ApiOperation(value = "Get groups that the user belongs to", response = Group.class, responseContainer = "List")
    public List<Group> getGroupsFromUser(@ApiParam(hidden = true) @Auth Token authToken, @ApiParam("User") @PathParam("userId") long userId) {
        User authUser = userDAO.findById(authToken.getUserId());
        Helper.checkUser(authUser, userId);

        User user = userDAO.findById(userId);
        if (user == null) {
            throw new CustomWebApplicationException("User not found.", HttpStatus.SC_BAD_REQUEST);
        }

        List grouplist = new ArrayList(user.getGroups());
        return grouplist;
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/groups/{groupId}/users")
    @ApiOperation(value = "Get users that belongs to a group", response = User.class, responseContainer = "List")
    public List<User> getUsersFromGroup(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam("Group") @PathParam("groupId") long groupId) {
        Group group = groupDAO.findById(groupId);
        if (group == null) {
            throw new CustomWebApplicationException("Group not found.", HttpStatus.SC_BAD_REQUEST);
        }

        List userlist = new ArrayList(group.getUsers());
        return userlist;

    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/groups")
    @ApiOperation(value = "List all groups", response = Group.class, responseContainer = "List")
    public List<Group> allGroups(@ApiParam(hidden = true) @Auth Token authToken) {
        return groupDAO.findAll();
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/groups/{groupId}")
    @ApiOperation(value = "List a group", response = Group.class)
    public Group getGroup(@ApiParam(hidden = true) @Auth Token authToken, @ApiParam("Group") @PathParam("groupId") long groupId) {
        return groupDAO.findById(groupId);
    }

    @PUT
    @Timed
    @UnitOfWork
    @Path("/{userId}/groups")
    @ApiOperation(value = "Add a group to a user", response = User.class)
    public User addGroupToUser(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam("User ID of user") @PathParam("userId") long userId,
            @ApiParam(value = "RegisterRequest to refresh the list of repos for a user", required = true) Group group) {
        User authUser = userDAO.findById(authToken.getUserId());
        Helper.checkUser(authUser, userId);

        User user = userDAO.findById(userId);
        // Group group = groupDAO.findById(groupId);

        if (user != null && group != null) {
            user.addGroup(group);
        } else {
            LOG.info("user or group is null");
            throw new CustomWebApplicationException("Group and/or user not found.", HttpStatus.SC_BAD_REQUEST);
        }

        return user;

    }

    @DELETE
    @Timed
    @UnitOfWork
    @Path("/{userId}/groups/{groupId}")
    @ApiOperation(value = "Remove a user from a group", response = User.class)
    @ApiResponses(@ApiResponse(code = HttpStatus.SC_BAD_REQUEST, message = "Invalid user or group value"))
    public User removeUserFromGroup(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam("User ID of user") @PathParam("userId") long userId, @ApiParam("Group ID of group") @PathParam("groupId") long groupId) {
        User authUser = userDAO.findById(authToken.getUserId());
        Helper.checkUser(authUser, userId);

        User user = userDAO.findById(userId);
        Group group = groupDAO.findById(groupId);

        if (user != null && group != null) {
            user.removeGroup(group);
        } else {
            LOG.info("user or group is null");
            throw new CustomWebApplicationException("Group and/or user not found.", HttpStatus.SC_BAD_REQUEST);
        }
        return user;
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/{userId}/containers/registered")
    @ApiOperation(value = "List all registered containers from a user", notes = "Get user's registered containers only", response = Container.class, responseContainer = "List")
    public List<Container> userRegisteredContainers(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam(value = "User ID", required = true) @PathParam("userId") Long userId) {
        User user = userDAO.findById(authToken.getUserId());
        Helper.checkUser(user, userId);

        List<Container> repositories = new ArrayList(user.getContainers());

        for (Iterator<Container> iterator = repositories.iterator(); iterator.hasNext();) {
            Container c = iterator.next();

            if (!c.getIsRegistered()) {
                iterator.remove();
            }
        }

        return repositories;
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/{userId}/containers/refresh")
    @ApiOperation(value = "Refresh repos owned by the logged-in user", notes = "Updates some metadata", response = Container.class, responseContainer = "List")
    @SuppressWarnings("checkstyle:methodlength")
    public List<Container> refresh(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam(value = "User ID", required = true) @PathParam("userId") Long userId) {

        User authUser = userDAO.findById(authToken.getUserId());
        Helper.checkUser(authUser, userId);

        List<Token> tokens = tokenDAO.findBitbucketByUserId(userId);

        if (!tokens.isEmpty()) {
            Token bitbucketToken = tokens.get(0);
            Helper.refreshBitbucketToken(bitbucketToken, client, tokenDAO, bitbucketClientID, bitbucketClientSecret);
        }

        List<Container> containers = Helper.refresh(userId, client, objectMapper, userDAO, containerDAO, tokenDAO, tagDAO, fileDAO);
        return containers;
    }

    @GET
    @Path("/{userId}/containers")
    @Timed
    @UnitOfWork
    @ApiOperation(value = "List repos owned by the logged-in user", notes = "Lists all registered and unregistered containers owned by the user", response = Container.class, responseContainer = "List")
    public List<Container> userContainers(@ApiParam(hidden = true) @Auth Token token,
            @ApiParam(value = "User ID", required = true) @PathParam("userId") Long userId) {
        User user = userDAO.findById(token.getUserId());
        Helper.checkUser(user, userId);

        List<Container> ownedContainers = new ArrayList(user.getContainers());
        return ownedContainers;
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/user")
    @ApiOperation(value = "Get the logged-in user", response = User.class)
    public User getUser(@ApiParam(hidden = true) @Auth Token authToken) {
        User user = userDAO.findById(authToken.getUserId());
        return user;
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/organizations")
    @ApiOperation(value = "Get user's organizations", notes = "For testing purposes. Returns the list of organizations from user's Quay.io account", response = ArrayList.class, responseContainer = "List", hidden = true)
    public ArrayList getOrganizations(@ApiParam(hidden = true) @Auth Token authToken) {
        User authUser = userDAO.findById(authToken.getUserId());
        // Helper.checkUser(authUser);

        List<Token> tokens = tokenDAO.findQuayByUserId(authUser.getId());
        Token token;
        if (tokens.isEmpty()) {
            throw new CustomWebApplicationException("Quay.io token not found.", HttpStatus.SC_INTERNAL_SERVER_ERROR);
        } else {
            token = tokens.get(0);
        }

        String url = "https://quay.io/api/v1/user/";
        Optional<String> asString = ResourceUtilities.asString(url, token.getContent(), client);
        if (asString.isPresent()) {
            String response = asString.get();
            LOG.info("RESOURCE CALL: {}", url);

            Gson gson = new Gson();
            // Map<String, String> map = new HashMap<>();
            // map = (Map<String, String>) gson.fromJson(response, map.getClass());
            //
            // String username = map.get("username");
            // LOG.info(username);

            Map<String, ArrayList> map2 = new HashMap<>();
            map2 = (Map<String, ArrayList>) gson.fromJson(response, map2.getClass());
            ArrayList organizations = map2.get("organizations");

            return organizations;
        }
        return null;
    }

    @GET
    @Timed
    @UnitOfWork
    @Path("/{userId}/bitbucketUser")
    @ApiOperation(value = "Test bitbucket", notes = "NO authentication", response = String.class)
    public String getBitbucketUser(@ApiParam(hidden = true) @Auth Token authToken,
            @ApiParam(value = "User ID", required = true) @PathParam("userId") Long userId) {
        User user = userDAO.findById(authToken.getUserId());
        Helper.checkUser(user, userId);

        List<Token> tokens = tokenDAO.findBySource(TokenType.BITBUCKET_ORG.toString());

        Token bitbucketToken;

        if (tokens.isEmpty()) {
            LOG.info("BITBUCKET token not found!");
            throw new CustomWebApplicationException("Bitbucket token not found", HttpStatus.SC_CONFLICT);
        } else {
            bitbucketToken = tokens.get(0);
        }

        String json = "";

        String url = "https://bitbucket.org/api/2.0/users/victoroicr";
        Optional<String> asString = ResourceUtilities.asString(url, bitbucketToken.getContent(), client);

        if (asString.isPresent()) {
            json = asString.get();
        }

        return json;
    }
}
