package eu.dnetlib.organizations.controller;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import eu.dnetlib.common.controller.AbstractDnetController;
import eu.dnetlib.organizations.importer.ImportExecution;
import eu.dnetlib.organizations.importer.ImportExecutor;
import eu.dnetlib.organizations.model.SystemConfiguration;
import eu.dnetlib.organizations.model.utils.VocabularyTerm;
import eu.dnetlib.organizations.model.view.PersistentOrganizationView;
import eu.dnetlib.organizations.model.view.UserView;
import eu.dnetlib.organizations.repository.SystemConfigurationRepository;
import eu.dnetlib.organizations.repository.UserRepository;
import eu.dnetlib.organizations.repository.readonly.UserViewRepository;
import eu.dnetlib.organizations.utils.DatabaseUtils;
import eu.dnetlib.organizations.utils.MailDispatcher;

@RestController
public class AdminController extends AbstractDnetController {

	@Autowired
	private UserRepository userRepository;

	@Autowired
	private UserViewRepository userViewRepository;

	@Autowired
	private SystemConfigurationRepository systemConfigurationRepository;

	@Autowired
	private ImportExecutor importExecutor;

	@Autowired
	private DatabaseUtils dbUtils;

	@Autowired
	private MailDispatcher mailDispatcher;

	@PostMapping(value = "/registration_api/newUser")
	public Map<String, Integer> newUser(final @RequestBody UserRegistration user, final Authentication authentication) {

		final String email = UserInfo.getEmail(authentication);
		final String fullname = UserInfo.getFullname(authentication);
		final String organization = UserInfo.getOrganization(authentication);

		final Map<String, Integer> res = new HashMap<>();

		if (!UserInfo.isNotAuthorized(authentication) || userRepository.existsById(email)) {
			res.put("status", 2);
		} else {
			dbUtils.newUser(email, fullname, organization, user.getReferencePerson(), user.getRequestMessage(), user.getCountries());

			final UserView savedUser = userViewRepository.findById(email).get();
			mailDispatcher.sendRequestRegistrationMail(savedUser);

			res.put("status", 1);
		}
		return res;
	}

	@GetMapping("/api/users")
	public Iterable<UserView> users(final Authentication authentication) {
		if (UserInfo.isSuperAdmin(authentication)) {
			return userViewRepository.findAll();
		} else if (UserInfo.isNationalAdmin(authentication)) {

			// IMPORTANT: a national admin can manage ONLY the users where ALL the countries are under his control
			final List<UserView> res = new ArrayList<>();
			final List<String> myCountries = dbUtils.listCountriesForUser(UserInfo.getEmail(authentication))
				.stream()
				.map(VocabularyTerm::getValue)
				.collect(Collectors.toList());

			for (final UserView uw : userViewRepository.findAll()) {
				if (uw.getCountries() != null && uw.getCountries().length > 0 && myCountries.containsAll(Arrays.asList(uw.getCountries()))) {
					res.add(uw);
				}
			}
			return res;
		} else {
			return new ArrayList<>();
		}
	}

	@PostMapping("/api/users")
	public Iterable<UserView> updateUser(@RequestBody final UserView userView, final Authentication authentication) {
		if (UserInfo.getEmail(authentication).equals(userView.getEmail())) { throw new RuntimeException("You can't edit your own user"); }

		dbUtils.updateUser(userView);

		if (userView.getRole().equals(UserRole.USER.toString()) ||
			userView.getRole().equals(UserRole.NATIONAL_ADMIN.toString()) ||
			userView.getRole().equals(UserRole.ADMIN.toString())) {

			mailDispatcher.sendUpdatedUserMail(userView);
		}

		return users(authentication);
	}

	@DeleteMapping("/api/users")
	public Iterable<UserView> deleteUser(final @RequestParam String email, final Authentication authentication) {
		if (UserInfo.getEmail(authentication).equals(email)) { throw new RuntimeException("You can't delete your own user"); }
		dbUtils.deleteUser(email);
		return users(authentication);
	}

