package managedbean.profile;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
import javax.resource.NotSupportedException;

import TO.FamilyDoctorTO;
import TO.LoggedUserTO;
import TO.MedicalSpecialtyTO;
import TO.PatientTO;
import TO.PrimaryHealthCareCenterTO;
import TO.SpecialistDoctorTO;
import common.Constants;
import common.HashUtils;
import common.UserType;
import managedbean.common.ManagedBeanBase;
import managedbean.common.SessionUtils;
import managedbean.common.ValidationUtils;

/**
 * ManagedBEan que gestiona el registro de usuarios: Usuarios de tipo "Paciente" Usuarios de tipo "Médico de Familia" usuarios de tipo "Médico especialista"
 * 
 * @author Marcos García Núñez (mgarcianun@uoc.edu)
 *
 */
@Named("UpdateProfile")
@ViewScoped
public class UpdateProfileMBean extends ManagedBeanBase implements Serializable {

	private static final long serialVersionUID = 1L;

	private int id;
	private String cipCode;
	private String nif;
	private String name;
	private String surname;
	private String currentPassword;
	private String oldPassword;
	private String password;
	private String email;
	private String lastUIQueryPH;
	private String lastUIQueryMS;
	private String lastUIQueryFD;

	private List<UserType> userTypes;
	private UserType userType;
	private PrimaryHealthCareCenterTO primaryHealthCareCenter;
	private MedicalSpecialtyTO medicalSpecialty;
	private List<MedicalSpecialtyTO> medicalSpecialtiesList;
	private List<PrimaryHealthCareCenterTO> primaryHealthCareCentersList;
	private FamilyDoctorTO familyDoctor;
	private List<FamilyDoctorTO> familyDoctorList;

	public UpdateProfileMBean() {

	}

