package com.thinkbiganalytics.metadata.sla.spi.core;

/*-
 * #%L
 * thinkbig-sla-core
 * %%
 * Copyright (C) 2017 ThinkBig Analytics
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import com.thinkbiganalytics.alerts.api.Alert;
import com.thinkbiganalytics.alerts.sla.AssessmentAlerts;
import com.thinkbiganalytics.alerts.spi.AlertManager;
import com.thinkbiganalytics.metadata.sla.api.ServiceLevelAgreement;
import com.thinkbiganalytics.metadata.sla.api.ServiceLevelAssessment;
import com.thinkbiganalytics.metadata.sla.spi.AssessorNotFoundException;
import com.thinkbiganalytics.metadata.sla.spi.ServiceLevelAgreementChecker;
import com.thinkbiganalytics.metadata.sla.spi.ServiceLevelAgreementProvider;
import com.thinkbiganalytics.metadata.sla.spi.ServiceLevelAssessmentProvider;
import com.thinkbiganalytics.metadata.sla.spi.ServiceLevelAssessor;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

import javax.inject.Inject;
import javax.inject.Named;

/**
 */
public class DefaultServiceLevelAgreementChecker implements ServiceLevelAgreementChecker {


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

    @Inject
    protected ServiceLevelAgreementProvider slaProvider;

    @Inject
    protected ServiceLevelAssessmentProvider assessmentProvider;

    @Inject
    protected ServiceLevelAssessor assessor;

    @Inject
    @Named("kyloAlertManager")
    protected AlertManager alertManager;


    //store a ref to the last Assessment ID that was alerted
    protected Map<ServiceLevelAgreement.ID, ServiceLevelAssessment.ID> alertedAssessments;


    public DefaultServiceLevelAgreementChecker() {
        this.alertedAssessments = Collections
            .synchronizedMap(new LinkedHashMap<ServiceLevelAgreement.ID, ServiceLevelAssessment.ID>());
    }

    /**
     * Caller should wrap this in commit
     */
    public void checkAgreements() {
        List<? extends ServiceLevelAgreement> list = slaProvider.getAgreements();

        LOG.info("Checking {} service level agreements", list.size());

        for (ServiceLevelAgreement agreement : list) {
            checkAgreement(agreement);
        }

        LOG.info("Completed checking SLAs");


    }

    /**
     * Check the Agreement. Caller needs to wrap this in MetadataAccesss transcation
     */
    public void checkAgreement(ServiceLevelAgreement agreement) {
        if (agreement != null) {
            Alert newAlert = null;
            if (isAssessable(agreement)) {
                LOG.info("Assessing SLA  : " + agreement.getName());

                try {
                    ServiceLevelAssessment assessment = assessor.assess(agreement);

                    if (shouldAlert(agreement, assessment)) {
                        newAlert = alertManager.create(AssessmentAlerts.VIOLATION_ALERT_TYPE,
                                                       Alert.Level.FATAL,
                                                       "Violation of SLA: " + agreement.getName(), assessment.getId());


                    }
                } catch (AssessorNotFoundException e) {
                    LOG.info("SLA assessment failed.  Assessor Not found: {} - Exception: {}", agreement.getName(), e);
                }
            }
            if (newAlert != null) {
                // Record this assessment as the latest for this SLA.
                alertedAssessments.put(agreement.getId(), (ServiceLevelAssessment.ID) newAlert.getContent());
                LOG.info("SLA assessment failed: {} - generated alert: {}", agreement.getName(), newAlert.getId());
            }
        }


    }


    /**
     * Determine whether an alert should be generated for this assessment by comparing is to the last one for the same SLA.
     *
     * @return true if the alert should be generated
     */
    protected boolean shouldAlert(ServiceLevelAgreement agreement, ServiceLevelAssessment assessment) {
        boolean shouldAlert = false;
        try {
            // Get the last assessment that was created for this SLA (if any).
            ServiceLevelAssessment previous = null;
            ServiceLevelAssessment.ID previousId = this.alertedAssessments.get(agreement.getId());
            if (previousId != null) {
                previous = this.assessmentProvider.findServiceLevelAssessment(previousId);
            } else {
                previous = this.assessmentProvider.findLatestAssessment(agreement.getId());
            }

            if (previous != null) {

                if (previous.getAgreement() == null && StringUtils.isNotBlank(assessment.getServiceLevelAgreementId())) {
                    ServiceLevelAgreement previousAgreement = slaProvider.getAgreement(slaProvider.resolve(assessment.getServiceLevelAgreementId()));

                }

                shouldAlert = assessment.compareTo(previous) != 0;
            } else {
                shouldAlert = true;
            }
        } catch (Exception e) {
            LOG.error("Error checking shouldAlert for {}. {} ", agreement.getName(), e.getMessage(), e);
        }
        return shouldAlert;
    }

    private boolean isAssessable(ServiceLevelAgreement agreement) {
        // TODO: validate that this is a kind of agreement that we assess.  Assume we assess all SLAs for now.
        return true;
    }


}
