package it.enea.cruise.controller;

import java.io.IOException;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.mail.MessagingException;

import it.enea.scp.idp.sdk.model.UserDto;
import it.enea.cruise.service.idp.IdpProvider;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import org.json.JSONObject;

import org.springframework.util.StringUtils;

import freemarker.template.TemplateException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.ApiResponse;
import it.enea.cruise.dto.CommunityDTO;
import it.enea.cruise.dto.LocalUserDTO;
import it.enea.cruise.model.user.Community;
import it.enea.cruise.model.user.Device;
import it.enea.cruise.model.user.Role;
import it.enea.cruise.model.user.User;
import it.enea.cruise.model.user.UserCommunity;
import it.enea.cruise.model.user.UserRole;
import it.enea.cruise.model.user.UserToken;
import it.enea.cruise.service.structure.TableMetadataInterface;
import it.enea.cruise.repositories.CommunityRepository;
import it.enea.cruise.repositories.UserRepository;
import it.enea.cruise.repositories.DTOManager;
import it.enea.cruise.repositories.RoleRepository;
import it.enea.cruise.repositories.UserCommunityRepository;
import it.enea.cruise.repositories.UserRoleRepository;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;


/**
 * User controller for exposing REST API calls This class manages all the
 * methods for managing both users and communities in the Energy Community
 * environment.
 * 
 * It connects directly to User and Community entities and to the Identiy
 * Provider tools developed by ENEA.
 *
 * @author Gianluca D'Agosta
 * @version 1.0
 * @since 2021-05-12
 */

@RestController
@Api(value = "UserController")
@Transactional("userTransactionManagerIDP")
@Log4j2
public class UserController {

	@Value("${testingPhase}")
	private Boolean testingPhase;

	@Autowired
	UserRoleRepository userRoleInterface;

	@Autowired
	UserRepository userInterface;

	@Autowired
	CommunityRepository communityInterface;

	@Autowired
	UserCommunityRepository userCommunityInterface;

	@Autowired
	RoleRepository roleInterface;

	@Autowired
	TableMetadataInterface tmInterface;

	@Autowired
	IdpProvider idpClientManager;

	@Autowired
	DTOManager dTOManager;
	
	@Autowired
	MailController jmc;

	@Value("${communityRoleAdmin}")
	private String communityRoleAdmin;

	@Value("${oracleRoleAdmin}")
	private String oracleRoleAdmin;

	@Value("${platformRoleAdmin}")
	private String platformRoleAdmin;
	
	@Value("${stakeHolderRole}")
	private String stakeHolderRole;
	
	@Value("${communityMemberRole}")
	private String communityMemberRole;
	
	@Value("${idp.clientSecret}")
	private String secret;

	List<User> users = new ArrayList<User>();

	/**
	 * Returns the list of the users in the DB This method will be removed soon
	 * because the lackness of secutiry. In fact, it does not evaluate the role of
	 * the requester and does not select the community the users belong to.
	 * 
	 * @param: null
	 * @exception: Return an Internal Server error in case of exception
	 * 
	 */
	@Deprecated
	@GetMapping("/ListAllUsers")
	public ResponseEntity<List<User>> getAllUsers() {
		users = new ArrayList<User>();
		try {
			userInterface.findAll().forEach(users::add);
		} catch (Exception e) {
			return new ResponseEntity(e.getLocalizedMessage(),
					// headers,
					HttpStatus.INTERNAL_SERVER_ERROR);
		}
		return new ResponseEntity<List<User>>(users, HttpStatus.OK);
	}

	
	/**
	 * Returns the list of the users in the DB This method will be removed soon
	 * because the lackness of secutiry. In fact, it does not evaluate the role of
	 * the requester and does not select the community the users belong to.
	 * 
	 * @param: null
	 * @exception: Return an Internal Server error in case of exception
	 * 
	 */
	
	@GetMapping("/community/listAllCommunities")
	public ResponseEntity<List<CommunityDTO>> getAllCommunities() {
		List<CommunityDTO> communities = new ArrayList<CommunityDTO>();
		try {
			List<Community> communitiesTemp = communityInterface.findAll();
			for (Community c : communitiesTemp) {
				CommunityDTO cDTO = dTOManager.communityDTO(c);
				communities.add(cDTO);
			}
		} catch (Exception e) {
			return new ResponseEntity(e.getLocalizedMessage(),
					// headers,
					HttpStatus.INTERNAL_SERVER_ERROR);
		}
		return new ResponseEntity<List<CommunityDTO>>(communities, HttpStatus.OK);
	}

	
	/**
	 * Creates or updates a user from a JWT valid token This method will be removed
	 * because it uses the KeyCloak identity provided. At the moment, it is
	 * maintained for compatibility purposes.
	 * 
	 * @params: String token
	 * @return: The user if the creation or update is successful
	 * @return: Forbidden if the token is not valid
	 * @exception: Return an Internal Server error in case of exception
	 * 
	 */

