package com.atlassian.stash.internal.license;

import com.atlassian.bitbucket.event.license.LicenseChangedEvent;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.extras.api.AtlassianLicense;
import com.atlassian.extras.api.LicenseManager;
import com.atlassian.extras.api.Product;
import com.atlassian.extras.api.ProductLicense;
import com.atlassian.extras.api.bitbucket.BitbucketServerLicense;
import com.atlassian.extras.decoder.api.LicenseDecoder;
import com.atlassian.stash.internal.server.ApplicationProperty;
import com.atlassian.stash.internal.server.ApplicationPropertyDao;
import com.google.common.collect.Iterables;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;

@Component("licenseHelper")
public class DefaultLicenseHelper implements LicenseHelper {

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

    private final LicenseDecoder licenseDecoder;
    private final LicenseManager licenseManager;
    private final ApplicationPropertyDao propertyDao;
    private final EventPublisher eventPublisher;

    /**
     * Value taken very early from the config file. Used when app is being setup. Can be null.
     */
    private final String setupLicense;

    @Autowired
    public DefaultLicenseHelper(LicenseDecoder licenseDecoder, LicenseManager licenseManager,
                                ApplicationPropertyDao propertyDao, EventPublisher eventPublisher,
                                @Value("${setup.license:}") String setupLicense) {
        this.licenseDecoder = licenseDecoder;
        this.licenseManager = licenseManager;
        this.propertyDao = propertyDao;
        this.eventPublisher = eventPublisher;
        this.setupLicense = getSetupLicense(setupLicense);
    }

    @Override
    public SimpleDualLicense decode(@Nonnull String license) {
        if (licenseDecoder.canDecode(license)) {
            try {
                AtlassianLicense decoded = licenseManager.getLicense(license);

                ProductLicense productLicense = decoded.getProductLicense(Product.BITBUCKET_SERVER);
                if (productLicense instanceof BitbucketServerLicense) {
                    return new SimpleDualLicense((BitbucketServerLicense) productLicense, license);
                }

                // The license decoded without error, but did not include a Stash license
                log.warn("The decoded license does not include a Stash license. Products licensed: {}",
                        Iterables.toString(decoded.getProductLicenses()));
                return null;
            } catch (Exception e) {
                log.warn("The provided license could not be decoded", e);
            }
        }

        // This exception will not be displayed directly; LicenseServiceImpl catches this, ignores it
        // and throws an internationalized InvalidLicenseException
        throw new IllegalArgumentException();
    }

    @Override
    public String get() {
        // If app is being setup a transient value might be available
        String storedLicense = getStoredLicense();
        return storedLicense == null && !propertyDao.isSetup() ? setupLicense : storedLicense;
    }

    @Override
    public SimpleDualLicense getDecoded() {
        String encodedLicense = get();

        return encodedLicense == null ? null : decode(encodedLicense);
    }

    @Override
    public boolean isClusteringEnabled(boolean defaultValue) {
        DualLicense license = getDecoded();

        return license == null ? defaultValue : license.isClusteringEnabled();
    }

    @Override
    public void set(@Nonnull String newLicense) {
        String oldLicense = getStoredLicense();

        if (oldLicense == null) {
            propertyDao.create(new ApplicationProperty(ApplicationProperty.Key.LICENSE, newLicense));
        } else if (!oldLicense.equals(newLicense)) {
            propertyDao.update(new ApplicationProperty(ApplicationProperty.Key.LICENSE, newLicense));
        } else {
            return; // We don't want to publish an event if the license didn't actually change
        }

        eventPublisher.publish(new LicenseChangedEvent(this));
    }

    /**
     * @return standard app license stored in the db. Can be null during setup.
     */
    private String getStoredLicense() {
        ApplicationProperty property = propertyDao.getById(ApplicationProperty.Key.LICENSE);
        return (property == null) ? null : property.getValue();
    }

    /**
     * A license not set is a reasonable response for any component depending on it, while an invalid license is way
     * more error prone. Invalid licenses are specifically handled in
     * {@link com.atlassian.stash.internal.bootstrap.BootstrapOperation#setupLicense(com.atlassian.stash.internal.config.RemoveSetupConfigurationRequest.Builder)}.
     *
     * @param setupLicense
     * @return setupLicense or null
     */
    private String getSetupLicense(String setupLicense) {
        DualLicense dualLicense = null;
        try {
            if (StringUtils.isNotBlank(setupLicense)) {
                dualLicense = decode(setupLicense);
            }
        } catch (IllegalArgumentException e) {
            log.warn("Ignoring supplied license as it is invalid", e);
        }

        return dualLicense == null ? null : setupLicense;
    }
}