package com.shinesolutions.healthcheck.hc.impl;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Property;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.hc.annotations.SlingHealthCheck;
import org.apache.sling.hc.api.HealthCheck;
import org.apache.sling.hc.api.Result;
import org.apache.sling.hc.util.FormattingResultLog;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;

import java.util.Arrays;
import java.util.Dictionary;

/**
 * Health Check to test if all Bundles are active.
 */
@SlingHealthCheck(
        name = "Bundle Health Check",
        mbeanName = "bundleHC",
        description = "This health check scans the current OSGi bundles and reports if there is any inactive bundles.",
        tags = {"deep"}
)
public class ActiveBundleHealthCheck implements HealthCheck {

    private BundleContext bundleContext;

    private static final String BUNDLE_FRAGMENT_HOST     = "Fragment-Host";
    private static final String BUNDLE_ACTIVATION_POLICY = "Bundle-ActivationPolicy";
    private static final String LAZY_ACTIVATION_POLICY   = "lazy";

    @Property(label = "Ignored Bundles", description = "The bundles that will be ignored in the Active Bundle Health-Check")
    protected static final String IGNORED_BUNDLES = "bundles.ignored";
    protected String[] ignoredBundles;

    @Activate
    protected void activate(ComponentContext context) {
        bundleContext = context.getBundleContext();
        Dictionary<String, Object> properties = context.getProperties();

        if(properties != null) {
            ignoredBundles = PropertiesUtil.toStringArray(properties.get(IGNORED_BUNDLES));
        } else {
            ignoredBundles = new String[]{};
        }

    }

    @Deactivate
    protected void deactivate() {
        bundleContext = null;
    }

    @Override
    public Result execute() {
        FormattingResultLog resultLog = new FormattingResultLog();
        int inactiveBundles = 0;
        Bundle[] bundles = bundleContext.getBundles();

        for (Bundle bundle : bundles) {
            if ((!isActiveBundle(bundle)) && !isIgnoredBundle(bundle)) {
                inactiveBundles++;
                resultLog.warn("Bundle {} is not active. It is in state {}.", bundle.getSymbolicName(), bundle.getState());
            }
        }

        if (ignoredBundles != null) {
            resultLog.debug("The following bundles will be ignored: {}", Arrays.toString(ignoredBundles));
        }

        if (inactiveBundles > 0) {
            resultLog.warn("There are {} inactive Bundles", inactiveBundles);
        } else {
            resultLog.info("All bundles are considered active");
        }

        return new Result(resultLog);
    }

    /**
     * Checks whether the provided bundle is in a string Array of ignored bundles.
     *
     * @param bundle
     * @return
     */
    private boolean isIgnoredBundle(Bundle bundle) {
        return (ignoredBundles != null &&
                Arrays.asList(ignoredBundles).contains(bundle.getSymbolicName()));
    }

    /**
     * Checks whether the provided bundle is active. A bundle is considered active if it meets the following criteria:
     * - the bundle is active, or
     * - it is a fragment bundle, or
     * - it has a lazy activation policy
     *
     * @param bundle
     * @return
     */
    private static boolean isActiveBundle(Bundle bundle) {
        return (bundle.getState() == Bundle.ACTIVE ||
                bundle.getHeaders().get(BUNDLE_FRAGMENT_HOST) != null) ||
                (bundle.getHeaders().get(BUNDLE_ACTIVATION_POLICY) != null &&
                        bundle.getHeaders().get(BUNDLE_ACTIVATION_POLICY).equals(LAZY_ACTIVATION_POLICY));
    }
}