	/*@ApiOperation(value = "Create or update user from KEYCLOAK token", response = User.class, tags = "")
	@PostMapping("/userFromToken")
	@Deprecated
	public ResponseEntity<User> manageUserFromToken(
			@ApiParam(name = "token", type = "String", value = "JWT Token of the user", example = "", required = true) @RequestParam("token") String token) {

		User userTemp = new User();
		User userTempFromDB = new User();

		User resultUser = new User();

		try {
			*//*
			 * Check if the token is valid connecting to remote keycloak provider.
			 *//*
			Boolean isValid = false; 
			*//*
			 * Remove the usage of the keycloak manager for this application.
			 * And returns always an error on interrogating this function
			 *//*
			//km.isValidToken(token);

			if (isValid || testingPhase) {
				userTemp = JWTManager.jwt2User(token);

				userTempFromDB = userInterface.findBySubject(userTemp.getSubject());
				Community community = communityInterface.findByCommunityid(userTemp.getTmpCommunity());

				if (userTempFromDB == null) {
					*//*
					 * The user does not exist on the DB but the token is valid. => user has to be
					 * created on the DB
					 *//*
					for (String localRole : userTemp.getKeycloakRoles()) {
						Role dbRole = roleInterface.findByRolename(localRole);
						if (dbRole.getId() > 0) {
							Role role = roleInterface.getById(dbRole.getId());
							UserRole ur = new UserRole();
							ur.setUser(userTemp);
							ur.setRole(role);
							ur.setCommunity(community);
							// userTemp.addUserRole(ur);
						}
					}
					UserCommunity uc = new UserCommunity();
					uc.setCommunity(community);
					uc.setUser(userTemp);
					uc.setWallet(userTemp.getTmpWallet());
					userTemp.setSingleUsercommunity(uc);

					userInterface.save(userTemp);
					resultUser = userInterface.findBySubject(userTemp.getSubject());
				} else {

					userTempFromDB = userTempFromDB.merge(userTemp);
					// IMposto la comunita'
					UserCommunity uc1 = new UserCommunity();
					uc1.setCommunity(community);
					uc1.setUser(userTempFromDB);
					uc1.setWallet(userTempFromDB.getTmpWallet());
					userTempFromDB.setSingleUsercommunity(uc1);

					for (String localRole : userTemp.getKeycloakRoles()) {
						Role dbRole = roleInterface.findByRolename(localRole);
						if (dbRole.getId() > 0) {
							// userTempFromDB.addUserRoleInCommunity(dbRole, community);
						}
					}

					userInterface.save(userTempFromDB);
					resultUser = userInterface.findBySubject(userTempFromDB.getSubject());
				}

			} else {
				return new ResponseEntity("Provided token is not valid ",
						// headers,
						HttpStatus.FORBIDDEN);
			}
		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
			return new ResponseEntity("Internal error",
					// headers,
					HttpStatus.INTERNAL_SERVER_ERROR);
		}
		return new ResponseEntity<User>(resultUser,
				// headers,
				HttpStatus.CREATED);

	}
*/
	@ApiOperation(value = "Return a list of users from their email", response = User.class, tags = "")
	@GetMapping("/userFromEmail")
	public ResponseEntity<UserDto> getUserDTOFromEmail(
			@ApiParam(name = "email", type = "String", value = "Subject string", example = "", required = true) @RequestParam("email") String email) {
	
	UserDto user = idpClientManager.getUserByEmail(email);
	return new ResponseEntity<UserDto>(user,
			// headers,
			HttpStatus.CREATED);
	}
	
	
	/**
	 * Returns a user from the Subject code At the moment, it just extact
	 * information about the user from the subject. It is not connected to the
	 * Identity Provider, at the moment, so the ressult contains only a userid and a
	 * subject.
	 * 
	 * @param: String subject code
	 * @return: The user if found
	 * @return: NotFound if the user does not exist on DB
	 * 
	 */
	@ApiOperation(value = "Return a list of users from their subject code", response = User.class, tags = "")
	@GetMapping("/userFromSubject")
	public ResponseEntity<LocalUserDTO> getUserDTOFromSubject(
			@ApiParam(name = "subject", type = "String", value = "Subject string", example = "", required = true) @RequestParam("subject") String subject) {
		User user;
		LocalUserDTO lUserDTO = new LocalUserDTO();
		try {
			user = userInterface.findBySubject(subject);
			//String email = encryptionManager.decrypt(user.getSubject());
			UserDto userDto = idpClientManager.getUserByEmail(user.getSubject());
			user.mergeWithDto(userDto);

			lUserDTO = dTOManager.userDTO(user);

		} catch (Exception e) {
			log.info(e.getLocalizedMessage());
			return new ResponseEntity("User does not exist in DB",
					// headers,
					HttpStatus.NOT_FOUND);

		}
		return new ResponseEntity<LocalUserDTO>(lUserDTO,
				// headers,
				HttpStatus.FOUND);
	}

	/**
	 * Register a user from the simple Registration form
	 * 
	 * @params: the User representation coming from the registration form
	 * @return: The user if created
	 * @return: Conflict if the user already exists on DB or on KeyCloak
	 * 
	 */
	// @ApiOperation(value = "Register a user from form data", response =
	// User.class, tags = "")
	
	@PostMapping("/user/remoteLogin")
	public ResponseEntity<String> remoteLogin(
			@RequestParam String token ){
		
			System.out.println(token.toString());
		
		return new ResponseEntity<String>(token.toString(), HttpStatus.OK);
	}
	
