package eu.dnetlib.data.claims.handler;

import eu.dnetlib.data.claims.entity.*;
import eu.dnetlib.data.claims.sql.SQLStoreException;
import eu.dnetlib.data.claims.sql.SqlDAO;
import eu.dnetlib.data.claims.utils.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Created by kiatrop on 5/2/2016.
 */
public class ClaimHandler {

    SqlDAO sqlDAO = null;
    QueryGenerator queryGenerator = null;
    ResultHandler resultHandler = null;
    ProjectHandler projectHandler = null;
    OrganizationHandler organizationHandler = null;
    ClaimValidation claimValidation = null;
    @Autowired
    IndexResultHandler indexResultHandler;
    ContextUtils contextUtils;
    ExternalRecordHandler externalRecordHandler;

    private static final Logger logger = LogManager.getLogger(ClaimHandler.class);

    /**
     * Saves a list of claims in DB
     * @param claims
     */
    public void saveClaims(List<Claim> claims){
        for(Claim claim:claims){
            try {
                saveClaim(claim);
            } catch (SQLStoreException e) {
                logger.error("Claim couldn't be saved : " + claim.getId() + "\n" + claim.toString(), e);
            } catch (SQLException e) {
                logger.error("Claim couldn't be saved : " + claim.getId() + "\n" + claim.toString(), e);
            }
        }
    }
   public String  generateSaveQueryForClaim(Claim claim, ArrayList<Object> params){

       String query= null;
       query = queryGenerator.generateInsertFullClaimQuery(claim, params);
       return query;
   }
    /**
     * saves a claim in DB
     * @param claim
     * @throws Exception
     */
    //TODO make private - used in Migration
    public String saveClaim(Claim claim) throws SQLStoreException, SQLException {
        logger.info("Saving claim...");
        String id = null;
        ArrayList<Object> params = new ArrayList<>();
        String query = generateSaveQueryForClaim(claim, params);
        ResultSet rs=sqlDAO.executePreparedQuery(query, params);
        if(rs.next()) {
            id = rs.getString(1);
        }
        rs.close();
        return id;
    }
    // - used in Migration
    public void saveOrphanClaimId(String id) throws SQLStoreException, Exception {
        logger.info("Saving orphan claim id...");

        String query = queryGenerator.generateInsertQueryForClaimsOrphanIds(id);
        sqlDAO.executeUpdateQuery(query);
    }

    /**
     * Gets the main information for a claim relation - builds a claim object
     * exports metadata
     * and saves the claim relations in the DB
     * @param user
     * @param sourceType
     * @param sourceId
     * @param sourceCollectedFrom
     * @param targetType
     * @param targetId
     * @param targetCollectedFrom
     * @throws Exception
     */
    public String buildAndInsertClaim(String user, String sourceType, String sourceId, String sourceCollectedFrom, String sourceAccessRights, String sourceEmbargoDate, String targetType, String targetId, String targetCollectedFrom, String targetAccessRights, String targetEmbargoDate, String claimedInDashboard, String idSuffix ) throws Exception, SQLStoreException {
        String id = null;

        claimValidation.validateCollectedFrom(targetCollectedFrom);
        claimValidation.validateCollectedFrom(sourceCollectedFrom);
        logger.info("Trying to create a claim {user:"+user+", sourceType:"+sourceType+", sourceId:"+sourceId+", sourceCollectedFrom:"+sourceCollectedFrom+", sourceAccessRights:"+sourceAccessRights
                +", sourceEmbargoDate:"+sourceEmbargoDate +", targetType:"+targetType +", targetId:"+targetId +", targetCollectedFrom:"+targetCollectedFrom +", targetAccessRights:"+targetAccessRights
                +", targetEmbargoDate:"+targetEmbargoDate+"}");
        Claim claim = buildClaim(user,sourceType,sourceId,sourceCollectedFrom, sourceAccessRights, sourceEmbargoDate, targetType, targetId, targetCollectedFrom, targetAccessRights, targetEmbargoDate, claimedInDashboard, idSuffix);
        if(claimValidation.validateClaim(claim)) {
            //TODO organization??
            claim = exportMedatataForClaim(claim);
            id = saveClaim(claim);
        }else{
            logger.info("claim is not valid");
        }
        return id;
    }

