package uk.nhs.careconnect.ri.stu3.dao;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import org.hl7.fhir.dstu3.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Repository;
import uk.nhs.careconnect.fhir.OperationOutcomeException;
import uk.nhs.careconnect.ri.database.daointerface.*;
import uk.nhs.careconnect.ri.stu3.dao.transforms.ScheduleEntityToFHIRScheduleTransformer;
import uk.nhs.careconnect.ri.database.entity.location.LocationEntity;
import uk.nhs.careconnect.ri.database.entity.practitioner.PractitionerEntity;
import uk.nhs.careconnect.ri.database.entity.practitioner.PractitionerRole;
import uk.nhs.careconnect.ri.database.entity.schedule.ScheduleEntity;
import uk.nhs.careconnect.ri.database.entity.schedule.ScheduleIdentifier;
import uk.nhs.careconnect.ri.database.entity.schedule.*;
import uk.nhs.careconnect.ri.database.entity.healthcareService.*;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.*;
import javax.transaction.Transactional;
import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

@Repository
@Transactional
public class ScheduleDao implements ScheduleRepository {

    @PersistenceContext
    EntityManager em;


    @Autowired
    ScheduleEntityToFHIRScheduleTransformer scheduleEntityToFHIRScheduleTransformer;

    @Autowired
    @Lazy
    ConceptRepository conceptDao;

    @Autowired
    PatientRepository patientDao;

    @Autowired
    OrganisationRepository organisationDao;

    @Autowired
    LocationRepository locationDao;

    @Autowired
    PractitionerRepository practitionerDao;

    @Autowired
    PractitionerRoleRepository practitionerRoleDao;

    @Autowired
    private LibDao libDao;

    @Autowired
    HealthcareServiceRepository healthcareServiceDao;

    @Autowired
    private CodeSystemRepository codeSystemSvc;

    private static final Logger log = LoggerFactory.getLogger(ScheduleDao.class);


    @Override
    public void save(FhirContext ctx, ScheduleEntity scheduleEntity) {
        em.persist(scheduleEntity);
    }

    @Override
    public Schedule read(FhirContext ctx, IdType theId) {

        if (daoutils.isNumeric(theId.getIdPart())) {
            ScheduleEntity scheduleEntity = (ScheduleEntity) em.find(ScheduleEntity.class, Long.parseLong(theId.getIdPart()));
            return scheduleEntity == null
                    ? null
                    : scheduleEntityToFHIRScheduleTransformer.transform(scheduleEntity);

        } else {
            return null;
        }
    }

    @Override
    public ScheduleEntity readEntity(FhirContext ctx, IdType theId) {
        if (daoutils.isNumeric(theId.getIdPart())) {
            ScheduleEntity scheduleEntity = (ScheduleEntity) em.find(ScheduleEntity.class, Long.parseLong(theId.getIdPart()));

            return scheduleEntity;

        } else {
            return null;
        }
    }

