/*******************************************************************************
  * Copyright 2Fi Business Solutions Ltd.
  *
  * This code is copyrighted. Under no circumstances should any party, people,
  * or organization should redistribute any portions of this code in any form,
  * either verbatim or through electronic media, to any third parties, unless
  * under explicit written permission by 2Fi Business Solutions Ltd.
  ******************************************************************************/
package com.ffii.tbms.user.web;

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

import javax.servlet.http.HttpServletRequest;

import com.ffii.core.Settings;
import com.ffii.core.Settings.PasswordRule;
import com.ffii.core.setting.service.SettingsService;
import com.ffii.core.User;
import com.ffii.core.utils.BooleanUtils;
import com.ffii.core.utils.CriteriaUtils;
import com.ffii.core.utils.JsonUtils;
import com.ffii.core.utils.MapUtils;
import com.ffii.core.utils.Params;
import com.ffii.core.utils.SecurityUtils;
import com.ffii.core.utils.StringUtils;
import com.ffii.core.utils.web.ServletRequestUtils;
import com.ffii.core.web.AbstractController;
import com.ffii.core.web.view.json.JsonView;
import com.ffii.tbms.user.service.AuthService;
import com.ffii.tbms.user.service.GroupService;
import com.ffii.tbms.user.service.UserService;
import com.ffii.tbms.user.service.UserSettingsService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping(value = "/user")
public class UserController extends AbstractController {

	@Autowired
	private AuthService authService;

	@Autowired
	private GroupService groupService;

	@Autowired
	private UserService userService;

	@Autowired
	private SettingsService settingsService;

	@Autowired
	private UserDetailsService userDetailsService;

	@Autowired
	private UserSettingsService userSettingsService;

	@Autowired
	private StandardPasswordEncoder passwordEncoder;

	@SuppressWarnings("unchecked")
	@RequestMapping(value = "/save", method = RequestMethod.POST)
	public String save(Model model, HttpServletRequest request, @RequestParam(defaultValue = "-1") Integer id,
			@RequestParam(required = false) String password,
			@RequestParam(required = false) String groupUpdatedRecordsData,
			@RequestParam(required = false) String authUpdatedRecordsData) throws Exception {

		if (SecurityUtils.isGranted("USER_MAINTAIN")) {
			User instance;
			if (id > 0) {
				instance = userService.find(id);
			} else {
				instance = new User();
				PasswordRule rule = new Settings.PasswordRule(settingsService);
				String randomPassword = SecurityUtils.genPwd(rule.getMin(), rule.needNumberChar(),
						rule.needUpperEngChar(), rule.needLowerEngChar(), rule.needSpecialChar());
				instance.setPassword(passwordEncoder.encode(randomPassword));
			}

			ServletRequestUtils.doBind(request, instance, "");

			// handle password hash
			if (StringUtils.isNotBlank(password)) {
				instance.setPassword(passwordEncoder.encode(password));
			}

			id = userService.saveOrUpdate(instance);

			// handle user's group
			if (groupUpdatedRecordsData != null) {
				List<Map<String, Object>> groupUpdatedRecords = JsonUtils
						.fromJsonStringAsListOfMap(groupUpdatedRecordsData);

				List<Map<String, Object>> groupBatchInsertValues = new ArrayList<>();
				List<Map<String, Object>> groupBatchDeleteValues = new ArrayList<>();

				for (Map<String, Object> record : groupUpdatedRecords) {
					Map<String, Object> batchValue = new HashMap<>(2);
					batchValue.put("userId", id);
					batchValue.put("groupId", record.get("id"));

					if (BooleanUtils.isTrue(record.get("v"))) {
						groupBatchInsertValues.add(batchValue);
					} else {
						groupBatchDeleteValues.add(batchValue);
					}
				}
				if (groupBatchInsertValues.size() > 0)
					groupService.addGroupUser(groupBatchInsertValues.toArray((Map<String, Object>[]) new Map[0]));
				if (groupBatchDeleteValues.size() > 0)
					groupService.removeGroupUser(groupBatchDeleteValues.toArray((Map<String, Object>[]) new Map[0]));
			}

			// Handle Group Authority
			if (authUpdatedRecordsData != null) {
				List<Map<String, Object>> authUpdatedRecords = JsonUtils
						.fromJsonStringAsListOfMap(authUpdatedRecordsData);

				List<Map<String, Object>> authBatchInsertValues = new ArrayList<Map<String, Object>>();
				List<Map<String, Object>> authBatchDeleteValues = new ArrayList<Map<String, Object>>();

				for (Map<String, Object> record : authUpdatedRecords) {
					Map<String, Object> batchValue = new HashMap<String, Object>(2);
					batchValue.put("userId", id);
					batchValue.put("authority", record.get("authority"));

					if (BooleanUtils.isTrue(record.get("v"))) {
						authBatchInsertValues.add(batchValue);
					} else {
						authBatchDeleteValues.add(batchValue);
					}
				}

				if (authBatchInsertValues.size() > 0)
					authService.batchInsertUsersAuthorities(
							authBatchInsertValues.toArray((Map<String, Object>[]) new Map[0]));

				if (authBatchDeleteValues.size() > 0)
					authService.batchDeleteUsersAuthorities(
							authBatchDeleteValues.toArray((Map<String, Object>[]) new Map[0]));
			}

			model.addAttribute(Params.ID, id);
			model.addAttribute(Params.VERSION_ID, instance.getVersionId());

			model.addAttribute(Params.SUCCESS, Boolean.TRUE);
		} else {
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}

		return JsonView.NAME;
	}