    /**
     * Gets the main information of a relation claim and
     * builds a claim object
     * @param user
     * @param sourceType publication, dataset, project, context
     * @param sourceId openaireId, doi or orcid workid
     * @param sourceCollectedFrom only for source with type publication, dataset
     * @param targetType publication, dataset, project, context
     * @param targetId openaireId, doi or orcid workid
     * @param targetCollectedFrom only for target with type publication, dataset
     * @return a claim object
     * @throws Exception
     */
    public Claim buildClaim(String user, String sourceType, String sourceId,String sourceCollectedFrom, String sourceAccessRights, String sourceEmbargoDate, String targetType,String targetId,String targetCollectedFrom, String targetAccessRights, String targetEmbargoDate, String claimedInDashboard, String idSuffix ) throws Exception {
        Claim claim = new Claim();
        claim.setUserMail(user);
        claim.setClaimedInDashboard(claimedInDashboard);
        claim.setDate(new Date());
        //date
        claim.setSourceType(sourceType);
        claim.setTargetType(targetType);
        claim.setSource(buildOpenaireEntity(sourceId,sourceType,sourceCollectedFrom,sourceAccessRights, sourceEmbargoDate, idSuffix));
        claim.setTarget(buildOpenaireEntity(targetId,targetType,targetCollectedFrom, targetAccessRights, targetEmbargoDate, idSuffix));

        if (claim.getSource() == null) {
           throw new ClaimValidationException("The source id is invalid.");
        }

        if (claim.getTarget() == null) {
            throw new ClaimValidationException("The target id is invalid.");
        }
        //TODO from external sources need an OPenaireID
        return claim;

    }

    public boolean updateClaimCurationInfo(String curatedBy, String claimId, boolean approved) throws SQLStoreException, SQLException, Exception {
        logger.info("Updating claim curation info...");
        ArrayList<Object> params = new ArrayList<>();
        String query = queryGenerator.generateUpdateClaimCuration(curatedBy, claimId, approved, params);
        //ResultSet rs = sqlDAO.executeUpdateQuery(query, params);
        return sqlDAO.executeUpdateQuery(query, params);

        /*
        boolean success = false;
        while (rs.next())
        {
            success = true;
        }
        rs.close();
        return success;
        */
    }


