package com.flowable.local.work.service;

import com.flowable.audit.api.AuditService;
import com.flowable.audit.api.runtime.AuditInstance;
import com.flowable.audit.api.runtime.AuditInstanceQuery;
import com.flowable.audit.engine.impl.persistence.entity.AuditInstanceEntity;
import org.flowable.cmmn.api.history.HistoricCaseInstance;
import org.flowable.cmmn.api.runtime.CaseInstance;
import org.flowable.cmmn.engine.CmmnEngine;
import org.flowable.common.rest.api.DataResponse;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.*;

/*
    Returns audit trail entries based on:
    - scopeType cmmn
      - rootId <root.id>
        -> audit trail entries for case with specified id
      - instanceName <root.name>
        -> audit trail entries for case with specified name
    - scopeType dataObject
      - dataObjectId <data object instance id>
        -> audit trail entries for data object with specified instance id, provided by DataObjectAuditTrailEventHandler
      - dataObjectModelKey <data object model key>
        -> audit trail entries for data object with specified model key, provided by DataObjectAuditTrailEventHandler
 */
@Component
public class AuditTrailService {
    protected static final Logger LOGGER = LoggerFactory.getLogger(AuditTrailService.class);
    protected final CmmnEngine cmmnEngine;
    protected final AuditService auditService;

    public AuditTrailService(CmmnEngine cmmnEngine, AuditService auditService) {
        this.cmmnEngine = cmmnEngine;
        this.auditService = auditService;
    }

    public DataResponse<HashMap<String, Object>> getAuditTrail(String scopeType, String scopeId, String rootId, String instanceName,
                                                               String dataObjectId, String dataObjectModelKey,
                                                               Integer start, Integer size,
                                                               String createdAfter, String createdBefore,
                                                               String creatorId, String subType, String subScopeId) {
        String scopeDefinitionId = null;
        switch (scopeType) {
            case "cmmn":
                // Check for rootId or instanceName if scopeId is not provided
                if (scopeId == null || scopeId.trim().isEmpty()) {
                    if (rootId != null && !rootId.trim().isEmpty()) {
                        scopeId = rootId;
                    } else if (instanceName != null && !instanceName.trim().isEmpty()) {
                        CaseInstance caseInstance = this.cmmnEngine.getCmmnRuntimeService().createCaseInstanceQuery().caseInstanceBusinessKey("asdf").singleResult();
//                        CaseInstance caseInstance = this.cmmnEngine.getCmmnRuntimeService().createCaseInstanceQuery().caseInstanceName(instanceName).singleResult();
                        if (caseInstance != null) {
                            scopeId = caseInstance.getId();
                        } else {
                            HistoricCaseInstance historicCaseInstance = this.cmmnEngine.getCmmnHistoryService().createHistoricCaseInstanceQuery().caseInstanceName(instanceName).singleResult();
                            if (historicCaseInstance != null) {
                                scopeId = historicCaseInstance.getId();
                            }
                        }
                    } else {
                        LOGGER.warn("Missing parameter rootId or instanceName for scopeType=cmmn");
                        return createAuditEntryDataResponse(null, 0, 0);
                    }
                }
                break;
            case "dataObject":
                // Check for dataObjectId or dataObjectModelKey if scopeId is not provided
                if (scopeId == null || scopeId.trim().isEmpty()) {
                    if (dataObjectId != null && !dataObjectId.trim().isEmpty()) {
                        scopeId = dataObjectId;
                    } else if (dataObjectModelKey != null && !dataObjectModelKey.trim().isEmpty()) {
                        scopeDefinitionId = dataObjectModelKey;
                    } else {
                        LOGGER.warn("Missing parameter dataObjectId or dataObjectModelKey for scopeType=dataObject");
                        return createAuditEntryDataResponse(null, 0, 0);
                    }
                }
                break;
            default:
                LOGGER.warn("Invalid scopeType: {}", scopeType);
                return createAuditEntryDataResponse(null, 0, 0);
        }
        return queryAuditTrail(scopeType, scopeId, scopeDefinitionId, start, size, createdAfter, createdBefore, creatorId, subType, subScopeId);
    }

    protected DataResponse<HashMap<String, Object>> queryAuditTrail(String scopeType, String scopeId,
                                                                    String scopeDefinitionId,
                                                                    Integer start, Integer size,
                                                                    String createdAfter, String createdBefore,
                                                                    String creatorId, String subType, String subScopeId) {
        // Create query from parameters
        AuditInstanceQuery query = this.auditService.createAuditInstanceQuery().orderByCreationTime().desc();
        query.scopeType(scopeType);
        query.scopeId(scopeId);
        query.scopeDefinitionId(scopeDefinitionId);
        if (createdAfter != null && !createdAfter.trim().isEmpty()) {
            query.createdAfter(DateTime.parse(createdAfter).toDate());
        }
        if (createdBefore != null && !createdBefore.trim().isEmpty()) {
            query.createdBefore(DateTime.parse(createdBefore).toDate());
        }
        query.creatorId(creatorId);
        query.subType(subType);
        query.subScopeId(subScopeId);

        // Run paged query
        int startIndex = start == null ? 0 : start;
        List<AuditInstance> auditInstances;
        if (size != null) {
            auditInstances = query.listPage(startIndex, size);
        } else {
            auditInstances = query.list();
        }

        // Map query result into proper list
        List<HashMap<String, Object>> entries = new ArrayList<>();
        for (AuditInstance auditInstance : auditInstances) {
            entries.add(convertAuditInstance(auditInstance));
        }
        return createAuditEntryDataResponse(entries, query.count(), startIndex);
    }

    protected DataResponse<HashMap<String, Object>> createAuditEntryDataResponse(List<HashMap<String, Object>> entries, long totalCount, int startIndex) {
        DataResponse<HashMap<String, Object>> auditTrail = new DataResponse<>();
        if (entries != null && !entries.isEmpty()) {
            auditTrail.setData(entries);
            auditTrail.setTotal(totalCount);
            auditTrail.setStart(startIndex);
            auditTrail.setSize(entries.size());
        }
        auditTrail.setSort("creationTime");
        auditTrail.setOrder("desc");
        return auditTrail;
    }

    protected HashMap<String, Object> convertAuditInstance(AuditInstance auditInstance) {
        HashMap<String, Object> auditEntry = new HashMap<>();
        AuditInstanceEntity auditInst = (AuditInstanceEntity) auditInstance;
        auditEntry.put("id", auditInst.getId());
        auditEntry.putAll((Map<String, String>) auditInst.getPersistentState());

        HashMap<String, Object> auditInstPayload = (HashMap<String, Object>) auditInst.getPayload();
        if (auditInstPayload != null) {
            auditEntry.put("payload", new HashMap<>(auditInstPayload));
        }
        return auditEntry;
    }
}