	@GetMapping("/user/remoteLogin")
	public ResponseEntity<String> getRemoteLogin(
			@RequestParam String token){
		
			System.out.println(token.toString());
		
		return new ResponseEntity<String>(token.toString(), HttpStatus.OK);
	}
	
	
	@PostMapping("/user/register")
	public ResponseEntity<ResultMessage> registerFromForm(
			@ApiParam(name = "user", type = "User", value = "User representation from theinput form", example = "", required = true) 
			@RequestBody LocalUserDTO user) {
		/*
		 * Controllo che ci sia almeno la mail.
		 */
		if (user.getEmail().isEmpty()) {
			ResultMessage msg = new ResultMessage(false, "The user has not email" );
				return new ResponseEntity<ResultMessage>(msg, HttpStatus.OK);
		}
		
		/*
		 * Controllo se l'utente esiste gia' sui due DB  o almeno su IDP
		 */

		UserDto userIDP = idpClientManager.getUserByEmail(user.getEmail());
		
		if (userIDP == null) {
			/*
			 * L'utente non esiste su IDP e quindi posso procedere alla registrazione
			 * prima su IDP per ottenere il subject
			 */
			
			UserDto uDto = new UserDto();
			uDto.setEmail(user.getEmail());
			uDto.setName(user.getName());
			uDto.setSurname(user.getSurname());
			uDto.setPassword(user.getPassword());
			uDto.setPasswordConfirm(user.getPassword());
			uDto.setEmailConfirmed(true);
			uDto.setRole("Citizen");
			uDto.setOrganization("EnergyCommunity");

			Boolean registerdonIDP = idpClientManager.createUser(uDto);
			if (!registerdonIDP) {
				log.info("Error on registering on IDP");
				ResultMessage msg = new ResultMessage(false, "Error on registering on IDP" );
				return new ResponseEntity<ResultMessage>(msg, HttpStatus.OK);
			}else {
				uDto= idpClientManager.getUserByEmail(uDto.getEmail());
			}
			
			/*
			 * Adesso creo l'utente locale con le informazioni e le comunita'. 
			 */
			
			User tmpUser = new User();
			tmpUser.setSubject(uDto.getUuid());
			user.setSubject(uDto.getUuid());
			// Inserisco le comunita
			for (CommunityDTO cDto : user.getUsercommunities()) {
				Community c = communityInterface.findByCommunityid(cDto.getCommunityid());
				UserCommunity uc1 = new UserCommunity();
				uc1.setCommunity(c);
				uc1.setUser(tmpUser);
				uc1.setWallet(cDto.getWallet());
				uc1.setSubscriptiondate(new Date(System.currentTimeMillis()));
				uc1.setActive(true);
				tmpUser.setSingleUsercommunity(uc1);
				Role tmpRole = roleInterface.getById(new Long(2));
				UserRole ur = new UserRole();
				ur.setCommunity(c);
				ur.setUser(tmpUser);
				ur.setRole(tmpRole);
				tmpUser.setSingleUserRole(ur);
			}

			tmpUser = userInterface.save(tmpUser);
			log.info("user saved, probably... the number of available users is " + userInterface.count());

			user.setUserid(tmpUser.getUserid());

			

			} else {
			log.info("User with email " + user.getEmail() + " already exists");
			ResultMessage msg = new ResultMessage(false, "User with email " + user.getEmail() + " already exists" );
			
			return new ResponseEntity<ResultMessage>(msg, HttpStatus.OK);
		}
		
		//OK. Se sono arrivato qui posso mandare la mail di conferma
		log.info("Sending activation email to " + user.getEmail());
		
	
		
		try {
			jmc.sendMessageUsingFreemarkerTemplate(user);
		} catch (IOException | TemplateException | MessagingException e) {
			log.info("Mailing failed");
			log.info(e.getLocalizedMessage());
		}
		ResultMessage msg = new ResultMessage(true, "User created" );
		return new ResponseEntity<ResultMessage>(msg, HttpStatus.OK);
	}
	
	/**
	 * Returns the number of participants in a a community managed by the
	 * owner of the token
	 * 
	 * @params: The admin's token
	 * @return: The number of participants, if the user is and admin
	 * @return: A forbidden message otherwise
	 * 
	 */
	@ApiOperation(value = "Get the number of users in a community from the administrator's token", response = User.class, tags = "")
	@PostMapping("/getNumberOfParticipants")
	ResponseEntity<?> getNumberOfParticipants(
			@ApiParam(name = "token", type = "String", value = "The administrator's token", example = "", required = true) @RequestParam String token) {
		
		
		User u = new User();
		Boolean isValid = idpClientManager.isValid(token);
		Integer result = 0;
		
		if (isValid) {
			User userdto = idpClientManager.getUserByToken(token);
		
			u = userInterface.findBySubject(userdto.getUuid());
			if (u.hasRole(communityRoleAdmin) || u.hasRole(oracleRoleAdmin) || u.hasRole(platformRoleAdmin)) {
				for (UserCommunity c : u.getUsercommunities()) {
					result = userInterface.countBySubjectIsNotNullAndCommunityId(c.getId().getCommunityId());
				}
			}
		}else {
			return new ResponseEntity<String>("User is not an addmin",
					HttpStatus.FORBIDDEN);
		}
		return new ResponseEntity<Integer> (result, HttpStatus.OK);
	}
	
	
	/**
	 * Returns a user from a wallet if the token owner is an admin for the community
	 * 
	 * @params: The wallet 
	 * @params: the admin's token
	 * @return: The user, if the if the token belongs to an admin
	 * @return: A forbidden message otherwise
	 * 
	 */
	@ApiOperation(value = "Get a user in a community from the wallet(s)", response = User.class, tags = "")
	@PostMapping("/getUserFromWallet")
	ResponseEntity<?> getUserFromWallet(
			@RequestBody List<String> wallets,
			@ApiParam(name = "token", type = "String", value = "The administrator's token", example = "", required = true) @RequestParam String token) 
		 {
		LocalUserDTO lUserDTO = new LocalUserDTO();
		
		User u = new User();
		Boolean isValid = idpClientManager.isValid(token);

		Map<String, List<User>> resultMap = new HashMap<String, List<User>>();
		
		if (isValid) {
			User userdto = idpClientManager.getUserByToken(token);
		
			u = userInterface.findBySubject(userdto.getUuid());
			if (u.hasRole(communityRoleAdmin) || u.hasRole(oracleRoleAdmin) || u.hasRole(platformRoleAdmin)) {
				for (Community c : u.getCommunities()) {
					List<User> result = new ArrayList<User>();
					for (String wallet : wallets) {
						result.addAll(userInterface.findFromWalletAndCommunityId(wallet, c.getCommunityid()));
					}
					if (result.size()> 0) {
						u = result.get(0); //qualcosa non va se ne trovo piu' di uno
						UserDto userdtoNew = idpClientManager.getUserFromUUID(
								u.getSubject());
						u.mergeWithDto(userdtoNew);
						lUserDTO = dTOManager.userDTO(u);
						return new ResponseEntity<LocalUserDTO> (lUserDTO, HttpStatus.OK);
					}
				}
			}
			if (u.hasRole(stakeHolderRole) || u.hasRole(communityMemberRole)) {
				for (Community c : u.getCommunities()) {
					List<User> result = new ArrayList<User>();
					for (String wallet : wallets) {
						result.addAll(userInterface.findFromWalletAndCommunityId(wallet, c.getCommunityid()));
					}
					if (result.size()> 0) {
						u = result.get(0); 
						UserDto userdtoNew = idpClientManager.getUserByEmail(
								u.getSubject());
						u.mergeWithDto(userdtoNew);
						lUserDTO.setEmail(u.getEmail());
						lUserDTO.setPhone(u.getPhone());
						return new ResponseEntity<LocalUserDTO> (lUserDTO, HttpStatus.OK);
					}
				}
			}

			
		}else {
			return new ResponseEntity<String>("User is not an addmin",
					HttpStatus.FORBIDDEN);
		}
		return new ResponseEntity<LocalUserDTO> (lUserDTO, HttpStatus.OK);
	}

