package com.atlassian.stash.internal.maintenance;

import com.atlassian.event.api.EventPublisher;
import com.atlassian.johnson.event.AddEvent;
import com.atlassian.johnson.event.EventLevel;
import com.atlassian.johnson.event.EventType;
import com.atlassian.johnson.event.RemoveEvent;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.google.common.util.concurrent.ExecutionList;
import com.google.common.util.concurrent.MoreExecutors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

/**
 * @since 2.1
 */
public class DefaultMaintenanceLock implements MaintenanceLock {

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

    private final SystemMaintenanceEvent event;
    private final EventPublisher eventPublisher;
    private final I18nService i18nService;
    private final ExecutionList listeners;
    private final ApplicationUser owner;
    private final String token;

    private volatile boolean locked;

    public DefaultMaintenanceLock(EventPublisher eventPublisher, I18nService i18nService,
                                  ApplicationUser owner, String token) {
        this.eventPublisher = checkNotNull(eventPublisher, "eventPublisher");
        this.i18nService = i18nService;
        this.owner = checkNotNull(owner, "owner");
        this.token = checkNotNull(token, "token");

        event = new SystemMaintenanceEvent(EventType.get("performing-maintenance"),
                "The system is unavailable while maintenance is being performed.",
                EventLevel.get("system-maintenance"), token);
        listeners = new ExecutionList();
    }

    public void addListener(Runnable listener) {
        listeners.add(listener, MoreExecutors.directExecutor());
    }

    @Nonnull
    @Override
    public ApplicationUser getOwner() {
        return owner;
    }

    @Nonnull
    @Override
    public String getUnlockToken() {
        return token;
    }

    public void lock() {
        checkState(!locked, "Already locked");
        locked = true;
        eventPublisher.publish(new AddEvent(this, event));
    }

    @Override
    public void unlock(@Nonnull String token) {
        checkNotNull(token, "token");
        checkState(locked, "Not locked");

        // unlock is not secured beyond validating that the provided token is the correct unlock token. No permission
        // checks can be performed because the 'unlock' call is made from an unauthenticated context (the maintenance
        // lock screen) - no Spring Security processing is performed on the maintenance lock screen.
        if (event.isToken(token)) {
            eventPublisher.publish(new RemoveEvent(this, event));

            log.info("Maintenance has been completed. The system lock has been released");
            listeners.execute();
        } else {
            log.warn("An invalid token ({}) was supplied to attempt to unlock the system", token);
            throw new IncorrectTokenMaintenanceException(
                    i18nService.createKeyedMessage("bitbucket.service.maintenance.lock.incorrectunlocktoken"),
                    token);
        }
    }
}
