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

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.param.ReferenceParam;
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.ConsentEntityToFHIRConsentTransformer;
import uk.nhs.careconnect.ri.database.entity.codeSystem.ConceptEntity;
import uk.nhs.careconnect.ri.database.entity.careTeam.CareTeamEntity;
import uk.nhs.careconnect.ri.database.entity.consent.*;
import uk.nhs.careconnect.ri.database.entity.documentReference.DocumentReferenceEntity;
import uk.nhs.careconnect.ri.database.entity.organization.OrganisationEntity;
import uk.nhs.careconnect.ri.database.entity.patient.PatientEntity;
import uk.nhs.careconnect.ri.database.entity.practitioner.PractitionerEntity;
import uk.nhs.careconnect.ri.database.entity.questionnaireResponse.QuestionnaireResponseEntity;
import uk.nhs.careconnect.ri.database.entity.relatedPerson.RelatedPersonEntity;

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

@Repository
@Transactional
public class ConsentDao implements ConsentRepository {

    @PersistenceContext
    EntityManager em;

    @Autowired
    @Lazy
    ConceptRepository conceptDao;

    @Autowired
    PatientRepository patientDao;

    @Autowired
    PractitionerRepository practitionerDao;

    @Autowired
    @Lazy
    EncounterRepository encounterDao;

    @Autowired
    ConditionRepository conditionDao;

    @Autowired
    AllergyIntoleranceRepository allergyDao;

    @Autowired
    RiskAssessmentRepository riskDao;

    @Autowired
    ObservationRepository observationDao;

    @Autowired
    QuestionnaireResponseRepository formDao;

    @Autowired
    DocumentReferenceRepository documentDao;

    @Autowired
    OrganisationRepository organisationDao;

    @Autowired
    CareTeamRepository teamDao;

    @Autowired
    RelatedPersonRepository personDao;

    @Autowired
    private CodeSystemRepository codeSystemSvc;

    @Autowired
    private LibDao libDao;
    
    @Autowired
    ConsentEntityToFHIRConsentTransformer consentEntityToFHIRConsentTransformer;

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

    @Override
    public void save(FhirContext ctx, ConsentEntity
            consent) throws OperationOutcomeException {

    }

    @Override
    public Consent read(FhirContext ctx, IdType theId) {
        if (daoutils.isNumeric(theId.getIdPart())) {
            ConsentEntity consent = (ConsentEntity) em.find(ConsentEntity.class, Long.parseLong(theId.getIdPart()));
            return consentEntityToFHIRConsentTransformer.transform(consent);
        }
        return null;
    }