	/**
	 * Returns the list of communities the user belongs to It uses the Keycloak
	 * application for controlling the requester's roles. This methd shall be
	 * duplicated for the Identity Provider tool
	 * 
	 * @param: the JWT token of the user
	 * @return: The list of the user communities
	 * @return: Forbidden if the token is not valid
	 * @return: Forbidden if the user is not an administrator
	 * 
	 */
	@ApiOperation(value = "Get all users in a community from the administrator's token", response = User.class, tags = "")
	@PostMapping("/getusercommunities")
	ResponseEntity<List<LocalUserDTO>> getUserCommunities(
			@ApiParam(name = "token", type = "String", value = "The administrator's token", example = "", required = true) @RequestParam String token) {
		
		
		User u = new User();
		List<User> usersInCommunity = new ArrayList<User>();
		List<LocalUserDTO> listOfUsers = new ArrayList<LocalUserDTO>();
		Boolean isValid = idpClientManager.isValid(token);
	

		if (isValid) {
			User userdto = idpClientManager.getUserByToken(token);
	
			u = userInterface.findBySubject(userdto.getUuid());
			if (u.hasRole(communityRoleAdmin) || u.hasRole(oracleRoleAdmin) || u.hasRole(platformRoleAdmin)) {
				for (UserCommunity c : u.getUsercommunities()) {
					usersInCommunity.addAll(userInterface.findBySubjectIsNotNullAndActiveIsTrueAndCommunityId(c.getId().getCommunityId()));
				}
				
				for (User uTmp : usersInCommunity) {
					
					String subejct = uTmp.getSubject();
					log.info("email: " + subejct);
					if(subejct != null && !subejct.isEmpty()) {	
						UserDto tmpUserDto = idpClientManager.getUserByEmail(subejct);
						if (tmpUserDto != null ) {
							uTmp.mergeWithDto(tmpUserDto);
							LocalUserDTO lUserDTO = dTOManager.userDTO(uTmp);
							lUserDTO.setEmail(tmpUserDto.getEmail());
							listOfUsers.add(lUserDTO);
						}
					}
				}	
			
		

			
				
			} else {
				return new ResponseEntity("User is not an addmin",
						HttpStatus.FORBIDDEN);
			}
		} else {
			return new ResponseEntity("Provided token is not valid ", // headers,
					HttpStatus.FORBIDDEN);
		}
		return new ResponseEntity<List<LocalUserDTO>>(listOfUsers, // headers,
				HttpStatus.OK);
	}

	
	@ApiOperation(value = "Generate subject", response = String.class, tags = "")
	@PostMapping("/generateSubject")
	@Deprecated
	ResponseEntity<String> generateSubject(
			@ApiParam(name = "mail", type = "String", value = "The user's email", example = "", required = false) 
			@RequestBody String email){
		//String subject = encryptionManager.encrypt(email);
		
		return new ResponseEntity<String> ("", HttpStatus.OK);
	}
	
	
	@ApiOperation(value = "Generate email", response = String.class, tags = "")
	@PostMapping("/returnEmail")
	@Deprecated
	ResponseEntity<String> generateEmail(
			@ApiParam(name = "Subject", type = "String", value = "The user's subject", example = "", required = false) 
			@RequestBody String subject){
		//String email = encryptionManager.decrypt(subject);
		
		return new ResponseEntity<String> ("", HttpStatus.OK);
	}
	
	
	/**
	 * Returns the list of users if the requester is a platform admin This method
	 * shall be moved for using the Identity Provider tool
	 * 
	 * @param: the JWT token of the user
	 * @return: The list of the users
	 * @return: Forbidden if the token is not valid
	 * @return: Forbidden if the user is not an administrator
	 * 
	 */