	@RequestMapping(value = "/get-lang", method = { RequestMethod.GET, RequestMethod.POST })
	public String getLang(Model model, HttpServletRequest request) throws Exception {
		User instance = userService.find(SecurityUtils.getUser().getId());
		model.addAttribute("locale", instance.getLocale());
		model.addAttribute(Params.SUCCESS, Boolean.TRUE);
		return JsonView.NAME;
	}

	@RequestMapping(value = "/get-settings", method = { RequestMethod.GET, RequestMethod.POST })
	public String getUserSettings(Model model, HttpServletRequest request) throws Exception {
		List<Map<String, Object>> userSettings = userSettingsService.searchSettings(new HashMap<>());
		model.addAttribute(Params.RECORDS, userSettings);
		model.addAttribute(Params.SUCCESS, Boolean.TRUE);
		return JsonView.NAME;
	}

	@RequestMapping(value = "/setting/save", method = { RequestMethod.GET, RequestMethod.POST })
	public String saveUserSetting(Model model, HttpServletRequest request, String name, String value) throws Exception {
		userSettingsService.saveSetting(name, value);
		model.addAttribute(Params.SUCCESS, Boolean.TRUE);
		return JsonView.NAME;
	}

	@RequestMapping(value = "/change-lang", method = { RequestMethod.POST })
	public String changeLang(Model model, HttpServletRequest request, @RequestParam String locale) throws Exception {
		User instance = userService.find(SecurityUtils.getUser().getId());
		if (instance != null) {
			instance.setLocale(locale);
			userService.saveOrUpdate(instance);
			model.addAttribute(Params.SUCCESS, Boolean.TRUE);
		} else {
			model.addAttribute(Params.MSG, "no record");
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}
		return JsonView.NAME;
	}

	@RequestMapping(value = "/delete", method = RequestMethod.POST)
	public String delete(Model model, HttpServletRequest request, @RequestParam Integer id) throws Exception {

		if (SecurityUtils.isGranted("USER_MAINTAIN")) {

			// find, mark delete, and save
			User instance = userService.find(id);
			instance.setDeleted(true);
			userService.saveOrUpdate(instance);

			model.addAttribute(Params.SUCCESS, Boolean.TRUE);
		} else {
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}

		return JsonView.NAME;
	}

	@RequestMapping(value = "/load", method = { RequestMethod.GET, RequestMethod.POST })
	public String load(Model model, HttpServletRequest request, @RequestParam Integer id) throws Exception {

		if (SecurityUtils.isGranted("USER_MAINTAIN")) {
			Map<String, Object> data = userService.findUserMap(MapUtils.toHashMap("id", id));

			// remove un-necessary fields
			data.remove("password");

			model.addAttribute(Params.DATA, data);
			model.addAttribute(Params.SUCCESS, data != null);
		} else {
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}

		return JsonView.NAME;
	}

	@RequestMapping(value = "/combo.json", method = { RequestMethod.GET, RequestMethod.POST })
	public String comboJson(Model model, HttpServletRequest request, @RequestParam(required=false, defaultValue="false") Boolean send) throws Exception {

		Map<String, Object> args = new HashMap<String, Object>();

		if(send){
			args.put("senderId", SecurityUtils.getUser().getId());
		}


		CriteriaUtils.addInteger(request, args, Params.ID);
		CriteriaUtils.addStringLike(request, args, Params.QUERY);

		// if current user is not from host compnay, then restrict access to users in
		// (self company + host company)
		if (SecurityUtils.getUser().getCompanyId() > 1) {
			List<Integer> companyIds = new ArrayList<Integer>();
			companyIds.add(1); // add host company
			companyIds.add(SecurityUtils.getUser().getCompanyId());

			args.put("companyIds", companyIds);
		}

		List<Map<String, Object>> records = userService.searchForCombo(args);

		model.addAttribute(Params.RECORDS, records);

		return JsonView.NAME;
	}