    /**
     * Gets the main information of an openaire entityinfrastruct_::openaire
     * and returns a full  object
     * @param id
     * @param type
     * @param collectedFrom
     * @return openaire Entity (Context, Result, Project)
     * @throws Exception
     */
    public OpenaireEntity buildOpenaireEntity(String id, String type, String collectedFrom, String accessRights, String embargoDate, String idSuffix) throws Exception {
        logger.debug(id+" "+ type+" "+collectedFrom);
        if(type==null){
            return null;
        }
        else if(type.equals(ClaimUtils.CONTEXT)){
            return contextUtils.fetchContextById(id);
        }else if (type.equals(ClaimUtils.PROJECT)){
            Project project = projectHandler.fetchProjectByID(id);
//            if(project == null){
//                project = projectHandler.fetchProjectByID(id);
//            }
            if(project == null){
                logger.error("Project with id:"+id + " couldn't be fetched.");
            }

            return project;
        }else if (type.equals(ClaimUtils.ORGANIZATION)){
            Organization organization = organizationHandler.fetchOrganizationByID(id);
            if(organization == null){
                logger.error("organization with id:"+id + " couldn't be fetched.");
            }

            return organization;
        }else if (type.equals(ClaimUtils.PUBLICATION)||type.equals(ClaimUtils.DATASET)||type.equals(ClaimUtils.SOFTWARE) ||type.equals(ClaimUtils.OTHER) ){
            if(collectedFrom == null){
                return null;
            }else if(collectedFrom.equals(ClaimUtils.CROSSREF)){

                Result result = externalRecordHandler.fetchResultfromCrossref(id, idSuffix);
                if(result == null){
                    logger.error("Record with id: " + id + " couldn't be fetched from crossref.");
                }

                result.setAccessRights(accessRights);
                result.setEmbargoEndDate(embargoDate);
                result.setResultType(type);
                return result;
            }else if (collectedFrom.equals(ClaimUtils.DATACITE)){
                Result result = externalRecordHandler.fetchResultfromDatacite(id, idSuffix);
                if(result == null){
                    logger.error("Record with id:"+id + " couldn't be fetched from datacite.");
                }else {
                    result.setAccessRights(accessRights);
                    result.setEmbargoEndDate(embargoDate);
                    result.setResultType(type);
                }
                return result;
            }else if (collectedFrom.equals(ClaimUtils.ORCID)){
                Result result = externalRecordHandler.fetchResultfromOrcid(id, idSuffix);
                if(result == null){
                    logger.error("Record with id:"+id + " couldn't be fetched from ORCID.");
                }else {
                    result.setAccessRights(accessRights);
                    result.setEmbargoEndDate(embargoDate);
                    result.setResultType(type);
                }
                return result;
            }else if (collectedFrom.equals(ClaimUtils.OPENAIRE)){
                Result result = null;
                if(indexResultHandler.searchUtils.getNewJsonSearchAPIUrl() != null){
                    result = indexResultHandler.fetchResultById(id, type);
                }else {
                    if (type.equals(ClaimUtils.PUBLICATION)) {
                        result = indexResultHandler.fetchPublicationById(id);

                    } else if (type.equals(ClaimUtils.DATASET)) {
                        result = indexResultHandler.fetchDatasetById(id);
                    } else if (type.equals(ClaimUtils.SOFTWARE)) {
                        result = indexResultHandler.fetchSoftwareById(id);
                    } else if (type.equals(ClaimUtils.OTHER)) {
                        result = indexResultHandler.fetchOtherById(id);
                    }
                }
                if(result == null){
                    logger.error("Record with id:"+id + " and type " + type + " couldn't be fetched from openaire.");
                }
//                replace openaire_id prefix with  userclaim___::
                indexResultHandler.updateOpenAIREIdPrefix(result);
                return result;
            }
        }
        return null;
    }


    /**
     * Exports the metadata for the source and the target of a claim and sets the proper field
     * @param claim
     * @return claim with the proper metadata path of the result of source and target
     */
    public Claim exportMedatataForClaim(Claim claim){
        if(claim.getTargetType().equals(ClaimUtils.DATASET)||claim.getTargetType().equals(ClaimUtils.PUBLICATION) ||claim.getTargetType().equals(ClaimUtils.SOFTWARE)||claim.getTargetType().equals(ClaimUtils.OTHER)){
            String path = resultHandler.exportMetadataFileForResult((Result)claim.getTarget());
            ((Result) claim.getTarget()).setRecordPath(path);
        }
        if(claim.getSourceType().equals(ClaimUtils.DATASET)||claim.getSourceType().equals(ClaimUtils.PUBLICATION)||claim.getSourceType().equals(ClaimUtils.SOFTWARE)||claim.getSourceType().equals(ClaimUtils.OTHER)){
            String path = resultHandler.exportMetadataFileForResult((Result)claim.getSource());
            ((Result) claim.getSource()).setRecordPath(path);
        }
        return claim;

    }

