package ejb.profile;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import TO.FamilyDoctorTO;
import TO.MedicalSpecialtyTO;
import TO.PatientTO;
import TO.PrimaryHealthCareCenterTO;
import TO.SpecialistDoctorTO;
import common.Constants;
import common.HashUtils;
import ejb.common.CommonFacadeLocal;
import jpa.FamilyDoctorJPA;
import jpa.MedicalSpecialtyJPA;
import jpa.PatientJPA;
import jpa.PrimaryHealthCareCenterJPA;
import jpa.SpecialistDoctorJPA;

/**
 * 
 * @author Marcos García Núñez (mgarcianun@uoc.edu)
 *
 */
@Stateless
public class ProfileFacadeBean implements ProfileFacadeRemote {

	// Persistence Unit Context
	@PersistenceContext(unitName = "MyHealth")
	private EntityManager entman;

	//Propiedad que permite el acceso local a los métodos del EJB de servicios comunes (para recuperar entidades, etc).
	@EJB
	CommonFacadeLocal commonServices;

	/**
	 * Método que genera un nuevo número de profesional, basando en la secuencia de base de datos. Los números de profesional son únicos y comunes para médicos de familia y médicos
	 * especialistas.
	 * 
	 * Si un número de profesional no se utiliza no se vuelve a reutilizar, por lo que pueden existir huecos en la númeración.
	 * 
	 * @return String con el número número de profesional generado.
	 */
	private String getNextProfessionalNumber() {
		Query q = entman.createNativeQuery("select nextval('myhealth.profesionalnumber')");
		return Constants.PROFESSIONAL_NUMBER_PREFIX.concat(String.valueOf(q.getSingleResult()));
	}

	/**
	 * Método que genera un nuevo Código de Identificación de Paciente (CIP) basado en una secuencia de base de datos. Los códigos CIP son únicos, y si un CIP no se utiliza tras
	 * haber sido generado, no se vuelve a reutilizar.
	 * 
	 * @return String con el CIP gnerado
	 */
	private String getNextPersonalIdentificationCode() {
		Query q = entman.createNativeQuery("select nextval('myhealth.codigoidentificacionpaciente')");
		return Constants.PERSONAL_IDENTIFICATION_CODE_PREFIX.concat(String.valueOf(q.getSingleResult()));
	}

	/**
	 * Cambia el médico de familia asignado a un paciente. Recibe como parametros el identificador del paciente y el identificador del nuevo médico de familia
	 * 
	 * Si el identificador de paciente no corresponde a ningún paciente registrado se lanza una excepción. Si el identificador del médico de familia no corresponde a ningún médico
	 * de familia se lanza una excepción.
	 * 
	 * @return PatientTO (Transfer Object del paciente al que se la ha cambiado el médico de familia).
	 */
	public PatientTO changeFamilyDoctor(int patientId, int newDoctor) throws Exception {
		PatientJPA pat = entman.find(PatientJPA.class, patientId);
		if (pat == null) {
			throw new Exception("No se pueden actualizar los datos del paciente porque no se encuentra en la base de datos ningún registro con id: " + String.valueOf(patientId));
		}

		FamilyDoctorJPA fd = entman.find(FamilyDoctorJPA.class, newDoctor);
		if (fd == null) {
			throw new Exception(
					"No se pueden actualizar los datos del médico de familia porque no se encuentra en la base de datos ningún registro con id: " + String.valueOf(newDoctor));
		}

		pat.setFamilyDoctor(fd);
		entman.persist(pat);

		return this.commonServices.getPOJOforPatientJPA(pat, 1);
	}

	/**
	 * Registra un nuevo paciente en el sistema Recibe como parametros el nif del paciente, el nombre, los apellidos, la contraseña y la dirección de correo electronico.
	 * 
	 * El método genera un nuevo CIP (código de identificación de paciente) y lo asigna al nuevo paciente. El médico se familia queda está establecido a null, y deberá ser
	 * seleccionado por el paciente una vez registrado en el sistema.
	 * 
	 * La contraseña recibida se encrypta con el algoritmo MD5.
	 * 
	 * @return PatientTO Transfer Object correspondiente al paciente registrado.
	 */
	public PatientTO registerPatient(String nif, String name, String surname, String password, String email) {
		PatientJPA pat = new PatientJPA(this.getNextPersonalIdentificationCode(), nif, name, surname, HashUtils.hashMD5(password), email, null);
		entman.persist(pat);

		return this.commonServices.getPOJOforPatientJPA(pat, 1);
	}

