package com.atlassian.stash.internal.spring.security;

import com.atlassian.bitbucket.Product;
import com.atlassian.bitbucket.auth.*;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.user.NoSuchUserException;
import com.atlassian.stash.internal.annotation.Profiled;
import com.atlassian.stash.internal.auth.AuthenticationService;
import com.atlassian.stash.internal.user.InternalPermissionService;
import com.atlassian.stash.internal.user.StashUserAuthenticationToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component("authenticationProvider")
public class PluginAuthenticationProvider implements AuthenticationProvider {

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

    private final AuthenticationService authenticationService;
    private final I18nService i18nService;
    private final InternalPermissionService permissionService;

    @Autowired
    public PluginAuthenticationProvider(AuthenticationService authenticationService, I18nService i18nService,
                                        InternalPermissionService permissionService) {
        this.authenticationService = authenticationService;
        this.i18nService = i18nService;
        this.permissionService = permissionService;
    }

    @Override
    @Profiled
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        HttpAuthenticationContextToken authToken = (HttpAuthenticationContextToken) authentication;
        HttpAuthenticationContext context = authToken.getContext();
        try {
            Optional<StashUserAuthenticationToken> token = authenticationService.authenticate(context);
            token.ifPresent(t -> {
                //Do the permission check using the context, rather than user, because using the context will allow the
                //PermissionService to lazily cache the PermissionGraph for the user in the context.
                if (!permissionService.hasGlobalPermission(t, Permission.LICENSED_USER)) {
                    throw new NoAccessAuthenticationException(
                            i18nService.createKeyedMessage("bitbucket.web.auth.notlicensed", Product.NAME));
                }
            });
            return token.orElse(null);
        } catch (CaptchaAuthenticationException e) {
            log.debug("Authentication failed - CAPTCHA required");
            throw new LockedException(e.getLocalizedMessage(), e);
        } catch (AuthenticationSystemException e) {
            log.warn("Could not authenticate {}", context.getUsername(), e);
            throw new AuthenticationServiceException(e.getLocalizedMessage(), e);
        } catch (InactiveUserAuthenticationException e) {
            log.debug("Authentication failed - User is inactive");
            throw new DisabledException(e.getLocalizedMessage(), e);
        } catch (NoAccessAuthenticationException e) {
            log.debug("Authentication failed - User is not licensed");
            throw new DisabledException(e.getLocalizedMessage(), e);
        } catch (com.atlassian.bitbucket.auth.AuthenticationException e) {
            log.debug("Authentication failed - Bad credentials");
            throw new BadCredentialsException(e.getLocalizedMessage(), e);
        } catch (NoSuchUserException e) {
            log.debug("Authentication failed - User not found");
            throw new BadCredentialsException(e.getLocalizedMessage(), e);
        }
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return HttpAuthenticationContextToken.class.isAssignableFrom(aClass);
    }
}