    public void deleteClaim(String user, String claimId) throws SQLStoreException, Exception {

        ArrayList<Object> params = new ArrayList<>();
        String query = queryGenerator.generateSelectClaimQuery(claimId,user, params);
        ResultSet rs = sqlDAO.executePreparedQuery(query, params);
        if(rs.next()) {
            String sourceType =rs.getString(2);
            String sourceId =rs.getString(3);
            String targetType =rs.getString(4);
            String targetId =rs.getString(5);
            this.checkRecordFile(sourceType, sourceId);
            this.checkRecordFile(targetType, targetId);

            ArrayList<Object> params2 = new ArrayList<>();
            String query2 = queryGenerator.generateDeleteFullClaimQuery(claimId,user,sourceType,sourceId,targetType,targetId, params2);
            sqlDAO.executeUpdateQuery(query2, params2);
        }else{
            logger.error("Claim with id : "+ claimId+" user:  "+user+" couldn't be deleted." );
        }
        rs.close();
    }

    public void checkRecordFile(String type, String id) throws SQLStoreException, SQLException {
        if(type.equals("publication") || type.equals("dataset")|| type.equals("other")|| type.equals("software")){
            this.resultHandler.deleteRecordFile(type,id);
        }
    }
    public boolean deleteClaim(String claimId) throws Exception, SQLStoreException {

        ArrayList<Object> params = new ArrayList<>();
        String query = queryGenerator.generateSelectClaimQuery(claimId, params);
        ResultSet rs = sqlDAO.executePreparedQuery(query, params);
        if(rs.next()) {
            String sourceType =rs.getString(2);
            String sourceId =rs.getString(3);
            String targetType =rs.getString(4);
            String targetId =rs.getString(5);
            String user =rs.getString(6);
            this.checkRecordFile(sourceType, sourceId);
            this.checkRecordFile(targetType, targetId);
            ArrayList<Object> params2 = new ArrayList<>();
            String query2 = queryGenerator.generateDeleteFullClaimQuery(claimId,user,sourceType,sourceId,targetType,targetId, params2);
            sqlDAO.executeUpdateQuery(query2, params2);
            rs.close();
            return  true;
        }
        rs.close();
        logger.error("Couldn't delete claim with id : " + claimId +". It doesn't not exist.");
        return false;

    }

    public SqlDAO getSqlDAO() {
        return sqlDAO;
    }

    public void setSqlDAO(SqlDAO sqlDAO) {
        this.sqlDAO = sqlDAO;
    }

    public QueryGenerator getQueryGenerator() {
        return queryGenerator;
    }

    public void setQueryGenerator(QueryGenerator queryGenerator) {
        this.queryGenerator = queryGenerator;
    }

    public ResultHandler getResultHandler() {
        return resultHandler;
    }

    public void setResultHandler(ResultHandler resultHandler) {
        this.resultHandler = resultHandler;
    }

    public ProjectHandler getProjectHandler() {
        return projectHandler;
    }

    public void setProjectHandler(ProjectHandler projectHandler) {
        this.projectHandler = projectHandler;
    }

    public ClaimValidation getClaimValidation() {
        return claimValidation;
    }

    public void setClaimValidation(ClaimValidation claimValidation) {
        this.claimValidation = claimValidation;
    }

    public ContextUtils getContextUtils() {
        return contextUtils;
    }

    public void setContextUtils(ContextUtils contextUtils) {
        this.contextUtils = contextUtils;
    }

    public IndexResultHandler getIndexResultHandler() {
        return indexResultHandler;
    }

    public void setIndexResultHandler(IndexResultHandler indexResultHandler) {
        this.indexResultHandler = indexResultHandler;
    }

    public ExternalRecordHandler getExternalRecordHandler() {
        return externalRecordHandler;
    }

    public void setExternalRecordHandler(ExternalRecordHandler externalRecordHandler) {
        this.externalRecordHandler = externalRecordHandler;
    }

    public OrganizationHandler getOrganizationHandler() {
        return organizationHandler;
    }

    public void setOrganizationHandler(OrganizationHandler organizationHandler) {
        this.organizationHandler = organizationHandler;
    }
}