    @Override
    public Schedule create(FhirContext ctx, Schedule schedule, IdType theId, String theConditional) throws OperationOutcomeException  {
        log.debug("Schedule.save");

        ScheduleEntity scheduleEntity = null;

        if (schedule.hasId()) scheduleEntity = readEntity(ctx, schedule.getIdElement());

        if (theConditional != null) {
            try {
                if (theConditional.contains("https://tools.ietf.org/html/rfc4122")) {
                    URI uri = new URI(theConditional);

                    String scheme = uri.getScheme();
                    log.info("** Scheme = "+scheme);
                    String host = uri.getHost();
                    log.info("** Host = "+host);
                    String query = uri.getRawQuery();
                    log.debug(query);
                    String[] spiltStr = query.split("%7C");
                    log.debug(spiltStr[1]);

                    List<ScheduleEntity> results = searchScheduleEntity(ctx,  new TokenParam().setValue(spiltStr[1]).setSystem("https://tools.ietf.org/html/rfc4122"),null); //,null
                    for (ScheduleEntity con : results) {
                        scheduleEntity = con;
                        break;
                    }
                } else {
                    log.info("NOT SUPPORTED: Conditional Url = "+theConditional);
                }

            } catch (Exception ex) {

            }
        }

        if (scheduleEntity == null) {
            scheduleEntity = new ScheduleEntity();
        }

        if (schedule.hasActive()) {
            scheduleEntity.setActive(schedule.getActive());
        }


        em.persist(scheduleEntity);
        log.debug("Schedule.saveIdentifier");
        for (Identifier identifier : schedule.getIdentifier()) {
            ScheduleIdentifier scheduleIdentifier = null;

            for (ScheduleIdentifier orgSearch : scheduleEntity.getIdentifiers()) {
                if (identifier.getSystem().equals(orgSearch.getSystemUri()) && identifier.getValue().equals(orgSearch.getValue())) {
                    scheduleIdentifier = orgSearch;
                    break;
                }
            }
            if (scheduleIdentifier == null)  scheduleIdentifier = new ScheduleIdentifier();

            scheduleIdentifier= (ScheduleIdentifier) libDao.setIdentifier(identifier, scheduleIdentifier );
            scheduleIdentifier.setSchedule(scheduleEntity);
            em.persist(scheduleIdentifier);
        }

        for (Reference actorRef :schedule.getActor()) {
            ScheduleActor actor = new ScheduleActor();
            actor.setScheduleEntity(scheduleEntity);
            if (actorRef.getReference().contains("Practitioner")) {


                PractitionerEntity practitionerEntity = practitionerDao.readEntity(ctx, new IdType(actorRef.getReference()));
                if (practitionerEntity != null) {
                    actor.setPractitionerEntity(practitionerEntity);
                }
            }
            if (actorRef.getReference().contains("PractitonerRole")) {

                PractitionerRole practitionerRole = practitionerRoleDao.readEntity(ctx, new IdType(actorRef.getReference()));
                if (practitionerRole != null) {
                    actor.setPractitionerRole(practitionerRole);
                }
            }
            if (actorRef.getReference().contains("HealthcareService")) {


                HealthcareServiceEntity healthcareService = healthcareServiceDao.readEntity(ctx, new IdType(actorRef.getReference()));
                if (healthcareService != null) {
                    actor.setHealthcareServiceEntity(healthcareService);
                }
            }
            if (actorRef.getReference().contains("Location")) {


                LocationEntity location = locationDao.readEntity(ctx, new IdType(actorRef.getReference()));
                if (location != null) {
                    actor.setLocationEntity(location);
                }
            }



            em.persist(actor);

        }
        em.persist(scheduleEntity);
        log.info("Schedule.Transform");
        return scheduleEntityToFHIRScheduleTransformer.transform(scheduleEntity);

    }

    @Override
    public List<Schedule> searchSchedule(FhirContext ctx, TokenParam identifier,  StringParam id) { // , ReferenceParam organisation
        List<ScheduleEntity> qryResults = searchScheduleEntity(ctx,identifier,id); //,organisation
        List<Schedule> results = new ArrayList<>();

        for (ScheduleEntity scheduleEntity : qryResults) {
            Schedule schedule = scheduleEntityToFHIRScheduleTransformer.transform(scheduleEntity);
            results.add(schedule);
        }

        return results;
    }

    @Override
    public List<ScheduleEntity> searchScheduleEntity(FhirContext ctx, TokenParam identifier, StringParam id) { // , ReferenceParam organisation
        List<ScheduleEntity> qryResults = null;

        CriteriaBuilder builder = em.getCriteriaBuilder();

        CriteriaQuery<ScheduleEntity> criteria = builder.createQuery(ScheduleEntity.class);
        Root<ScheduleEntity> root = criteria.from(ScheduleEntity.class);


        List<Predicate> predList = new LinkedList<Predicate>();
        List<Organization> results = new ArrayList<Organization>();

        if (identifier !=null)
        {
            Join<ScheduleEntity, ScheduleIdentifier> join = root.join("identifiers", JoinType.LEFT);

            Predicate p = builder.equal(join.get("value"),identifier.getValue());
            predList.add(p);
            // TODO predList.add(builder.equal(join.get("system"),identifier.getSystem()));

        }
        if (id != null) {
            Predicate p = builder.equal(root.get("id"),id.getValue());
            predList.add(p);
        }


        Predicate[] predArray = new Predicate[predList.size()];
        predList.toArray(predArray);
        if (predList.size()>0)
        {
            criteria.select(root).where(predArray);
        }
        else
        {
            criteria.select(root);
        }

        qryResults = em.createQuery(criteria).setMaxResults(daoutils.MAXROWS).getResultList();

        return qryResults;
    }

    @Override
    public Long count() {
        CriteriaBuilder qb = em.getCriteriaBuilder();
        CriteriaQuery<Long> cq = qb.createQuery(Long.class);
        cq.select(qb.count(cq.from(ScheduleEntity.class)));
        return em.createQuery(cq).getSingleResult();
    }
}