    @Override
    public Consent create(FhirContext ctx, Consent consent, IdType theId, String theConditional) throws OperationOutcomeException {
        log.debug("Consent.save");
        //  log.info(ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(encounter));
        ConsentEntity consentEntity = null;

        if (consent.hasId()) consentEntity = readEntity(ctx, consent.getIdElement());

        if (theConditional != null) {
            try {


                if (theConditional.contains("fhir.leedsth.nhs.uk/Id/consent")) {
                    URI uri = new URI(theConditional);

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

                    List<ConsentEntity> results = searchEntity(ctx, null, new TokenParam().setValue(spiltStr[1]).setSystem("https://fhir.leedsth.nhs.uk/Id/consent"),null);
                    for (ConsentEntity con : results) {
                        consentEntity = con;
                        break;
                    }
                } else {
                    log.info("NOT SUPPORTED: Conditional Url = "+theConditional);
                }

            } catch (Exception ex) {

            }
        }

        if (consentEntity == null) consentEntity = new ConsentEntity();

        if (consent.hasStatus()) {
            consentEntity.setStatus(consent.getStatus());
        }

        PatientEntity patientEntity = null;
        if (consent.hasPatient()) {
            log.trace(consent.getPatient().getReference());
            patientEntity = patientDao.readEntity(ctx, new IdType(consent.getPatient().getReference()));
            consentEntity.setPatient(patientEntity);
        }


        if (consent.hasPeriod() ) {
            try {
                consentEntity.setPeriodEndDateTime(consent.getPeriod().getEnd());
            } catch (Exception ex) {

            }
            try {
                consentEntity.setPeriodStartDateTime(consent.getPeriod().getStart());
            } catch (Exception ex) {

            }
        }
        if (consent.hasDateTime()) {
            try {
                consentEntity.setDateTime(consent.getDateTime());
            } catch (Exception ex) {

            }
        }

        if (consent.hasSourceIdentifier()) {
            try {
                consentEntity.setSourceSystem(consent.getSourceIdentifier().getSystem());
                consentEntity.setSourceValue(consent.getSourceIdentifier().getValue());
            } catch (Exception ex) {

            }
        }
        if (consent.hasSourceReference()) {
            try {
                if (consent.getSourceReference().getReference().contains("QuestionnaireResponse")) {
                    QuestionnaireResponseEntity questionnaireResponseEntity = formDao.readEntity(ctx, new IdType(consent.getSourceReference().getReference()));
                    consentEntity.setForm(questionnaireResponseEntity);
                }
                if (consent.getSourceReference().getReference().contains("DocumentReference")) {
                    DocumentReferenceEntity documentReferenceEntity = documentDao.readEntity(ctx, new IdType(consent.getSourceReference().getReference()));
                    consentEntity.setDocument(documentReferenceEntity);
                }
                consentEntity.setSourceSystem(consent.getSourceIdentifier().getSystem());
                consentEntity.setSourceValue(consent.getSourceIdentifier().getValue());
            } catch (Exception ex) {

            }
        }

        if (consent.hasPolicyRule()) {
            consentEntity.setPolicyRule(consent.getPolicyRule());
        }


        em.persist(consentEntity);


        if (consent.hasIdentifier()) {
            Identifier identifier = consent.getIdentifier();

            ConsentIdentifier
                    consentIdentifier = null;

            for (ConsentIdentifier orgSearch : consentEntity.getIdentifiers()) {
                if (identifier.getSystem().equals(orgSearch.getSystemUri()) && identifier.getValue().equals(orgSearch.getValue())) {
                    consentIdentifier = orgSearch;
                    break;
                }
            }
            if (consentIdentifier == null)  consentIdentifier = new ConsentIdentifier();

            consentIdentifier= (ConsentIdentifier) libDao.setIdentifier(identifier, consentIdentifier );
            consentIdentifier.setConsent(consentEntity);
            em.persist(consentIdentifier);

        }

        // Action

        for (ConsentAction consentAction : consentEntity.getActions()) {
            em.remove(consentAction);
        }
        for (CodeableConcept action : consent.getAction()) {
            ConsentAction consentAction = new ConsentAction();
            consentAction.setConsent(consentEntity);
            ConceptEntity concept = conceptDao.findAddCode(action.getCodingFirstRep());
            if (concept != null) consentAction.setActionCode(concept);

            em.persist(consentAction);
        }


        // Actor

        for (ConsentActor actor : consentEntity.getActors()) {
            em.remove(actor);
        }
        for (Consent.ConsentActorComponent actor : consent.getActor()) {
            ConsentActor consentActor = new ConsentActor();
            consentActor.setConsent(consentEntity);
            if (actor.hasRole()) {
                ConceptEntity concept = conceptDao.findAddCode(actor.getRole().getCodingFirstRep());
                if (concept != null) consentActor.setRoleCode(concept);
            }
            if (actor.hasReference()) {
                if (actor.getReference().getReference().contains("CareTeam")) {
                    CareTeamEntity careTeamEntity = teamDao.readEntity(ctx, new IdType(actor.getReference().getReference()));
                    consentActor.setReferenceCareTeam(careTeamEntity);
                }
                if (actor.getReference().getReference().contains("Organization")) {
                    OrganisationEntity organisationEntity = organisationDao.readEntity(ctx, new IdType(actor.getReference().getReference()));
                    consentActor.setReferenceOrganisation(organisationEntity);
                }
                if (actor.getReference().getReference().contains("Patient")) {
                    PatientEntity patientEntity1 = patientDao.readEntity(ctx, new IdType(actor.getReference().getReference()));
                    consentActor.setReferencePatient(patientEntity1);
                }
                if (actor.getReference().getReference().contains("Practitioner")) {
                    PractitionerEntity practitionerEntity = practitionerDao.readEntity(ctx, new IdType(actor.getReference().getReference()));
                    consentActor.setReferencePractitioner(practitionerEntity);
                }
                if (actor.getReference().getReference().contains("RelatedPerson")) {
                        RelatedPersonEntity relatedPersonEntity = personDao.readEntity(ctx, new IdType(actor.getReference().getReference()));
                        consentActor.setReferencePerson(relatedPersonEntity);
                }
            }

            em.persist(consentActor);
        }


        // Category

        for (ConsentCategory consentCategory : consentEntity.getCategories()) {
            em.remove(consentCategory);
        }
        for (CodeableConcept category : consent.getCategory()) {
            ConsentCategory consentCategory = new ConsentCategory();
            consentCategory.setConsent(consentEntity);
            ConceptEntity concept = conceptDao.findAddCode(category.getCodingFirstRep());
            if (concept != null) consentCategory.setCategory(concept);

            em.persist(consentCategory);
        }

        // Organisation
        for (ConsentOrganisation consentOrganisation : consentEntity.getOrganisations()) {
            em.remove(consentOrganisation);
        }
        for (Reference reference : consent.getOrganization()) {
            ConsentOrganisation consentOrganisation = new ConsentOrganisation();
            consentOrganisation.setConsent(consentEntity);

            OrganisationEntity organisationEntity = organisationDao.readEntity(ctx, new IdType(reference.getReference()));
            consentOrganisation.setOrganisation(organisationEntity);

            em.persist(consentOrganisation);
        }
        // Party
        for (ConsentParty party : consentEntity.getParties()) {
            em.remove(party);
        }
        for (Reference reference : consent.getConsentingParty()) {
            ConsentParty consentParty = new ConsentParty();
            consentParty.setConsent(consentEntity);
            if (reference.hasReference()) {
                if (reference.getReference().contains("Organization")) {
                        OrganisationEntity organisationEntity = organisationDao.readEntity(ctx, new IdType(reference.getReference()));
                        consentParty.setReferenceOrganisation(organisationEntity);
                }
                if (reference.getReference().contains("Patient")) {
                    PatientEntity patientEntity1 = patientDao.readEntity(ctx, new IdType(reference.getReference()));
                    consentParty.setReferencePatient(patientEntity1);
                }
                if (reference.getReference().contains("Practitioner")) {
                    PractitionerEntity practitionerEntity = practitionerDao.readEntity(ctx, new IdType(reference.getReference()));
                    consentParty.setReferencePractitioner(practitionerEntity);
                }
                if (reference.getReference().contains("RelatedPerson")) {
                        RelatedPersonEntity relatedPersonEntity = personDao.readEntity(ctx, new IdType(reference.getReference()));
                        consentParty.setReferencePerson(relatedPersonEntity);
                }
            }
            em.persist(consentParty);
        }
        // Policy

        for (ConsentPolicy policy : consentEntity.getPolicies()) {
            em.remove(policy);
        }
        for (Consent.ConsentPolicyComponent policy : consent.getPolicy()) {
            ConsentPolicy policyEntity = new ConsentPolicy();
            policyEntity.setConsent(consentEntity);
            if (policy.hasAuthority()) {
                policyEntity.setAuthority(policy.getAuthority());
            }
            if (policy.hasUri()) {
                policyEntity.setPolicyUri(policy.getUri());
            }
            em.persist(policyEntity);
        }

        // Purpose

        for (ConsentPurpose consentPurpose: consentEntity.getPurposes()) {
            em.remove(consentPurpose);
        }
        for (Coding coding : consent.getPurpose()) {
            ConsentPurpose consentPurpose = new ConsentPurpose();
            consentPurpose.setConsent(consentEntity);
            ConceptEntity concept = conceptDao.findAddCode(coding);
            if (concept != null) consentPurpose.setPurposeCode(concept);

            em.persist(consentPurpose);
        }

        return consentEntityToFHIRConsentTransformer.transform(consentEntity);
    }

