/**
 *
 */
package com.thinkbiganalytics.metadata.core.feed;

/*-
 * #%L
 * thinkbig-feed-manager-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.metadata.api.MetadataAccess;
import com.thinkbiganalytics.metadata.api.event.MetadataEventListener;
import com.thinkbiganalytics.metadata.api.event.MetadataEventService;
import com.thinkbiganalytics.metadata.api.event.feed.FeedOperationStatusEvent;
import com.thinkbiganalytics.metadata.api.event.feed.OperationStatus;
import com.thinkbiganalytics.metadata.api.event.feed.PreconditionTriggerEvent;
import com.thinkbiganalytics.metadata.api.feed.Feed;
import com.thinkbiganalytics.metadata.api.feed.FeedPrecondition;
import com.thinkbiganalytics.metadata.api.feed.FeedProvider;
import com.thinkbiganalytics.metadata.api.op.FeedOperation;
import com.thinkbiganalytics.metadata.api.sla.FeedExecutedSinceFeed;
import com.thinkbiganalytics.metadata.sla.api.AssessmentResult;
import com.thinkbiganalytics.metadata.sla.api.Metric;
import com.thinkbiganalytics.metadata.sla.api.ServiceLevelAgreement;
import com.thinkbiganalytics.metadata.sla.api.ServiceLevelAssessment;
import com.thinkbiganalytics.metadata.sla.spi.ServiceLevelAssessor;

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

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;

/**
 * Service for assessing {@link FeedPrecondition}
 */
public class FeedPreconditionService {

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

    @Inject
    private ServiceLevelAssessor assessor;

    @Inject
    private FeedProvider feedProvider;

    @Inject
    private MetadataAccess metadata;

    @Inject
    private MetadataEventService eventService;

    private FeedOperationListener listener = new FeedOperationListener();

    @PostConstruct
    public void addEventListener() {
        this.eventService.addListener(this.listener);
    }

    @PreDestroy
    public void removeEventListener() {
        this.eventService.removeListener(this.listener);
    }


    public ServiceLevelAssessment assess(FeedPrecondition precond) {
        ServiceLevelAgreement sla = precond.getAgreement();
        return this.assessor.assess(sla);
    }

    private void checkPrecondition(Feed<?> feed, OperationStatus operationStatus) {
        FeedPrecondition precond = feed.getPrecondition();

        if (precond != null) {
            log.debug("Checking precondition of feed: {} ({})", feed.getName(), feed.getId());

            ServiceLevelAgreement sla = precond.getAgreement();
            boolean isAssess = sla.getObligationGroups().stream()
                .flatMap(obligationGroup -> obligationGroup.getObligations().stream())
                .flatMap(obligation -> obligation.getMetrics().stream())
                .anyMatch(metric -> isMetricDependentOnStatus(metric, operationStatus));

            if (isAssess) {
                ServiceLevelAssessment assessment = this.assessor.assess(sla);

                if (assessment.getResult() == AssessmentResult.SUCCESS) {
                    log.info("Firing precondition trigger event for feed:{} ({})", feed.getName(), feed.getId());
                    this.eventService.notify(new PreconditionTriggerEvent(feed.getId()));
                }
            } else {
                log.debug("Feed {}.{} does not depend on feed {}", feed.getCategory(), feed.getName(), operationStatus.getFeedName());
            }
        }
    }

    /**
     * To avoid feeds being triggered by feeds they do not depend on
     */
    private boolean isMetricDependentOnStatus(Metric metric, OperationStatus operationStatus) {
        return !(metric instanceof FeedExecutedSinceFeed) || operationStatus.getFeedName().equalsIgnoreCase(((FeedExecutedSinceFeed) metric).getCategoryAndFeed());
    }

    private class FeedOperationListener implements MetadataEventListener<FeedOperationStatusEvent> {

        @Override
        public void notify(FeedOperationStatusEvent event) {
            FeedOperation.State state = event.getData().getState();

            // TODO as precondition check criteria are not implemented yet, 
            // check all preconditions of feeds that have them.
            if (state == FeedOperation.State.SUCCESS) {
                metadata.read(() -> {
                    for (Feed<?> feed : feedProvider.getFeeds()) {
                        // Don't check the precondition of the feed that that generated this change event.
                        // TODO: this might not be the correct behavior but none of our current metrics
                        // need to be assessed when the feed itself containing the precondition has changed state.
                        if (!feed.getQualifiedName().equals(event.getData().getFeedName())) {
                            checkPrecondition(feed, event.getData());
                        }
                    }
                    return null;
                }, MetadataAccess.SERVICE);
            }
        }
    }
}