	@ApiOperation(value = "Get all users if the requester is a platform admin", response = User.class, tags = "")
	@PostMapping("/getusers")
	ResponseEntity<List<LocalUserDTO>> getUsers(
			@ApiParam(name = "token", type = "String", value = "The administrator's token", example = "", required = false) @RequestParam(required = false) String token,
			@ApiParam(name = "Authorization", type = "String", value = "The administrator's token", example = "", required = false) @RequestHeader(name = "Authorization", required = false) String tokenAuth) {
		List<User> users = new ArrayList<User>();
		List<LocalUserDTO> resultList = new ArrayList<LocalUserDTO>();
		Boolean isValid = false;
		User u = new User();
		User userdto;
		String subject = "";
		if ((token == null || token.length() == 0) && (tokenAuth == null || tokenAuth.length() == 0)) {
			return new ResponseEntity("Token has not been provided", // headers,
					HttpStatus.FORBIDDEN);
		}
		if (token != null) {
			//isValid = km.isValidToken(token);

			//if (isValid) {
				/* Ok, I'm using the keycloak manager */
				//subject = JWTManager.getSubject(token);
			//}
		} else {
			isValid = idpClientManager.isValid(tokenAuth);
			if (isValid) {
				userdto = idpClientManager.getUserByToken(tokenAuth);
				subject = userdto.getUuid();
			} else {
				return new ResponseEntity("Provided token is not valid ", // headers,
						HttpStatus.FORBIDDEN);
			}

			u = userInterface.findBySubject(userdto.getUuid());
			if (u.hasRole(platformRoleAdmin)) {
				users = userInterface.findBySubjectIsNotNull();
				
				for (User uTmp : users) {
					
						String subjectLocal = uTmp.getSubject();
						if(subjectLocal != null && !subjectLocal.isEmpty()) {	
							UserDto tmpUserDto = idpClientManager.getUserByEmail(subjectLocal);
							if (tmpUserDto != null ) {
								uTmp.mergeWithDto(tmpUserDto);
								LocalUserDTO lUserDTO = dTOManager.userDTO(uTmp);
								lUserDTO.setEmail(tmpUserDto.getEmail());
								resultList.add(lUserDTO);
							}
						}
					}	
				
				
				
			} else {
				return new ResponseEntity("User is not a " + platformRoleAdmin, // headers,
						HttpStatus.FORBIDDEN);
			}

		}
		return new ResponseEntity<List<LocalUserDTO>>(resultList, // headers,
				HttpStatus.OK);
	}
		
	// @ApiOperation(value = "Get all users as the requester is a platform admin",
	// response = User.class, tags = "")
	// @GetMapping("/getusers")
	// ResponseEntity<List<User>> getgetUsers(
	// @ApiParam(name = "token", type = "String", value = "The administrator's
	// token", example = "", required = true)
	// @RequestParam String token) {
	// return getUsers(token, "");
	// }

//	/**
//	 * Returns the list of users belonging to a community
//	 * 
//	 * @param: Long: the communityId
//	 * @return: The list of the user in the community
//	 * 
//	 * 
//	 */
//	@PostMapping("/getusercommunitiesFromLong")
//	ResponseEntity<List<User>> getUserCommunitiesFromLong(@RequestParam Long cId) {
//		List<User> usersInCommunity = userInterface.findAllByCommunityId(cId);
//
//		return new ResponseEntity<List<User>>(usersInCommunity, HttpStatus.OK);
//	}