    @Override
    public ConsentEntity readEntity(FhirContext ctx, IdType theId) {
        if (daoutils.isNumeric(theId.getIdPart())) {
            ConsentEntity consentIntolerance = (ConsentEntity) em.find(ConsentEntity.class, Long.parseLong(theId.getIdPart()));
            return consentIntolerance;
        }
        return null;
    }

    @Override
    public List<Consent> search(FhirContext ctx, ReferenceParam patient, TokenParam identifier, StringParam id) {
        List<ConsentEntity> qryResults = searchEntity(ctx,patient, identifier,id);
        List<Consent> results = new ArrayList<>();

        for (ConsentEntity consentIntoleranceEntity : qryResults)
        {
            // log.trace("HAPI Custom = "+doc.getId());
            Consent consent = consentEntityToFHIRConsentTransformer.transform(consentIntoleranceEntity);
            results.add(consent);
        }

        return results;
    }

    @Override
    public List<ConsentEntity> searchEntity(FhirContext ctx, ReferenceParam patient, TokenParam identifier, StringParam resid) {
        List<ConsentEntity> qryResults = null;

        CriteriaBuilder builder = em.getCriteriaBuilder();

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

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

        if (patient != null) {
            // KGM 4/1/2018 only search on patient id
            if (daoutils.isNumeric(patient.getIdPart())) {
                Join<ConsentEntity, PatientEntity> join = root.join("patient", JoinType.LEFT);

                Predicate p = builder.equal(join.get("id"), patient.getIdPart());
                predList.add(p);
            } else {
                Join<ConsentEntity, PatientEntity> join = root.join("patient", JoinType.LEFT);

                Predicate p = builder.equal(join.get("id"), -1);
                predList.add(p);
            }
        }
        if (resid != null) {
            Predicate p = builder.equal(root.get("id"),resid.getValue());
            predList.add(p);
        }
        if (identifier !=null)
        {
            Join<ConsentEntity, ConsentIdentifier> 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()));

        }

        
        ParameterExpression<Date> parameterLower = builder.parameter(Date.class);
        ParameterExpression<Date> parameterUpper = builder.parameter(Date.class);

        

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


        TypedQuery<ConsentEntity> typedQuery = em.createQuery(criteria).setMaxResults(daoutils.MAXROWS);
        
        qryResults = typedQuery.getResultList();

        return qryResults;
    }

    @Override
    public Long count() {
        CriteriaBuilder qb = em.getCriteriaBuilder();
        CriteriaQuery<Long> cq = qb.createQuery(Long.class);
        cq.select(qb.count(cq.from(ConsentEntity.class)));
        //cq.where(/*your stuff*/);
        return em.createQuery(cq).getSingleResult();
    }
}