package com.atlassian.bitbucket.internal.mirroring.mirror.auth.ssh;

import com.atlassian.bitbucket.auth.SshAuthenticationContext;
import com.atlassian.bitbucket.auth.SshAuthenticationHandler;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.internal.mirroring.mirror.InternalUpstreamService;
import com.atlassian.bitbucket.internal.mirroring.mirror.auth.DelegatedAuthenticationHandlerBase;
import com.atlassian.bitbucket.internal.mirroring.mirror.client.InternalUpstreamClientFactory;
import com.atlassian.bitbucket.internal.mirroring.ssh.encoding.PublicKeyEncodingHelper;
import com.atlassian.bitbucket.internal.mirroring.user.ApplicationUserWithPermissions;
import com.atlassian.bitbucket.mirroring.mirror.IntegrationState;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamServer;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.SecurityService;
import com.atlassian.sal.api.transaction.TransactionTemplate;
import com.atlassian.util.concurrent.Promise;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class DelegatedSshAuthenticationHandler extends DelegatedAuthenticationHandlerBase implements SshAuthenticationHandler {

    private final PublicKeyEncodingHelper publicKeyEncodingHelper;

    public DelegatedSshAuthenticationHandler(InternalUpstreamClientFactory clientFactory, I18nService i18nService,
                                             InternalUpstreamService upstreamService, PublicKeyEncodingHelper publicKeyEncodingHelper,
                                             SecurityService securityService, TransactionTemplate transactionTemplate) {
        super(clientFactory, i18nService, upstreamService, securityService, transactionTemplate);
        this.publicKeyEncodingHelper = publicKeyEncodingHelper;
    }

    @Nullable
    @Override
    public ApplicationUser authenticate(@Nonnull SshAuthenticationContext context) {
        if (!upstreamService.isMirror()) {
            log.trace("Not delegating SSH authentication because this is not configured to be a mirror");
            return null;
        }

        UpstreamServer upstream = upstreamService.get();
        if (upstream != null && upstream.getState() == IntegrationState.UNKNOWN) {
            log.info("Not delegating SSH authentication because this mirror's installation state with respect to the " +
                    "upstream server is currently unknown");
            return null;
        } else if (upstream == null || upstream.getState() != IntegrationState.INSTALLED) {
            log.info("Not delegating SSH authentication because this mirror is not installed on an " +
                    "upstream server");
            return null;
        }

        log.debug("Delegating SSH authentication for username {} with {} public key",
                context.getUsername(), context.getPublicKey().getAlgorithm());

        switch(upstream.getType()) {
            case BITBUCKET_CLOUD:
                /**
                 * We return a mock user with no permissions here.
                 * The actual delegated auth check happens in a ssh-request-handler.
                 * This is because the auth code does not know which command the user is requesting.
                 * @see CloudAuthenticatingSshScmRequestHandler
                 */
                return new PublicKeyUser(publicKeyEncodingHelper.encodeAsOpenSsh(context.getPublicKey()));
            case BITBUCKET_SERVER:
                Promise<ApplicationUserWithPermissions> promise = clientFactory.createForServer(upstream)
                        .authenticateForUser(context.getUsername(), context.getPublicKey()).map(this::localiseResourcePermissions);
                return getWithTimeout(promise);
            default:
                log.info("Delegation failed due to unknown upstream type.");
                return null;
        }
    }
}
