package org.slc.sli.ingestion.transformation;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.PropertyUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.slc.sli.domain.Entity;
import org.slc.sli.domain.EntityMetadataKey;
import org.slc.sli.domain.NeutralQuery;
import org.slc.sli.domain.Repository;
import org.slc.sli.ingestion.NeutralRecord;
import org.slc.sli.ingestion.NeutralRecordEntity;
import org.slc.sli.ingestion.handler.Handler;
import org.slc.sli.ingestion.transformation.normalization.EntityConfig;
import org.slc.sli.ingestion.transformation.normalization.EntityConfigFactory;
import org.slc.sli.ingestion.transformation.normalization.IdNormalizer;
import org.slc.sli.ingestion.validation.DummyErrorReport;
import org.slc.sli.ingestion.validation.ErrorReport;

/**
 * EdFi to SLI data transformation
 *
 * @author okrook
 *
 */
public abstract class EdFi2SLITransformer implements Handler<NeutralRecord, List<SimpleEntity>> {

    private static final Logger LOG = LoggerFactory.getLogger(EdFi2SLITransformer.class);

    private static final String METADATA_BLOCK = "metaData";

    private IdNormalizer idNormalizer;

    private EntityConfigFactory entityConfigurations;

    private Repository<Entity> entityRepository;

    @Override
    public List<SimpleEntity> handle(NeutralRecord item) {
        return handle(item, new DummyErrorReport());
    }

    @Override
    public List<SimpleEntity> handle(NeutralRecord item, ErrorReport errorReport) {
        resolveReferences(item, errorReport);

        if (errorReport.hasErrors()) {
            return Collections.emptyList();
        }

        List<SimpleEntity> transformed = transform(item, errorReport);

        if (errorReport.hasErrors()) {
            return Collections.emptyList();
        }


        if (transformed != null && !transformed.isEmpty()) {

            for (SimpleEntity entity : transformed) {

                   if (entity.getMetaData() == null) {
                    entity.setMetaData(new HashMap<String, Object>());
                }

                entity.getMetaData().put(EntityMetadataKey.TENANT_ID.getKey(), item.getSourceId());

                matchEntity(entity, errorReport);

                if (errorReport.hasErrors()) {
                    return Collections.emptyList();
                }
            }
        } else {
            LOG.error("EdFi2SLI Transform has resulted in either a null or empty list of transformed SimpleEntities.");            
        }

        return transformed;
    }

    public void resolveReferences(NeutralRecord item, ErrorReport errorReport) {
        Entity entity = new NeutralRecordEntity(item);

        EntityConfig entityConfig = entityConfigurations.getEntityConfiguration(entity.getType());

        idNormalizer.resolveInternalIds(entity, item.getSourceId(), entityConfig, errorReport);
    }

    /**
     * Find a matched entity in the data store. If match is found the EntityID gets updated with the
     * ID from the data store.
     *
     * @param entity
     *            Entity to match
     * @param entityConfig
     *            Configuration for the entity
     * @param errorReport
     *            Error reporting
     */
    public void matchEntity(SimpleEntity entity, ErrorReport errorReport) {
        EntityConfig entityConfig = entityConfigurations.getEntityConfiguration(entity.getType());

        Map<String, String> matchFilter = createEntityLookupFilter(entity, entityConfig, errorReport);

        if (errorReport.hasErrors()) {
            return;
        }

        Iterable<Entity> match = entityRepository.findAllByPaths(entity.getType(), matchFilter, new NeutralQuery());
        if (match != null && match.iterator().hasNext()) {
            // Entity exists in data store.
            Entity matched = match.iterator().next();
            entity.setEntityId(matched.getEntityId());
            entity.getMetaData().putAll(matched.getMetaData());
        }
    }

    /**
     * Create entity lookup filter by fields
     *
     * @param entity : the entity to be looked up.
     * @param keyFields : the list of the fields with which to generate the filter
     * @param errorReport: error reporting
     * @return Look up filter
     *
     * @author tke
     */
    public Map<String, String> createEntityLookupFilter(SimpleEntity entity, EntityConfig entityConfig, ErrorReport errorReport) {
        Map<String, String> filter = new HashMap<String, String>();

        if (entityConfig.getKeyFields() == null || entityConfig.getKeyFields().size() == 0) {
            errorReport.fatal("Cannot find a match for an entity: No key fields specified", this);
        }

        String tenantId = entity.getMetaData().get(EntityMetadataKey.TENANT_ID.getKey()).toString();
        filter.put(METADATA_BLOCK + "." + EntityMetadataKey.TENANT_ID.getKey(), tenantId);

        try {
            for (String field : entityConfig.getKeyFields()) {
                //TODO this does not support using anything from a list
                Object fieldValue = PropertyUtils.getProperty(entity, field);
                filter.put(field, (String) fieldValue);
            }
        } catch (Exception e) {
            errorReport.error("Invalid key fields", this);
        }

        return filter;
    }

    protected abstract List<SimpleEntity> transform(NeutralRecord item, ErrorReport errorReport);

    public IdNormalizer getIdNormalizer() {
        return idNormalizer;
    }

    public void setIdNormalizer(IdNormalizer idNormalizer) {
        this.idNormalizer = idNormalizer;
    }

    public EntityConfigFactory getEntityConfigurations() {
        return entityConfigurations;
    }

    public void setEntityConfigurations(EntityConfigFactory entityConfigurations) {
        this.entityConfigurations = entityConfigurations;
    }

    public Repository<Entity> getEntityRepository() {
        return entityRepository;
    }

    public void setEntityRepository(Repository<Entity> entityRepository) {
        this.entityRepository = entityRepository;
    }

}

@@ -545,24 +545,90 @@
 er.Handler;%0A
+import org.slc.sli.ingestion.transformation.normalization.RefDef;%0A
 import org.s
@@ -4634,24 +4634,99 @@
 String%3E();%0A%0A
+        String errorMessage = %22ERROR: Invalid key fields for an entity%5Cn%22;%0A
         if (
@@ -4907,32 +4907,633 @@
 this);%0A        %7D
+%0A        else %7B%0A            errorMessage += %22       Entity      %22 + entity.getType() + %22%5Cn%22%0A                          + %22       Key Fields  %22 + entityConfig.getKeyFields() + %22%5Cn%22;%0A            if (entityConfig.getReferences() != null && entityConfig.getReferences().size() %3E 0) %7B%0A                errorMessage += %22     The following collections are referenced by the key fields:%22 + %22%5Cn%22;%0A                for (RefDef refDef : entityConfig.getReferences()) %7B%0A                    errorMessage += %22       collection = %22 + refDef.getRef().getCollectionName() + %22%5Cn%22;%0A                %7D%0A            %7D%0A        %7D
 %0A%0A        String
@@ -6074,28 +6074,20 @@
 ror(
-%22Invalid key fields%22
+errorMessage
 , th

Add more detail to Invalid Key Fields error message