	/**
	 * Inicializa el Bean:
	 * 
	 * Carga la lsita de tipos de usuario (perfiles) Recupera los datos del usuario logeado actualmente, y los asigna a las propiedades del managedBean.
	 * 
	 */
	@PostConstruct
	public void init() {
		this.userTypes = new ArrayList<UserType>();
		this.userTypes.add(UserType.PATIENT);
		this.userTypes.add(UserType.FAMILY_DOCTOR);
		this.userTypes.add(UserType.SPECIALIST_DOCTOR);
		this.lastUIQueryPH = "";
		this.lastUIQueryMS = "";

		// Recuperamos el usuario logeado actual
		LoggedUserTO usr = null;
		usr = SessionUtils.getloggedOnUser();

		if (usr == null)
			this.addFacesMessage(FacesMessage.SEVERITY_ERROR, "Sesión no válida", "Su sesión actual no es válida, por favor cierre su sesión y vuelva a logearse en el sistema.");
		else {
			this.userType = usr.getUserType();
			this.id = Integer.valueOf(usr.getId());

			switch (usr.getUserType()) {
			case PATIENT:
				this.familyDoctorList = this.getRemoteManagerCommon().listFamilyDoctorsPaged(0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
				this.setPatientData(this.getRemoteManagerCommon().findPatientById(this.id));
				break;
			case SPECIALIST_DOCTOR:
				this.medicalSpecialtiesList = this.getRemoteManagerCommon().listMedicalSpecialtiesPaged(0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
				this.setSpecialistDoctorData(this.getRemoteManagerCommon().findSpecialistDoctorById(this.id));
				break;
			case FAMILY_DOCTOR:
				this.primaryHealthCareCentersList = this.getRemoteManagerCommon().listCAPsPaged(0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
				this.setFamilyDoctorData(this.getRemoteManagerCommon().findFamilyDoctorById(this.id));
				break;
			case ADMINISTRATOR:
				this.addFacesMessage(FacesMessage.SEVERITY_ERROR, "Usuario sin perfil", "Usted es un usuario de tipo administrador y no puede editar su perfil de usuario.");
				break;
			}
		}
	}

	/**
	 * Establece los valores de las propiedades del managedBean a partir de los datos de un Transfer Object de un paciente
	 * 
	 * @param pat Paciente que está editando su perfil, y cuyos datos deben mostrarse en la vista.
	 */
	private void setPatientData(PatientTO pat) {
		this.id = pat.getId();
		this.cipCode = pat.getPersonalIdentificationCode();
		this.name = pat.getName();
		this.surname = pat.getSurname();
		this.nif = pat.getNif();
		this.email = pat.getEmail();
		this.currentPassword = pat.getPassword();
		this.familyDoctor = pat.getFamilyDoctor();
	}

	/**
	 * Establece los valores de las propiedades del managedBean a partir de los datos de un Transfer Object de un médico de familia
	 * 
	 * @param fd FamilyDoctor que está editando su perfil, y cuyos datos deben mostrarse en la vista.
	 */
	private void setFamilyDoctorData(FamilyDoctorTO fd) {
		this.id = fd.getId();
		this.cipCode = fd.getProfessionalNumber();
		this.name = fd.getName();
		this.surname = fd.getSurname();
		this.nif = fd.getNif();
		this.email = fd.getEmail();
		this.currentPassword = fd.getPassword();
		this.primaryHealthCareCenter = fd.getPrimaryHealthCareCenter();
	}

	/**
	 * Establece los valores de las propiedades del managedBean a partir de los datos de un Transfer Object de un médico especialista
	 * 
	 * @param sd SpecialistDoctor que está editando su perfil, y cuyos datos deben mostrarse en la vista.
	 */
	private void setSpecialistDoctorData(SpecialistDoctorTO sd) {
		this.id = sd.getId();
		this.cipCode = sd.getProfessionalNumber();
		this.name = sd.getName();
		this.surname = sd.getSurname();
		this.nif = sd.getNif();
		this.email = sd.getEmail();
		this.currentPassword = sd.getPassword();
		this.medicalSpecialty = sd.getMedicalSpecialty();
	}

	public List<UserType> getUserTypes() {
		return userTypes;
	}

	public List<MedicalSpecialtyTO> getMedicalSpecialtiesList() {
		return medicalSpecialtiesList;
	}

	public List<PrimaryHealthCareCenterTO> getPhcList() {
		return primaryHealthCareCentersList;
	}

	/**
	 * Método que implementa la búsqueda de un CAP a través del termino tecleado por el usuario en la lista de selección
	 * 
	 * @param query Termino tecleado por el usuario
	 * @return Lista de CAPs que coinciden con el termino búscado por el usuario.
	 */
	public List<PrimaryHealthCareCenterTO> completePrimaryHealCareCenter(String query) {
		if (query != null && query.equals(this.lastUIQueryPH) == false) {
			this.lastUIQueryPH = query;
			// Recuperamos las XXX primeras coincidencias
			this.primaryHealthCareCentersList = this.getRemoteManagerCommon().listCAPsFiltered(query, 0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
		}
		return this.primaryHealthCareCentersList;
	}

	/**
	 * Método que implementa la búsqueda de una especialidad a través del termino tecleado por el usuario en la lista de selección
	 * 
	 * @param query Termino tecleado por el usuario
	 * @return Lista de especialidades que coinciden con el termino búscado por el usuario.
	 */
	public List<MedicalSpecialtyTO> completeMedicalSpecialty(String query) {
		if (query != null && query.equals(this.lastUIQueryMS) == false) {
			this.lastUIQueryMS = query;
			// Recuperamos las XXX primeras coincidencias
			this.medicalSpecialtiesList = this.getRemoteManagerCommon().listMedicalSpecialtiesFiltered(query, 0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
		}
		return this.medicalSpecialtiesList;
	}

	/**
	 * Búsca si el NIF ya está en uso para otro usuario. Solo se permite que el mismo NIF se refistro como paciente y como médico (especialista o de familia, pero no como ambos).
	 * 
	 * Un Médico puede estar registrado como paciente con el mismo NIF, pero como médico de familia y especialista al mismo tiempo.
	 */
	public boolean checkNIFDuplicated(String nifValue, UserType userType, Integer userId) {
		return ValidationUtils.checkIfNifAlreadyRegistered(this.getRemoteManagerCommon(), userType, nifValue, userId);
	}

	public List<FamilyDoctorTO> getFamilyDoctorList() {
		return familyDoctorList;
	}

	/**
	 * Método que implementa la búsqueda de un médico de familia a través del termino tecleado por el usuario en la lista de selección
	 * 
	 * @param query Termino tecleado por el usuario
	 * @return Lista de médicos de familia que coinciden con el termino búscado por el usuario.
	 */
	public List<FamilyDoctorTO> completeFamilyDoctor(String query) {
		if (query != null && query.equals(this.lastUIQueryFD) == false) {
			this.lastUIQueryFD = query;
			// Recuperamos las 200 primeras coincidencias
			this.familyDoctorList = this.getRemoteManagerCommon().listFamilyDoctorsFiltered(query, 0, Constants.MAX_ITEMS_AUTOCOMPLETE_SEARCH);
		}
		return this.familyDoctorList;
	}

	public boolean isUserTypePatient() {
		return (this.userType == UserType.PATIENT);
	}

	public boolean isUserTypeFamilyDoctor() {
		return (this.userType == UserType.FAMILY_DOCTOR);
	}

	public boolean isUserTypeSpecialistDoctor() {
		return (this.userType == UserType.SPECIALIST_DOCTOR);
	}

	public boolean isUserTypeDoctor() {
		return (isUserTypeFamilyDoctor() || isUserTypeSpecialistDoctor());
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getSurname() {
		return surname;
	}

	public void setSurname(String surname) {
		this.surname = surname;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getNif() {
		return nif;
	}

	public void setNif(String nif) {
		this.nif = nif;
	}

	public int getId() {
		return id;
	}

	/**
	 * Método que guarda los datos realizados en la modificación del perfil. Tiene en cuenta el tipo de usuario que está editando su perfil.
	 * 
	 * Si el usuario es un médico de familia requiere que se seleccione un CAP para el usuario. Si el usuario es un médico especialista requiere que se seleccione una especialidad
	 * médica para el usuario. Se comprueba que el NIF especificado sea válido y no pertenezca a otro usuario registrado (ver relgas de NIF permitidos para usuarios en el método
	 * handleNIFValueChange)
	 * 
	 * Si se especifica una nueva contraseña, entonces se realiz un cambio de contraseña, y se requiere además: Que la nueva contraseña sea diferente a la anterior.
	 * 
	 * Después de guardar los datos, se actualizan los datos guardados en la sesión actual para reflejar un posible cambio de nombre y/o apellidos.
	 */
	public void saveData() {
		int error = 0;

		// Si no hay tipo de usuario, es que algo raro ha pasado (sesión caducada?). salimos.
		if (this.userType == null)
			return;

		try {
			this.nif = ValidationUtils.normalizeNIF(this.nif);

			boolean changePassword = (this.oldPassword != null && this.oldPassword.equals("") == false) || (this.password != null && this.password.equals("") == false);

			if (this.userType == UserType.ADMINISTRATOR) {
				this.addFacesMessage(FacesMessage.SEVERITY_ERROR, "Perfil de usuario incorrecto", "Su perfil es de un usuario de tipo administrador y no puede editar su perfil.");
				error++;
			}

			if (this.isUserTypeFamilyDoctor() && this.primaryHealthCareCenter == null) {
				this.addFacesMessage(FacesMessage.SEVERITY_WARN, "Centro de atención primaria no seleccionado", "Por favor, especifique un centro de atención primaria.");
				error++;
			}
			if (this.isUserTypeSpecialistDoctor() && this.medicalSpecialty == null) {
				this.addFacesMessage(FacesMessage.SEVERITY_WARN, "Especialidad médica no seleccionada", "Por favor, especifique una especialidad médica.");
				error++;
			}
			if (ValidationUtils.isValid(nif) == false) {
				this.addFacesMessage(FacesMessage.SEVERITY_WARN, "El NIF indicado no es válido", "Por favor, especifique un NIF válido.");
				error++;
			}
			if (ValidationUtils.checkIfNifAlreadyRegistered(this.getRemoteManagerCommon(), this.userType, this.nif, this.id) == true) {
				this.addFacesMessage("frmUpdateProfile:nif", FacesMessage.SEVERITY_WARN, "NIF duplicado", "El nif indicado pertenece a otro usuario previamente registrado");
				error++;
			}
			if (changePassword == true) {
				// el usuario queire cambiar el password Comprobamos que el password
				// especificado coincide con el guardado
				if (this.password == null || this.password.length() < 4) {
					this.addFacesMessage(FacesMessage.SEVERITY_ERROR, "Nueva contraseña incorrectra", "Su nueva contraseña debe tener al menos 4 caracteres.");
					error++;
				} else if (HashUtils.hashMD5(this.oldPassword).equals(this.currentPassword) == false) {
					this.addFacesMessage(FacesMessage.SEVERITY_ERROR, "Contraseña actual incorrecta",
							"Su actual contraseña es incorrecta. Por favor, especifique su contraseña actual.");
					error++;
				}
			}

			if (error == 0) {
				LoggedUserTO usr = null;
				switch (this.userType) {
				case PATIENT:
					PatientTO pat = this.getRemoteManagerProfile().updatePatientData(id, nif, name, surname, password, email);
					this.setPatientData(pat);
					usr = new LoggedUserTO(pat.getId().toString(), pat.getName(), pat.getPassword(), this.userType, pat.getDisplayName());

					break;
				case FAMILY_DOCTOR:
					FamilyDoctorTO fd = this.getRemoteManagerProfile().updateFamilyDoctorData(id, nif, name, surname, password, email, this.primaryHealthCareCenter);
					this.setFamilyDoctorData(fd);
					usr = new LoggedUserTO(fd.getId().toString(), fd.getName(), fd.getPassword(), this.userType, fd.getDisplayName());

					break;
				case SPECIALIST_DOCTOR:
					SpecialistDoctorTO sd = this.getRemoteManagerProfile().updateSpecialistDoctorData(id, nif, name, surname, password, email, this.medicalSpecialty);
					this.setSpecialistDoctorData(sd);
					usr = new LoggedUserTO(sd.getId().toString(), sd.getName(), sd.getPassword(), this.userType, sd.getDisplayName());

					break;
				case ADMINISTRATOR:
					break;
				}

				if (changePassword == true) {
					this.addFacesMessage(FacesMessage.SEVERITY_INFO, "Contraseña actualizada", "Su contraseña ha sido actualizada correctamente.");
					this.password = "";
					this.oldPassword = "";
				}

				// Actualizamos la sessión del usuario actual por si ha cambiado el nombre de usuario.
				SessionUtils.createOrUpdateSession(usr);

				this.addFacesMessage(FacesMessage.SEVERITY_INFO, "Los datos se han guardado", "Los datos de su perfil se han guardado correctamente.");
			}
		} catch (Exception ex) {
			this.manageException(ex);
		}
	}

	public UserType getUserType() {
		return userType;
	}

	public void setUserType(UserType value) {
		this.userType = value;
	}

	public MedicalSpecialtyTO getMedicalSpecialty() {
		return medicalSpecialty;
	}

	public void setMedicalSpecialty(MedicalSpecialtyTO medicalSpecialty) {
		this.medicalSpecialty = medicalSpecialty;
	}

	public PrimaryHealthCareCenterTO getPrimaryHealthCareCenter() {
		return primaryHealthCareCenter;
	}

	public void setPrimaryHealthCareCenter(PrimaryHealthCareCenterTO primaryHealthCareCenter) {
		this.primaryHealthCareCenter = primaryHealthCareCenter;
	}

	public String getOldPassword() {
		return oldPassword;
	}

	public void setOldPassword(String oldPassword) {
		this.oldPassword = oldPassword;
	}

	public FamilyDoctorTO getFamilyDoctor() {
		return familyDoctor;
	}

	public void setFamilyDoctor(FamilyDoctorTO familyDoctor) {
		this.familyDoctor = familyDoctor;
	}

	public String getCipCode() {
		return cipCode;
	}

}