	/**
	 * Delete the user from DB and ID-Provider
	 * 
	 * @param: User: the user to be deleted
	 * @return: True if cancellation is complete
	 */
	@ApiOperation(value = "Delete the user.", response = Boolean.class, tags = "")
	@DeleteMapping("/user/delete")
	@ApiResponses(value = { @ApiResponse(code = 200, message = "Success"),
			@ApiResponse(code = 403, message = "Token is null or not valid"),
			@ApiResponse(code = 500, message = "User cannot delete from DBs"), })
	public ResponseEntity<Boolean> deleteUser(
			@ApiParam(name = "userToken", type = "String", value = "The user's IDP token or email", example = "", required = true) @RequestBody String userToken) {

		// log.info("Deleting user with token = {} ", userToken);
		if (userToken == null || userToken == "") {
			log.info("Input is null ");
			return new ResponseEntity<Boolean>(false, HttpStatus.FORBIDDEN);
		}
		Pattern VALID_EMAIL_ADDRESS_REGEX = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$",
				Pattern.CASE_INSENSITIVE);
		Matcher matcher = VALID_EMAIL_ADDRESS_REGEX.matcher(userToken);
		UserDto userdto = new UserDto();
		Boolean isEmail = false;
		User userDel = new User();
		if (matcher.find()) {
			log.info("Deleting user with email = {} ", userToken);
			userdto = idpClientManager.getUserByEmail(userToken);
			isEmail = true;

		}
		if (idpClientManager.isValid(userToken)) {
			log.info("Deleting user with token = {} ", userToken);
			userDel = idpClientManager.getUserByToken(userToken);
		}
		if (userDel != null) {
			Boolean resultIDPRemove = idpClientManager.deleteUser(userDel.getEmail());

			if (!resultIDPRemove) {
				log.info("Failed to delete user from ID-Provider");
				return new ResponseEntity<Boolean>(false, HttpStatus.INTERNAL_SERVER_ERROR);
			}

			User user = userInterface.findBySubject(userdto.getUuid());

			if (user == null) {
				log.info("Failed to delete user from DB because the user does not exist");
				return new ResponseEntity<Boolean>(false, HttpStatus.INTERNAL_SERVER_ERROR);
			}
			userInterface.delete(user);

		} else if (isEmail) {
			User user = userInterface.findBySubject(userdto.getUuid());
			if (user == null) {
				log.info("Failed to delete user from DB because the user does not exist");
				return new ResponseEntity<Boolean>(false, HttpStatus.INTERNAL_SERVER_ERROR);
			}
			userInterface.delete(user);
		} else {
			log.info("Provided input is not valid or user not present in IDP", userToken);
			return new ResponseEntity<Boolean>(false, HttpStatus.FORBIDDEN);
		}
		return new ResponseEntity<Boolean>(true, HttpStatus.OK);
	}

	/**
	 * Login the user by providing user's details if username and password are
	 * correct.
	 * 
	 * @param: Username: the user's username
	 * @param: Password: the user's password
	 * @return: a full user if both username and password are correct, an empty user
	 *          if not.
	 */

	@ApiOperation(value = "Login the user. Returns Http.OK and User object if username and password are valid, Http.Forbidden otherwise", response = User.class, tags = "")
	@PostMapping("/user/login")
	@ApiResponses(value = { @ApiResponse(code = 200, message = "Success"),
			@ApiResponse(code = 403, message = "Username or password are invalid") })
	ResponseEntity<LocalUserDTO> login(
			@ApiParam(name = "username", type = "String", value = "The username", example = "", required = true) @RequestParam String username,
			@ApiParam(name = "password", type = "String", value = "The password", example = "", required = true) @RequestParam String password) {
		if (username == null || username == "" || password == null || password == "") {
			log.info("Username or password are empty");
			return new ResponseEntity<LocalUserDTO>(new LocalUserDTO(), HttpStatus.FORBIDDEN);
		}

		User userdto = idpClientManager.login(username, password);
		if (userdto == null) {
			log.info("Wrong username or password");
			return new ResponseEntity<LocalUserDTO>(new LocalUserDTO(), HttpStatus.FORBIDDEN);
		}
		
		log.info("UUID: " + userdto.getSubject());
		
		User user = userInterface.findBySubject(userdto.getSubject());
		if (user == null) {
			log.info("Adding into local DB");
			User tempUser = new User();
			tempUser.setSubject(userdto.getSubject());
			tempUser.merge(userdto);
			
			tempUser = userInterface.save(tempUser);
			LocalUserDTO lUserDTO = dTOManager.userDTO(tempUser);
			
			return new ResponseEntity<LocalUserDTO>(lUserDTO, HttpStatus.OK);
		}
		for (Device d : user.getDevices()) {
			log.info(d.getDevicename());
		}

		user.merge(userdto);
		
		LocalUserDTO lUserDTO = dTOManager.userDTO(user);
			log.info(lUserDTO.toString());
		
		return new ResponseEntity<>(lUserDTO, HttpStatus.OK);
	}

	/**
	 * Logout the user using the token
	 * 
	 * @param: User Token
	 * @return: True if the user is correctly logged out, false otherwise
	 */
	@ApiOperation(value = "Logout the user. Returns Http.OK and User object if username and password are valid, Http.Forbidden otherwise", response = Boolean.class, tags = "")
	@PostMapping("/user/logout")
	@ApiResponses(value = { @ApiResponse(code = 200, message = "Success"),
			@ApiResponse(code = 500, message = "Logout has failed for unknown causes") })
	ResponseEntity<Boolean> logout(
			@ApiParam(name = "token", type = "String", value = "The user's token", example = "", required = true) @RequestParam String token,
			@RequestHeader(name = "Authorization", required = false) String tokenAuth) {
		if (token == null || token == "") {
			if (tokenAuth == null || tokenAuth == "") {
				log.info("Token is empty");
				return new ResponseEntity<Boolean>(false, HttpStatus.INTERNAL_SERVER_ERROR);
			} else
				token = tokenAuth;
		}
		Boolean result = idpClientManager.logout(token);

		if (result) {
			return new ResponseEntity<Boolean>(result, HttpStatus.OK);
		} else {
			log.info("Failed to logout");
			return new ResponseEntity<Boolean>(result, HttpStatus.INTERNAL_SERVER_ERROR);
		}

	}

	/**
	 * Check if the token is valid and alive
	 * 
	 * @param: User Token
	 * @return: True if the user is the token is valid, false otherwise
	 */
	@ApiOperation(value = "Check if the token is valid and return a boolean", response = Boolean.class, tags = "")
	@PostMapping("/user/isTokenValid")
	@ApiResponses(value = { @ApiResponse(code = 200, message = "Success"),
			@ApiResponse(code = 403, message = "Token not valid"),
			@ApiResponse(code = 500, message = "Token is null") })
	ResponseEntity<Boolean> checkToken(
			@RequestHeader(name = "Authorization", required = false) String token,
			@ApiParam(name = "token", type = "String", value = "The user's token", example = "", required = false) 
			@RequestParam (name = "token", required = false) String token2 ) {
		token = StringUtils.hasText(token) ? token : token2;
		if (!StringUtils.hasText(token)) {
			log.info("Token is empty");
			return new ResponseEntity<Boolean>(false, HttpStatus.INTERNAL_SERVER_ERROR);
		}
		Boolean result = idpClientManager.isValid(token);

		if (result) {
			return new ResponseEntity<Boolean>(result, HttpStatus.OK);
		} else {
			return new ResponseEntity<Boolean>(result, HttpStatus.FORBIDDEN);
		}

	}

	/**
	 * Returns the list of available roles on the platform
	 * 
	 * @return: A list of Roles
	 */

	@ApiOperation(value = "Return the list of the roles active on the platform", response = Role.class, tags = "")
	@GetMapping("/role/listRoles")
	@ApiResponses(value = { @ApiResponse(code = 200, message = "Success"),
			@ApiResponse(code = 500, message = "Internal error") })
	ResponseEntity<List<Role>> getRoles() {
		List<Role> result = roleInterface.findAll();
		if (result != null) {
			return new ResponseEntity<List<Role>>(result, HttpStatus.OK);
		} else {
			return new ResponseEntity<List<Role>>(result, HttpStatus.INTERNAL_SERVER_ERROR);
		}

	}
	
	User getUserFromJWTToken(String token) {
			User user = new User();
			
			String[] chunks = token.split("\\.");
			Base64.Decoder decoder = Base64.getUrlDecoder();
			String header = new String(decoder.decode(chunks[0]));
			
			JSONObject payloadObj = new JSONObject(new String(decoder.decode(chunks[1])));
			
			UserToken userToken = new UserToken();
			userToken.setValue(token);
			
			user.setUsername(payloadObj.getString("username"));
			user.setName(payloadObj.getString("name"));
			user.setEmail(payloadObj.getString("email"));
			user.setSurname(payloadObj.getString("family_name"));
			user.setSubject(payloadObj.getString("sub"));
						
			return user;
	
	}


	/**
	 * Check if the token is valid and returns the user's details
	 * 
	 * @param: User Token
	 * @return: the user's details if the token is valid, an error otherwise
	 */
	@ApiOperation(value = "Check if the token is valid and return the user", response = Boolean.class, tags = "")
	@ApiResponses(value = { @ApiResponse(code = 200, message = "Success"),
			@ApiResponse(code = 403, message = "Token not valid or access forbidden"),
			@ApiResponse(code = 500, message = "Token is null") })
	@PostMapping("/user/getUserFromToken")
	ResponseEntity<LocalUserDTO> getUserFromToken(
			@ApiParam(name = "token", type = "String", value = "The user's ID-Provider token", example = "", required = true)
			@RequestParam String token
	) {

		if (token == null || token == "") {
			log.info("GetUserFromToken: Token is empty");
			return new ResponseEntity<>(new LocalUserDTO(), HttpStatus.BAD_REQUEST);
		}
		
		Boolean isJWT = checkIsJWTToken(token);
		User userdto = new User();
		if (isJWT == false) {
			userdto = idpClientManager.getUserByToken(token);}
		else {
			//è un token JWT
			userdto = getUserFromJWTToken(token);
		}

		if (userdto == null) {
			log.info("User not found on ID-Provider - Token is not valid");

			return new ResponseEntity<LocalUserDTO>(new LocalUserDTO(), HttpStatus.FORBIDDEN);
		}
		User user = getUserFromSubject(userdto.getUuid());
		if (user == null) {
			
			log.info("User not found on local DB");
			/*@TO-DO
			* L'utente e' registrato nell'identity provider e quindi dovrei abilitarlo anche localmente, mandandolo pero' nella pagina per 
			* completare la registrazione.
			*/
			
			LocalUserDTO lUDto = dTOManager.userDTO(userdto);
			lUDto.setNewUser(true);
			
			return new ResponseEntity<LocalUserDTO>(lUDto, HttpStatus.OK);
		}
		user.merge(userdto);

		LocalUserDTO lUserDTO = dTOManager.userDTO(user);

		log.info(lUserDTO);

		return new ResponseEntity<LocalUserDTO>(lUserDTO, HttpStatus.OK);

	}

	/**
	 * Check if the token is valid and returns the user's id code
	 * 
	 * @param: User Token
	 * @return: the user's details if the token is valid, an error otherwise
	 */
	@ApiOperation(value = "Check if the token is valid and return the user's id", response = Boolean.class, tags = "")
	@ApiResponses(value = { @ApiResponse(code = 200, message = "Success"),
			@ApiResponse(code = 403, message = "Token not valid or access forbidden"),
			@ApiResponse(code = 500, message = "Token is null") })
	@PostMapping("/user/getUserIdFromToken")
	ResponseEntity<Integer> getUserFromIdToken(
			@ApiParam(name = "Authorization", type = "String", value = "The user's ID-Provider token", example = "", required = true) @RequestHeader(name = "Authorization", required = true) String token) {
		User result = new User();

		if (token == null || token == "") {
			log.info("Token is empty");
			return new ResponseEntity<Integer>(0, HttpStatus.INTERNAL_SERVER_ERROR);
		}
		User userdto = idpClientManager.getUserByToken(token);

		if (userdto == null) {
			log.info("User not found on ID-Provider - Token is not valid");

			return new ResponseEntity<Integer>(0, HttpStatus.FORBIDDEN);
		}
		//String subject = encryptionManager.encrypt(userdto.getEmail());
		User user = getUserFromSubject(userdto.getUuid());
		if (user == null) {
			log.info("User not found on local DB");

			return new ResponseEntity<Integer>(0, HttpStatus.FORBIDDEN);
		}
		user.merge (userdto);

		return new ResponseEntity<Integer>(user.getUserid().intValue(), HttpStatus.OK);

	}
	/**
	 * Update the user's password if the tkoen is valid
	 * 
	 */
	@PostMapping("/user/updatepassword")
	public ResponseEntity<Boolean> updatePassword(
			@ApiParam(name = "Password", type = "Password", value = "User's new pasword", example = "", required = true) 
			@RequestBody LocalUserDTO userDTO,
			@RequestHeader(name = "Authorization", required = false) String token) {
		
		User userdto = idpClientManager.getUserByToken(token);

		if (userdto == null) {
			log.info("User not found on ID-Provider - Token is not valid");
			return new ResponseEntity<Boolean>(false, HttpStatus.FORBIDDEN);
		}
		
		Boolean result = idpClientManager.updatePassword(token, userDTO.getPassword());
		
		return new ResponseEntity<Boolean>(result, HttpStatus.OK);
		
	}

	/**
	 * Update the user's password if the tkoen is valid
	 * 
	 */
	@PostMapping("/user/update")
	public ResponseEntity<Boolean> updateUserInfo(
			@RequestBody LocalUserDTO userDTO,
			@RequestHeader(name = "Authorization", required = false) String token) {
		
		User userdto = idpClientManager.getUserByToken(token);
		
		
		if (userdto == null) {
			log.info("User not found on ID-Provider - Token is not valid");
			return new ResponseEntity<Boolean>(false, HttpStatus.FORBIDDEN);
		}
		
		User tmpUser = userInterface.findBySubject(userdto.getUuid());
		
		if (tmpUser == null) {
			tmpUser = new User();
			tmpUser.setSubject(userdto.getUuid());
		}
			
		for (CommunityDTO cDto : userDTO.getUsercommunities()) {
			Community c = communityInterface.findByCommunityid(cDto.getCommunityid());
			if (tmpUser.getCommunities() == null || !tmpUser.getCommunities().contains(c)) {
				UserCommunity uc1 = new UserCommunity();
				uc1.setCommunity(c);
				uc1.setUser(tmpUser);
				uc1.setWallet(cDto.getWallet());
				uc1.setSubscriptiondate(new Date(System.currentTimeMillis()));
				uc1.setActive(true);
				tmpUser.setSingleUsercommunity(uc1);
				Role tmpRole = roleInterface.getById(new Long(2));
				UserRole ur = new UserRole();
				ur.setCommunity(c);
				ur.setUser(tmpUser);
				ur.setRole(tmpRole);
				tmpUser.setSingleUserRole(ur);
			}
		}
		try {
			tmpUser = userInterface.save(tmpUser);
		}catch (Exception e) {
			log.info("Eccezione " + e.getLocalizedMessage());
		}
		log.info("user saved, probably... the number of available users is " + userInterface.count());
		
		Boolean result = true;
		
		return new ResponseEntity<Boolean>(result, HttpStatus.OK);
		
	}

	
	/**
	 * List the devices associated to a user
	 * 
	 * @param: User Token
	 * @return: the user's details if the token is valid, an error otherwise
	 */
	@ApiOperation(value = "List the devices associated to a user", response = Boolean.class, tags = "")
	@ApiResponses(value = { @ApiResponse(code = 200, message = "Success"),
			@ApiResponse(code = 403, message = "Token not valid or access forbidden"),
			@ApiResponse(code = 500, message = "Token is null") })
	@PostMapping("/user/getDevices")
	ResponseEntity<?> getDevices(
			@ApiParam(name = "token", type = "String", value = "The user's ID-Provider token", example = "", required = true) @RequestParam String token) {

		List<Device> result = new ArrayList<Device>();

		if (token == null || token == "") {
			log.info("Token is empty");
			return new ResponseEntity<List<Device>>(result, HttpStatus.INTERNAL_SERVER_ERROR);
		}
		User userdto = idpClientManager.getUserByToken(token);

		if (userdto == null) {
			log.info("User not found on ID-Provider - Token is not valid");

			return new ResponseEntity<List<Device>>(result, HttpStatus.FORBIDDEN);
		}
		//String subject = encryptionManager.encrypt(userdto.getEmail());
		User user = getUserFromSubject(userdto.getUuid());
		if (user == null) {
			log.info("User not found on local DB");

			return new ResponseEntity<List<Device>>(result, HttpStatus.FORBIDDEN);
		}

		try {
			result = userInterface.findAllDevicesByUserid(user.getUserid());

		} catch (Exception e) {
			System.out.println(e.getLocalizedMessage());
		}
		return new ResponseEntity<List<Device>>(result, HttpStatus.OK);

	}

	public User persistNewUser(User u, Community c, Role r) {

		UserCommunity uc = new UserCommunity();
		uc.setCommunity(c);
		uc.setUser(u);

		u.setSingleUsercommunity(uc);

		UserRole ur = new UserRole();

		ur.setRole(r);
		ur.setCommunity(c);
		ur.setUser(u);
		u.addUserRole(ur);

		userInterface.save(u);

		return u;

	}

	private User persistNewUserWithCommunityIdAndRoleId(User u, Long cId, Long rId) {

		Community c = communityInterface.findByCommunityid(cId);

		Role r = roleInterface.findById(rId).get();

		return persistNewUser(u, c, r);

	}

	private User persistNewUserWithCommunityNameAndRoleName(User u, String cName, String rName) {
		Community c = communityInterface.findByCommunityName(cName);

		Role r = roleInterface.findByRolename(rName);

		return persistNewUser(u, c, r);

	}