	@GetMapping("/api/sysconf")
	public SystemConfiguration sysConf(final Authentication authentication) {
		if (UserInfo.isSuperAdmin(authentication)) {
			return systemConfigurationRepository.findById(SystemConfiguration.DEFAULT_ID).get();
		} else {
			throw new RuntimeException("User not authorized");
		}
	}

	@GetMapping("/api/lastImportStatus")
	private ImportExecution lastImportExecution(final Authentication authentication) {
		if (UserInfo.isSuperAdmin(authentication)) {
			return importExecutor.getLastImportExecution();
		} else {
			throw new RuntimeException("User not authorized");
		}
	}

	@PostMapping("/api/sysconf")
	public SystemConfiguration saveSysConf(@RequestBody final SystemConfiguration sysConf, final Authentication authentication) {
		if (UserInfo.isSuperAdmin(authentication)) {
			sysConf.setId(SystemConfiguration.DEFAULT_ID);
			systemConfigurationRepository.save(sysConf);
			mailDispatcher.configure(sysConf);
			return sysConf;
		} else {
			throw new RuntimeException("User not authorized");
		}
	}

	@GetMapping("/api/refreshFulltextIndex")
	public List<String> refreshFulltextIndex(final Authentication authentication) {
		if (UserInfo.isSuperAdmin(authentication)) {
			new Thread(dbUtils::updateFulltextIndex).start();
			return Arrays.asList("The index update is in progress, please wait a few minutes");
		} else {
			throw new RuntimeException("User not authorized");
		}
	}

	@GetMapping("/api/performConsistencyCheck")
	public List<String> performConsistencyCheck(final Authentication authentication) {
		if (UserInfo.isSuperAdmin(authentication)) {
			new Thread(dbUtils::verifyConsistency).start();
			return Arrays.asList("The check is running, please wait a few minutes");
		} else {
			throw new RuntimeException("User not authorized");
		}
	}

	@GetMapping("/api/verifyCountriesInSuggestions")
	public List<String> verifyCountriesInSuggestions(final Authentication authentication) {
		if (UserInfo.isSuperAdmin(authentication)) {
			final List<String> list = dbUtils.invalidCountriesInSuggestions();
			if (list.isEmpty()) {
				return Arrays.asList("All countries are valid");
			} else {
				return Arrays.asList("Invalid countries in suggestions: " + StringUtils.join(list, ", "));
			}
		} else {
			throw new RuntimeException("User not authorized");
		}
	}

	@GetMapping("/api/clearCache")
	public List<String> clearCache(final Authentication authentication) {
		if (UserInfo.isSuperAdmin(authentication)) {
			dbUtils.clearCache();
			return Arrays.asList("All caches are cleared");
		} else {
			throw new RuntimeException("User not authorized");
		}
	}

	@GetMapping("/api/restartSuggestionsImport")
	public List<String> restartSuggestionsImport(final Authentication authentication) {
		if (UserInfo.isSuperAdmin(authentication)) {
			importExecutor.startImport("the portal, user: " + UserInfo.getEmail(authentication));
			return Arrays.asList("The import is running");
		} else {
			throw new RuntimeException("User not authorized");
		}
	}

	@GetMapping("/api/persistentOrgs")
	public Iterable<PersistentOrganizationView> listPersistentOrgs() {
		return dbUtils.listPersistentOrgs();
	}

	@PostMapping("/api/persistentOrgs")
	public Iterable<PersistentOrganizationView> addPersistentOrgs(@RequestBody final List<String> ids) {
		ids.forEach(id -> dbUtils.addPersistentOrgs(id));
		return dbUtils.listPersistentOrgs();
	}

	@DeleteMapping("/api/persistentOrgs")
	public Iterable<PersistentOrganizationView> deletePersistentOrgs(@RequestParam final String id) {
		dbUtils.deletePersistentOrgs(id);
		return dbUtils.listPersistentOrgs();
	}

}
