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.CodeableConcept;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Identifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.PractitionerRoleToFHIRPractitionerRoleTransformer;
import uk.nhs.careconnect.ri.database.entity.codeSystem.ConceptEntity;
import uk.nhs.careconnect.ri.database.entity.organization.OrganisationEntity;
import uk.nhs.careconnect.ri.database.entity.practitioner.*;
import uk.org.hl7.fhir.core.Stu3.CareConnectSystem;

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 PractitionerRoleDao implements PractitionerRoleRepository {

    @PersistenceContext
    EntityManager em;

    @Autowired
    private PractitionerRoleToFHIRPractitionerRoleTransformer
            practitionerRoleToFHIRPractitionerRoleTransformer;

    @Autowired
    private OrganisationRepository organisationDao;

    @Autowired
    private PractitionerRepository practitionerDao;

    @Autowired
    private CodeSystemRepository codeSystemDao;

    @Autowired
    private ConceptRepository codeDao;

    @Autowired
    private LibDao libDao;

    @Override
    public void save(FhirContext ctx, PractitionerRole practitioner) {

    }

    @Override
    public org.hl7.fhir.dstu3.model.PractitionerRole read(FhirContext ctx, IdType theId) {

        if (daoutils.isNumeric(theId.getIdPart())) {
            PractitionerRole roleEntity = (PractitionerRole) em.find(PractitionerRole.class, Long.parseLong(theId.getIdPart()));
            return roleEntity == null
                    ? null
                    : practitionerRoleToFHIRPractitionerRoleTransformer.transform(roleEntity);
        } else {
            return null;
        }

    }

    @Override
    public Long count() {

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

    @Override
    public PractitionerRole readEntity(FhirContext ctx, IdType theId) {
       return (PractitionerRole) em.find(PractitionerRole.class,Long.parseLong(theId.getIdPart()));
    }

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


    @Override
    public org.hl7.fhir.dstu3.model.PractitionerRole create(FhirContext ctx, org.hl7.fhir.dstu3.model.PractitionerRole practitionerRole, IdType theId, String theConditional) throws OperationOutcomeException  {

        PractitionerRole roleEntity = null;
        if (practitionerRole.hasId()) {
            roleEntity = (PractitionerRole) em.find(PractitionerRole.class, Long.parseLong(practitionerRole.getId()));
        }
        log.trace("theConditionalUrl = "+theConditional);
        if (theConditional != null) {
            try {
                log.trace("Contains is not null");
                //CareConnectSystem.ODSPractitionerCode
                if (theConditional.contains("fhir.nhs.uk/Id/sds-user-id")) {
                    log.trace("Contains "+theConditional);
                    URI uri = new URI(theConditional);

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

                    List<PractitionerRole> results = searchEntity(ctx, new TokenParam().setValue(spiltStr[1]).setSystem(CareConnectSystem.SDSUserId), null, null,null);
                    for (PractitionerRole org : results) {
                        roleEntity = org;
                        break;
                    }
                } else {
                    log.trace("NOT SUPPORTED: Conditional Url = " + theConditional);
                }

            } catch (Exception ex) {
                log.error("Exception "+ex.getMessage());
            }
        }
        if (roleEntity == null) {
            roleEntity = new PractitionerRole();
        }
        if (practitionerRole.getPractitioner() != null) {
            roleEntity.setPractitioner(practitionerDao.readEntity(ctx, new IdType(practitionerRole.getPractitioner().getReference())));
        }

        if (practitionerRole.getOrganization() != null) {
            roleEntity.setOrganisation(organisationDao.readEntity(ctx, new IdType(practitionerRole.getOrganization().getReference())));
        }

        if (practitionerRole.getCode().size() > 0) {
            if (practitionerRole.getCode().get(0).getCoding().get(0).getSystem().equals(CareConnectSystem.SDSJobRoleName)) {
                roleEntity.setRole(codeDao.findCode(practitionerRole.getCode().get(0).getCoding().get(0)));
            }
        }
        em.persist(roleEntity);

        for (CodeableConcept specialty : practitionerRole.getSpecialty()) {
            Boolean found = false;
            ConceptEntity specialtyConcept = codeDao.findCode(specialty.getCoding().get(0));
            for (PractitionerSpecialty searchSpecialty : roleEntity.getSpecialties()) {
                log.trace("Already has specialty = " + searchSpecialty.getSpecialty().getCode() + " code "+searchSpecialty.getSpecialty().getSystem());
                if (searchSpecialty.getSpecialty().getCode().equals(specialtyConcept.getCode())
                        && searchSpecialty.getSpecialty().getSystem().equals(specialtyConcept.getSystem())) found = true;
            }
            try {
                if (!found){
                    log.trace("not found! specialty = " + specialty.getCoding().get(0).getCode() + " code "+specialty.getCoding().get(0).getSystem());
                    PractitionerSpecialty practitionerSpecialty = new PractitionerSpecialty();
                    practitionerSpecialty.setPractitionerRole(roleEntity);
                    practitionerSpecialty.setSpecialty(specialtyConcept);
                    em.persist(practitionerSpecialty);
                    roleEntity.getSpecialties().add(practitionerSpecialty);
                }
            } catch (Exception ex) {

            }
        }

        for (PractitionerRoleIdentifier orgSearch : roleEntity.getIdentifiers()) {
            em.remove(orgSearch);
        }

        for (Identifier identifier : practitionerRole.getIdentifier()) {
            log.trace("Recieved identifier = " + identifier.getSystem() + " code "+identifier.getValue());

            PractitionerRoleIdentifier practitionerRoleIdentifier = null;
/*
            for (PractitionerRoleIdentifier orgSearch : roleEntity.getIdentifiers()) {
                if (identifier.getSystem().equals(orgSearch.getSystemUri()) && identifier.getValue().equals(orgSearch.getValue())) {
                    practitionerRoleIdentifier = orgSearch;
                    break;
                }
            }
            */
            if (practitionerRoleIdentifier == null) {
                practitionerRoleIdentifier = new PractitionerRoleIdentifier();

                log.trace("Not found Identifier!");


                practitionerRoleIdentifier.setPractitionerRole(roleEntity);
                practitionerRoleIdentifier= (PractitionerRoleIdentifier) libDao.setIdentifier(identifier,  practitionerRoleIdentifier);
                em.persist(practitionerRoleIdentifier);
                roleEntity.getIdentifiers().add(practitionerRoleIdentifier);
            }
        }
        return practitionerRole; //roleEntity;
    }

    @Override
    public List<PractitionerRole> searchEntity(FhirContext ctx,
            TokenParam identifier
            , ReferenceParam practitioner
            , ReferenceParam organisation
            , StringParam resid) {

        CriteriaBuilder builder = em.getCriteriaBuilder();

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

        List<Predicate> predList = new LinkedList<Predicate>();

        if (identifier !=null)
        {
            log.trace("Search on value = "+identifier.getValue());

            Join<PractitionerRole, PractitionerRoleIdentifier> 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 (resid != null) {
            Predicate p = builder.equal(root.get("id"),resid.getValue());
            predList.add(p);
        }
        if (practitioner != null) {
            Join<PractitionerRole, PractitionerEntity> joinPractitioner = root.join("practitionerEntity",JoinType.LEFT);
            Predicate p = null;
            if (daoutils.isNumeric(practitioner.getIdPart())) {
                p = builder.equal(joinPractitioner.get("id"),practitioner.getIdPart());
            } else {
                p = builder.equal(joinPractitioner.get("id"),-1);
            }

            predList.add(p);
        }
        if (organisation != null) {
            Join<PractitionerRole, OrganisationEntity> joinOrganisation = root.join("managingOrganisation",JoinType.LEFT);
            Predicate p = null;
            if (daoutils.isNumeric(organisation.getIdPart())) {
                p = builder.equal(joinOrganisation.get("id"), organisation.getIdPart());
            } else {
                p = builder.equal(joinOrganisation.get("id"), -1);
            }
            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);
        }

        return em.createQuery(criteria).setMaxResults(daoutils.MAXROWS).getResultList();

    }


    @Override
    public List<org.hl7.fhir.dstu3.model.PractitionerRole> search(FhirContext ctx,
            TokenParam identifier
            , ReferenceParam practitioner
            , ReferenceParam organisation
            , StringParam resid) {
        List<org.hl7.fhir.dstu3.model.PractitionerRole> results = new ArrayList<>();
        List<PractitionerRole> roles = searchEntity(ctx, identifier,practitioner,organisation,resid);
        for (PractitionerRole role : roles) {
             results.add(practitionerRoleToFHIRPractitionerRoleTransformer.transform(role));
        }
        return results;
    }

}