//
//	private void deleteUserBySubject(String subject) {
//		User newUser;
//		try {
//			newUser = userInterface.findById(new Long(159)).get();
//		} catch (Exception e) {
//			newUser = null;
//		}
//
//		UserRole ur = newUser.getUserroles().get(0);
//		userRoleInterface.delete(ur);
//		userRoleInterface.flush();
//
//		if (newUser != null) {
//			userInterface.delete(newUser);
//			userInterface.flush();
//		}
//
//	}
//
//	private User getAndUpdateUserFromSubject(String token) {
//		String subject = JWTManager.getSubject(token);
//		User u = userInterface.findBySubject(subject);
//		if (u == null) {
//			// l'utente non e' registrato...
//			u = JWTManager.jwt2User(token);
//			Community community = communityInterface.findByCommunityid(u.getTmpCommunity());
//			for (String localRole : u.getKeycloakRoles()) {
//				Role dbRole = roleInterface.findByRolename(localRole);
//				if (dbRole.getId() > 0) {
//					u.addRole(dbRole);
//				}
//			}
//			UserCommunity uc1 = new UserCommunity();
//			uc1.setCommunity(community);
//			uc1.setUser(u);
//			uc1.setWallet(u.getTmpWallet());
//			u.setSingleUsercommunity(uc1);
//			userInterface.save(u);
//		}
//
//		return u;
//
//	}

	public User getUserFromSubject(String subject) {
		User user = new User();
		try {
			user = userInterface.findBySubject(subject);

		} catch (Exception e) {
			log.info(e.getLocalizedMessage());
			return user;

		}
		return user;
	}
	
	@Getter
	@Setter
	@AllArgsConstructor
	@NoArgsConstructor
	private class ResultMessage{
		private Boolean success;
		private String message;
		
	}
	
	public User getLocalUserFromToken(String token) {
		User user = new User();
		Boolean isValid = idpClientManager.isValid(token);
		
		if (isValid) {
			User userdto = idpClientManager.getUserByToken(token);
		
			user = userInterface.findBySubject(userdto.getUuid());
		}
		return user;
	}
	
	private Boolean checkIsJWTToken(String token) {
		
		String[] chunks = token.split("\\.");
		Base64.Decoder decoder = Base64.getUrlDecoder();

		String header = new String(decoder.decode(chunks[0]));
			if (header.contains("JWT")) return true;
		
		return false;
	}
	
	
	
}
