package ejb.systemAdmin;

import java.util.ArrayList;
import java.util.List;

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

import TO.FamilyDoctorTO;
import TO.LoggedUserTO;
import TO.MedicalSpecialtyTO;
import TO.PatientTO;
import TO.PrimaryHealthCareCenterTO;
import TO.SpecialistDoctorTO;
import TO.SystemAdminTO;
import common.Constants;
import common.HashUtils;
import common.UserType;
import ejb.common.CommonFacadeLocal;
import jpa.SystemAdminJPA;
import jpa.FamilyDoctorJPA;
import jpa.MedicalSpecialtyJPA;
import jpa.PrimaryHealthCareCenterJPA;

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

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

	@EJB
	CommonFacadeLocal commonServices;

	/**
	 * Si la autenticación no es correcgta devuelve null, sino devuelve un POJO con datos del usuario logeado.
	 * 
	 * La autenticación se realiza en 2 pasos:
	 * 
	 * Paso 1. Se intenta localizar un registro de usuario, por orden:
	 * 
	 * a. Primero Paciente, si el identificador comienza por los caracteres correctos.
	 * 
	 * b.Después médico de familia, si el identificador es de profesional
	 * 
	 * c. Si no lo localizamos buscamos el identificador en la tabla de médicos especialistas (el identificador es de profesional)
	 * 
	 * d. Si no hemos localizado aún al usuario, lo buscamos en la tabla de administradores, aún cuando el identificador comience por cualquier carácter (podría ser una dirección
	 * de email que comienza por caracteres del identificaodr de paciente o profesional)
	 * 
	 * Paso 2. Si hemos localizado un registro de usuario, verificamos si el password recibido coincide con el de la base de datos, en tal caso la autenticación se compelta y es
	 * correcta.
	 */
	public LoggedUserTO login(String userCode, String pwd) {
		LoggedUserTO usr = null;

		if (userCode == null)
			return usr;
		
		userCode = userCode.trim().toUpperCase();
		
		if (userCode.startsWith(Constants.PERSONAL_IDENTIFICATION_CODE_PREFIX)) {
			// Si el identificador de usuario es de tipo paciente, intentamos realizar el
			// login.
			PatientTO pat = this.commonServices.findPatientByCode(userCode);
			if (pat != null) {
				usr = new LoggedUserTO(String.valueOf(pat.getId()), pat.getName(), pat.getPassword(), UserType.PATIENT, pat.getDisplayName());
			}
		} else if (userCode.startsWith(Constants.PROFESSIONAL_NUMBER_PREFIX)) {
			// Si el identificador de usuario es de tipo profesional, intentamos realizar el
			// login, primero como médico de familia, después como especialista
			FamilyDoctorTO fd = this.commonServices.findFamilyDoctorByCode(userCode);

			if (fd != null) {
				usr = new LoggedUserTO(String.valueOf(fd.getId()), fd.getName(), fd.getPassword(), UserType.FAMILY_DOCTOR, fd.getDisplayName());
			} else {
				// No era un código de médico de familia, intenamos logearlo como especialista
				SpecialistDoctorTO sd = this.commonServices.findSpecialistDoctorByCode(userCode);

				if (sd != null) {
					usr = new LoggedUserTO(String.valueOf(sd.getId()), sd.getName(), sd.getPassword(), UserType.SPECIALIST_DOCTOR, sd.getDisplayName());
				}
			}
		}

		// Si todavía no hemos conseguido autenticar al usuario, podría tratarse de una
		// dirección de email de un administrador que empiece por
		// PERSONAL_IDENTIFICATION_CODE_PREFIX o por PROFESSIONAL_NUMBER_PREFIX, por lo
		// cual intentamos login contra la tabla de administradores
		if (usr == null) {
			// Intentamos recuperar un registro de administrador
			userCode = userCode.toLowerCase();
			SystemAdminJPA adm = entman.find(SystemAdminJPA.class, userCode);

			if (adm != null) {
				usr = new LoggedUserTO(adm.getEmail(), adm.getEmail(), adm.getPassword(), UserType.ADMINISTRATOR, adm.getEmail());
			}
		}

		// Si el objeto usr, es que hemos localizado un registro de usuario, verificamos
		// la contraseña.
		if (usr != null) {
			// Comprobamos el password
			if (usr.getPassword().equals(HashUtils.hashMD5(pwd)) == false) {
				// Bad Password, devolvemos null! La autenticación falla.
				usr = null;
			}
		}

		return usr;
	}

	public List<MedicalSpecialtyTO> listSpecialtiesPaged(int pageNumber, int pageSize) {
		TypedQuery<MedicalSpecialtyJPA> query = entman.createQuery("SELECT s from MedicalSpecialtyJPA s order by s.name", MedicalSpecialtyJPA.class);

		if (pageSize > 0) {
			query.setFirstResult(pageNumber * pageSize);
			query.setMaxResults(pageSize);
		}

		List<MedicalSpecialtyJPA> allJPA = query.getResultList();
		List<MedicalSpecialtyTO> specialties = new ArrayList<MedicalSpecialtyTO>();

		for (MedicalSpecialtyJPA item : allJPA) {
			specialties.add(commonServices.getPOJOforMedicalSpecialtyJPA(item));
		}

		return specialties;
	}
	
	public Long getSpecialtiesCount() {
		TypedQuery<Long> query = entman.createQuery("SELECT count(1) from MedicalSpecialtyJPA", Long.class);
		
		return query.getSingleResult();
	}
	
	public MedicalSpecialtyTO updateSpecialty(int id, String name, String description) throws Exception {
		MedicalSpecialtyJPA ms = entman.find(MedicalSpecialtyJPA.class, id);

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

		ms.setName(name);
		ms.setDescription(description);

		entman.persist(ms);

		return this.commonServices.getPOJOforMedicalSpecialtyJPA(ms);
	}

	public MedicalSpecialtyTO findSpecialtyByName(String searchedName) {
		TypedQuery<MedicalSpecialtyJPA> query = entman.createQuery("from MedicalSpecialtyJPA ms where LOWER(ms.name)=LOWER(:name)", MedicalSpecialtyJPA.class);
		query.setMaxResults(1);
		query.setParameter("name", searchedName);

		List<MedicalSpecialtyJPA> results = query.getResultList();
		if (results.size() > 0)
			return this.commonServices.getPOJOforMedicalSpecialtyJPA(results.get(0));
		else
			return null;
	}

	public void deleteSpecialty(int id) throws Exception {
		MedicalSpecialtyJPA ms = entman.find(MedicalSpecialtyJPA.class, id);

		if (ms == null) {
			throw new Exception("No se puede borrar la especialidad porque no se encuentra en la base de datos ningún registro con id: " + String.valueOf(id));
		}

		entman.remove(ms);
	}

	public MedicalSpecialtyTO insertSpecialty(String name, String description) throws Exception {
		MedicalSpecialtyJPA ms = new MedicalSpecialtyJPA(name, description);
		entman.persist(ms);

		return this.commonServices.getPOJOforMedicalSpecialtyJPA(ms);
	}
	
	public PrimaryHealthCareCenterTO updateHealthCareCenter(int id, String name, String location) throws Exception {
		PrimaryHealthCareCenterJPA ms = entman.find(PrimaryHealthCareCenterJPA.class, id);

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

		ms.setName(name);
		ms.setLocation(location);

		entman.persist(ms);

		return this.commonServices.getPOJOforPrimaryHealthCareCenterJPA(ms);
	}
	
	public PrimaryHealthCareCenterTO findHealthCareCenterByName(String searchedName) {
		TypedQuery<PrimaryHealthCareCenterJPA> query = entman.createQuery("from PrimaryHealthCareCenterJPA cap where LOWER(cap.name)=LOWER(:name)", PrimaryHealthCareCenterJPA.class);
		query.setMaxResults(1);
		query.setParameter("name", searchedName);

		List<PrimaryHealthCareCenterJPA> results = query.getResultList();
		if (results.size() > 0)
			return this.commonServices.getPOJOforPrimaryHealthCareCenterJPA(results.get(0));
		else
			return null;
	}
	
	public void deleteHealthCareCenter(int id) throws Exception {
		PrimaryHealthCareCenterJPA cap = entman.find(PrimaryHealthCareCenterJPA.class, id);

		if (cap == null) {
			throw new Exception("No se puede borrar el CAP porque no se encuentra en la base de datos ningún registro con id: " + String.valueOf(id));
		}

		entman.remove(cap);
	}
	
	public PrimaryHealthCareCenterTO insertHealthCareCenter(String name, String location) throws Exception {
		PrimaryHealthCareCenterJPA cap = new PrimaryHealthCareCenterJPA(name, location);
		entman.persist(cap);

		return this.commonServices.getPOJOforPrimaryHealthCareCenterJPA(cap);
	}
	
	public Long getCAPCount() {
		TypedQuery<Long> query = entman.createQuery("SELECT count(1) from PrimaryHealthCareCenterJPA", Long.class);
		
		return query.getSingleResult();
	}
	
	public List<PrimaryHealthCareCenterTO> listCAPsPaged(int pageNumber, int pageSize) {
		TypedQuery<PrimaryHealthCareCenterJPA> query = entman.createQuery("SELECT c from PrimaryHealthCareCenterJPA c order by c.name", PrimaryHealthCareCenterJPA.class);

		if (pageSize > 0) {
			query.setFirstResult(pageNumber * pageSize);
			query.setMaxResults(pageSize);
		}

		List<PrimaryHealthCareCenterJPA> allJPA = query.getResultList();
		List<PrimaryHealthCareCenterTO> caps = new ArrayList<PrimaryHealthCareCenterTO>();

		for (PrimaryHealthCareCenterJPA item : allJPA) {
			caps.add(commonServices.getPOJOforPrimaryHealthCareCenterJPA(item));
		}

		return caps;
	}

	public Long getFamilyDoctorsByCAPCount(int primaryHealthCareCenterId) {
		TypedQuery<Long> query = entman.createQuery("SELECT count(1) from FamilyDoctorJPA d where d.primaryHealthCareCenter.id=:capId", Long.class);
		query.setParameter("capId", primaryHealthCareCenterId);

		return query.getSingleResult();
	}
	
	public List<FamilyDoctorTO> listAllFamilyDoctorsByCAPPaged(int primaryHealthCareCenterId, int pageNumber, int pageSize) {
		List<FamilyDoctorTO> familyDoctorsByCAP = new ArrayList<FamilyDoctorTO>();

		TypedQuery<FamilyDoctorJPA> query = entman.createQuery("SELECT d from FamilyDoctorJPA d where d.primaryHealthCareCenter.id=:capId order by d.name asc, d.surname asc",
				FamilyDoctorJPA.class);
		query.setParameter("capId", primaryHealthCareCenterId);

		if (pageSize > 0) {
			query.setFirstResult(pageNumber * pageSize);
			query.setMaxResults(pageSize);
		}

		List<FamilyDoctorJPA> allJPA = query.getResultList();

		for (FamilyDoctorJPA item : allJPA) {
			familyDoctorsByCAP.add(commonServices.getPOJOforFamilyDoctorJPA(item, 1));
		}

		return familyDoctorsByCAP;
	}

	public Long getPatientCount(int familyDoctorId) {
		TypedQuery<Long> query = entman.createQuery("SELECT count(1) from PatientJPA p where p.familyDoctor.id=:familyDoctorId", Long.class);
		query.setParameter("familyDoctorId", familyDoctorId);

		return query.getSingleResult();
	}
	
	public SystemAdminTO findAdminByEmail(String email) {
		TypedQuery<SystemAdminJPA> query = entman.createQuery("from SystemAdminJPA a where LOWER(a.email)=LOWER(:email)", SystemAdminJPA.class);
		query.setMaxResults(1);
		query.setParameter("email", email);

		List<SystemAdminJPA> results = query.getResultList();
		if (results.size() > 0)
			return this.commonServices.getPOJOforSystemAdminJPA(results.get(0));
		else
			return null;
	}
	
	public SystemAdminTO insertAdmin(String email, String password) throws Exception {
		SystemAdminJPA admin = new SystemAdminJPA(email, password);
		entman.persist(admin);

		return this.commonServices.getPOJOforSystemAdminJPA(admin);
	}
}
