/*
 * Copyright (C) 2016 Douglas Wurtele
 *
 * 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 org.wurtele.ArmyTracker.rest.services;

import java.util.List;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpSession;
import javax.transaction.Transactional;
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.MediaType;
import javax.ws.rs.core.Response;
import org.jboss.logging.Logger;
import org.wurtele.ArmyTracker.annotations.Restrict;
import org.wurtele.ArmyTracker.entities.User;
import org.wurtele.ArmyTracker.entities.UserRole;
import org.wurtele.ArmyTracker.models.Password;
import org.wurtele.ArmyTracker.models.enumerations.Rank;
import org.wurtele.ArmyTracker.models.enumerations.Role;
import org.wurtele.ArmyTracker.rest.ResponseMapBuilder;
import org.wurtele.ArmyTracker.rest.RestService;
import org.wurtele.ArmyTracker.rest.params.CreateUserParams;
import org.wurtele.ArmyTracker.rest.params.SaveUserParams;

/**
 *
 * @author Douglas Wurtele
 */
@Path("/users")
public class UsersService extends RestService {
	
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Transactional
	public Response listUsers() {
		try {
			this.logActivity(this.getUser(), null);
			List<User> users = em.createQuery("select distinct user from User as user left outer join fetch user.roles as roles", User.class).getResultList();
			return Response.ok(ResponseMapBuilder.buildSuccessMap(new String[]{"users"}, new Object[]{users})).build();
		} catch (Exception e) {
			return this.serverErrorResponse("Failed to load users list", e);
		}
	}
	
	@POST
	@Path("/save")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	@Restrict(Role.ADMIN)
	@Transactional
	public Response saveUser(SaveUserParams params) {
		try {
			this.logActivity(this.getUser(), params);
			User edit = em.find(User.class, params.getId());
			if (edit == null)
				return Response.ok(ResponseMapBuilder.buildErrorMap("User not found: " + params.getId(), null)).build();
			em.createQuery("delete from UserRole where pk.user = :user").setParameter("user", edit.getId()).executeUpdate();
			for (String r : params.getRoles()) {
				Role role = Role.valueOf(r);
				UserRole ur = new UserRole(edit, role);
				em.persist(ur);
				edit.getRoles().add(ur);
			}
			return Response.ok(ResponseMapBuilder.buildSuccessMap(null)).build();
		} catch (Exception e) {
			return this.serverErrorResponse("Failed to save user", e);
		}
	}
	
	@PUT
	@Path("/create")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	@Restrict(Role.ADMIN)
	@Transactional
	public Response createUser(CreateUserParams params) {
		try {
			this.logActivity(this.getUser(), params);
			if (em.find(User.class, params.getId()) != null)
				return Response.ok(ResponseMapBuilder.buildErrorMap("User already exists: " + params.getId(), null)).build();
			User create = new User();
			create.setId(params.getId());
			create.setRank(Rank.valueOf(params.getRank()));
			create.setFirstName(params.getFirstName());
			if (params.getMiddleInitial() != null)
				create.setMiddleInitial(params.getMiddleInitial().toCharArray()[0]);
			create.setLastName(params.getLastName());
			
			Password password = Password.generateRandom();
			create.setPassword(password.getEncrypted());
			create.setPasswordReset(true);
			em.persist(create);
			
			for (String r : params.getRoles()) {
				Role role = Role.valueOf(r);
				UserRole ur = new UserRole(create, role);
				em.persist(ur);
				create.getRoles().add(ur);
			}
			
			//TODO: send new user email
			
			return Response.ok(ResponseMapBuilder.buildSuccessMap(new String[]{"user"}, new Object[]{create})).build();
		} catch (Exception e) {
			return this.serverErrorResponse("Failed to create user", e);
		}
	}
	
	@DELETE
	@Path("/delete/{id}")
	@Consumes(MediaType.TEXT_PLAIN)
	@Produces(MediaType.APPLICATION_JSON)
	@Restrict(Role.ADMIN)
	@Transactional
	public Response deleteUser(@PathParam("id") String id) {
		try {
			User user = this.getUser();
			this.logActivity(user, id);
			final User delete = em.find(User.class, id);
			if (delete == null)
				return Response.ok(ResponseMapBuilder.buildErrorMap("User not found: " + id, null)).build();
			em.remove(delete);
			new Thread(() -> {
				try {
					utx.begin();
					EntityManager em = emf.createEntityManager();
					em.createQuery("delete from UserData where user = :user").setParameter("user", delete).executeUpdate();
					em.createQuery("delete from UserRole where pk.user = :user").setParameter("user", delete.getId()).executeUpdate();
					em.createQuery("delete from ActivityLog where pk.userId = :user").setParameter("user", delete.getId()).executeUpdate();
					utx.commit();
				}catch (Exception e) {
					Logger.getLogger(AdminService.class).error("Failed to clean database after removing " + delete.getId(), e);
				}
			}).start();
			if (delete.equals(user)) {
				HttpSession session = this.getSession();
				session.setAttribute("user", null);
				session.invalidate();
			}
			return Response.ok(ResponseMapBuilder.buildSuccessMap(null)).build();
		} catch (Exception e) {
			return this.serverErrorResponse("Failed to delete user", e);
		}
	}
	
}