	@RequestMapping(value = "/list.json", method = { RequestMethod.GET, RequestMethod.POST })
	public String listJson(Model model, HttpServletRequest request) throws Exception {

		if (SecurityUtils.isGranted("USER_MAINTAIN")) {
			Map<String, Object> args = new HashMap<String, Object>();

			CriteriaUtils.addInteger(request, args, Params.ID);
			CriteriaUtils.addStringLike(request, args, "fullname");
			CriteriaUtils.addStringLike(request, args, "username");
			CriteriaUtils.addInteger(request, args, "sysGroupId");
			CriteriaUtils.addInteger(request, args, "groupId");
			CriteriaUtils.addInteger(request, args, "recipient");

			List<Map<String, Object>> records = userService.search(args);

			model.addAttribute(Params.RECORDS, records);
		} else {
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}

		return JsonView.NAME;
	}

	@RequestMapping(value = "/login-as", method = RequestMethod.POST)
	public String loginAs(Model model, @RequestParam String username) {
		if (SecurityUtils.isGranted("USER_MAINTAIN")) {
			SecurityUtils.loginUser(userDetailsService, username);
			model.addAttribute(Params.SUCCESS, Boolean.TRUE);
		} else {
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}
		return JsonView.NAME;
	}

	@RequestMapping(value = "/change-password", method = RequestMethod.POST)
	public String changePassword(Model model, HttpServletRequest request, @RequestParam String password,
			@RequestParam String newPassword) throws Exception {

		int id = SecurityUtils.getUser().getId();
		User instance = userService.find(id);

		if (passwordEncoder.matches(password, instance.getPassword())) {
			PasswordRule rule = new Settings.PasswordRule(settingsService);
			if (SecurityUtils.checkPwd(newPassword, rule.getMin(), rule.getMax(), rule.needNumberChar(),
					rule.needUpperEngChar(), rule.needLowerEngChar(), rule.needSpecialChar())) {
				instance.setPassword(passwordEncoder.encode(newPassword));
				userService.saveOrUpdateForChangePwd(instance);

				model.addAttribute(Params.SUCCESS, Boolean.TRUE);
			} else {
				model.addAttribute(Params.MSG, rule.getWrongMsg());
				model.addAttribute(Params.SUCCESS, Boolean.FALSE);
			}
		} else {
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}

		return JsonView.NAME;
	}

	@RequestMapping(value = "/reset-password", method = RequestMethod.POST)
	public String resetPassword(Model model, HttpServletRequest request, @RequestParam Integer userId)
			throws Exception {

		if (SecurityUtils.isGranted("USER_MAINTAIN")) {
			User instance = userService.find(userId);
			if (instance != null) {
				PasswordRule rule = new Settings.PasswordRule(settingsService);
				String randomPassword = SecurityUtils.genPwd(rule.getMin(), rule.needNumberChar(),
						rule.needUpperEngChar(), rule.needLowerEngChar(), rule.needSpecialChar());

				instance.setPassword(passwordEncoder.encode(randomPassword));
				userService.saveOrUpdate(instance);

				model.addAttribute(Params.DATA, randomPassword);
			}
			model.addAttribute(Params.SUCCESS, Boolean.TRUE);
		} else {
			model.addAttribute(Params.MSG, "No Auth");
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}

		return JsonView.NAME;
	}

	@RequestMapping(value = "/clone", method = { RequestMethod.POST })
	public String clone(Model model, HttpServletRequest request, @RequestParam Integer id,
			@RequestParam String username, @RequestParam String password) throws Exception {
		if (SecurityUtils.isGranted("USER_MAINTAIN")) {
			User instance = userService.find(id);
			if (instance != null) {
				PasswordRule rule = new Settings.PasswordRule(settingsService);
				if (SecurityUtils.checkPwd(password, rule.getMin(), rule.getMax(), rule.needNumberChar(),
						rule.needUpperEngChar(), rule.needLowerEngChar(), rule.needSpecialChar())) {
					model.addAttribute(Params.SUCCESS,
							userService.clone(instance, username, passwordEncoder.encode(password)));
				} else {
					model.addAttribute(Params.MSG, rule.getWrongMsg());
					model.addAttribute(Params.SUCCESS, Boolean.FALSE);
				}
			} else {
				model.addAttribute(Params.MSG, "no record");
				model.addAttribute(Params.SUCCESS, Boolean.FALSE);
			}
		} else {
			model.addAttribute(Params.MSG, "No Auth");
			model.addAttribute(Params.SUCCESS, Boolean.FALSE);
		}
		return JsonView.NAME;
	}

}