	/**
	 * Registra un nuevo médico especialista en el sistema.
	 * 
	 * Recibe como parametros el nif, el nombre, los apellidos, la contraseña, la dirección de correo electronico y la especialidad.
	 * 
	 * El método genera un nuevo NIP (Número de Identificación de Profesional) y lo asigna al nuevo médico.
	 * 
	 * La contraseña recibida se encrypta con el algoritmo MD5.
	 * 
	 * El médoto comprueba que la especialidad médica recibida exista en el sistema en el momento de asignarla al nuevo médico, si la especialidad especificada no se encuentra se
	 * lanza una excepción.
	 * 
	 * @return SpecialistDoctorTO Transfer Object correspondiente al médico especialista registrado.
	 */
	public SpecialistDoctorTO registerSpecialistDoctor(String nif, String name, String surname, String password, String email, MedicalSpecialtyTO specialty)
			throws Exception {
		MedicalSpecialtyJPA ms = entman.find(MedicalSpecialtyJPA.class, specialty.getId());
		if (ms == null) {
			throw new Exception("No se encuentra la especialidad médica con identificador: " + specialty.getName());
		}

		SpecialistDoctorJPA sd = new SpecialistDoctorJPA(this.getNextProfessionalNumber(), nif, name, surname, HashUtils.hashMD5(password), email, ms);
		entman.persist(sd);

		return this.commonServices.getPOJOforSpecialistDoctorJPA(sd, 1);
	}

	/**
	 * Registra un nuevo médico de familia en el sistema.
	 * 
	 * Recibe como parametros el nif, el nombre, los apellidos, la contraseña, la dirección de correo electronico y el CAP (Centro Médico de Primaria).
	 * 
	 * El método genera un nuevo NIP (Número de Identificación de Profesional) y lo asigna al nuevo médico.
	 * 
	 * La contraseña recibida se encrypta con el algoritmo MD5.
	 * 
	 * El médoto comprueba que el CAP recibido exista en el sistema en el momento de asignarlo al nuevo médico, si el CAP no se encuentra se lanza una excepción.
	 * 
	 * @return FamilyDoctorTO Transfer Object correspondiente al médico de familia registrado.
	 */
	public FamilyDoctorTO registerFamilyDoctor(String nif, String name, String surname, String password, String email, PrimaryHealthCareCenterTO cap) throws Exception {
		PrimaryHealthCareCenterJPA phcC = entman.find(PrimaryHealthCareCenterJPA.class, cap.getId());
		if (phcC == null) {
			throw new Exception("No se encuentra el centro de atención primaria con identificador: " + cap.getName());
		}

		FamilyDoctorJPA fd = new FamilyDoctorJPA(this.getNextProfessionalNumber(), nif, name, surname, HashUtils.hashMD5(password), email, phcC);
		entman.persist(fd);

		return commonServices.getPOJOforFamilyDoctorJPA(fd, 1);
	}

	/**
	 * Actualiza los datos personales de un paciente
	 * 
	 * Recibe como parametros el id del paciente a actualizar, el nif, el nombre, los apellidos, *la contraseña, la dirección de correo electronico (El medico de familia asignado
	 * no se modifica).
	 * 
	 * El id de paciente recibido se busca en el sistema para realizar la actualización de datos, sino se encuentra se lanza una excepción.
	 * 
	 * La contraseña recibida solo se actualiza si se recibe el parámetro (no es nulo ni cadena vacía). La contraseña recibida se encrypta con el algoritmo MD5
	 * 
	 * @return PatientTO Transfer Object correspondiente al paciente actualizado..
	 */
	public PatientTO updatePatientData(int id, String nif, String name, String surname, String password, String email) throws Exception {
		PatientJPA pat = entman.find(PatientJPA.class, id);

		if (pat == null) {
			throw new Exception("No se pueden actualizar los datos del paciente porque no se encuentra en la base de datos ningún registro con id: " + String.valueOf(id));
		}

		pat.setNif(nif);
		pat.setName(name);
		pat.setSurname(surname);
		// Solo cambia el password si se especifica uno nuevo
		if (password != null && password.trim().equals("") == false)
			pat.setPassword(HashUtils.hashMD5(password));
		pat.setEmail(email);

		entman.persist(pat);

		return this.commonServices.getPOJOforPatientJPA(pat, 1);
	}

	/**
	 * Actualiza los datos personales de un médico especialista
	 * 
	 * Recibe como parametros el id del médico a actualizar, nif, el nombre, los apellidos, *la contraseña, la dirección de correo electronico y la especialidad médcia que se debe
	 * asignar.
	 * 
	 * El id del médico recibido se busca en el sistema para realizar la actualización de datos, sino se encuentra se lanza una excepción.
	 * 
	 * La contraseña recibida solo se actualiza si se recibe el parámetro (no es nulo ni cadena vacía). La contraseña recibida se encrypta con el algoritmo MD5
	 * 
	 * El método comprueba que la especialidad recibida existe en el sistema, en caso contrario lanza una excepción.
	 * 
	 * @return SpecialistDoctorTO Transfer Object correspondiente al médico especialista que se ha actualizado.
	 */
	public SpecialistDoctorTO updateSpecialistDoctorData(int id, String nif, String name, String surname, String password, String email, MedicalSpecialtyTO specialty)
			throws Exception {
		SpecialistDoctorJPA sd = entman.find(SpecialistDoctorJPA.class, id);
		if (sd == null) {
			throw new Exception("No se pueden actualizar los datos del médico de familia porque no se encuentra en la base de datos ningún registro con id: " + String.valueOf(id));
		}

		MedicalSpecialtyJPA ms = entman.find(MedicalSpecialtyJPA.class, specialty.getId());
		if (ms == null) {
			throw new Exception("No se encuentra la especialidad médica con identificador: " + specialty.getName());
		}

		sd.setNif(nif);
		sd.setName(name);
		sd.setSurname(surname);
		// Solo cambia el password si se especifica uno nuevo
		if (password != null && password.trim().equals("") == false)
			sd.setPassword(HashUtils.hashMD5(password));
		sd.setEmail(email);
		sd.setMedicalSpecialty(ms);

		entman.persist(sd);

		return this.commonServices.getPOJOforSpecialistDoctorJPA(sd, 1);
	}

	/**
	 * Actualiza los datos personales de un médico de familia
	 * 
	 * Recibe como parametros el id del médico a actualizar, nif, el nombre, los apellidos, *la contraseña, la dirección de correo electronico, y el CAP al cual está asignado el
	 * médico de familia.
	 * 
	 * El id del médico recibido se busca en el sistema para realizar la actualización de datos, sino se encuentra se lanza una excepción.
	 * 
	 * La contraseña recibida solo se actualiza si se recibe el parámetro (no es nulo ni cadena vacía). La contraseña recibida se encrypta con el algoritmo MD5
	 * 
	 * El método comprueba que el CAP recibida existe en el sistema, en caso contrario lanza una excepción.
	 * 
	 * @return FamilyDoctorTO Transfer Object correspondiente al médico de familia que se ha actualizado.
	 */
	public FamilyDoctorTO updateFamilyDoctorData(int id, String nif, String name, String surname, String password, String email, PrimaryHealthCareCenterTO phcTO) throws Exception {
		FamilyDoctorJPA fd = entman.find(FamilyDoctorJPA.class, id);
		if (fd == null) {
			throw new Exception("No se pueden actualizar los datos del médico de familia porque no se encuentra en la base de datos ningún registro con id: " + String.valueOf(id));
		}

		PrimaryHealthCareCenterJPA phcC = entman.find(PrimaryHealthCareCenterJPA.class, phcTO.getId());
		if (phcC == null) {
			throw new Exception("No se encuentra el centro de atención primaria con identificador: " + phcTO.getName());
		}

		fd.setNif(nif);
		fd.setName(name);
		fd.setSurname(surname);
		// Solo cambia el password si se especifica uno nuevo
		if (password != null && password.trim().equals("") == false)
			fd.setPassword(HashUtils.hashMD5(password));
		fd.setEmail(email);
		fd.setPrimaryHealthCareCenter(phcC);

		entman.persist(fd);

		return this.commonServices.getPOJOforFamilyDoctorJPA(fd, 1);
	}

	/***
	 * Cambia el CAP (Centro de Atención Primaria) asignado a un médico de familia.
	 * 
	 * Recibe como parametros el identificador del médico y el identificador del nuevo CAP a asginar.
	 * 
	 * Si el identificador del médico no corresponde a ningún médico de familia registrado se lanza una excepción.
	 * 
	 * Si el identificador del CAP no corresponde a ningún CAP registrado en el sistema se lanza una excpeción.
	 * 
	 * @return FamilyDoctorTO (Transfer Object del médico de familia al que se la ha cambiado el CAP).
	 */
	public FamilyDoctorTO changePrimaryHealthCareCenter(int professionalId, PrimaryHealthCareCenterTO newCenter) throws Exception {
		FamilyDoctorJPA fd = entman.find(FamilyDoctorJPA.class, professionalId);
		if (fd == null) {
			throw new Exception("No se encuentra en la base de datos ningún médico de familia con id: " + String.valueOf(professionalId));
		}

		PrimaryHealthCareCenterJPA phcC = entman.find(PrimaryHealthCareCenterJPA.class, newCenter.getId());
		if (phcC == null) {
			throw new Exception("No se encuentra el centro de atención primaria con identificador: " + newCenter.getName());
		}

		fd.setPrimaryHealthCareCenter(phcC);
		entman.persist(fd);

		return this.commonServices.getPOJOforFamilyDoctorJPA(fd, 1);
	}
}
