package com.atlassian.stash.internal.audit;

import com.atlassian.bitbucket.audit.AuditEntryBuilder;
import com.atlassian.event.api.EventListener;
import com.atlassian.event.api.EventPublisher;
import com.atlassian.bitbucket.audit.AuditEntry;
import com.atlassian.bitbucket.audit.Priority;
import com.atlassian.bitbucket.event.auth.AbstractAuthenticationEvent;
import com.atlassian.bitbucket.event.auth.AuthenticationFailureEvent;
import com.atlassian.bitbucket.event.auth.AuthenticationSuccessEvent;
import com.atlassian.bitbucket.request.RequestManager;
import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.auth.AuthenticationException;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.UserService;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Collections;

@Component("authenticationEventListener")
public class AuthenticationEventListener extends AbstractAuditEventListener {

    private final UserService userService;

    @Autowired
    public AuthenticationEventListener(AuditEntryLoggingService auditLoggingService, RequestManager requestManager,
                                       AuthenticationContext authContext, EventPublisher eventPublisher,
                                       UserService userService) {
        super(auditLoggingService, requestManager, authContext, eventPublisher);
        this.userService = userService;
    }

    @EventListener
    public void onAuthenticationFailure(AuthenticationFailureEvent event) throws Exception {
        AuditEntry auditEntry = createAuthenticationAuditEntryBuilder(event)
                .details(toDetailsString(event))
                .build();

        publish(event, auditEntry, Collections.<String>emptySet(),  Priority.HIGH);
    }

    @EventListener
    public void onAuthenticationSuccess(AuthenticationSuccessEvent event) throws Exception {
        AuditEntry auditEntry = createAuthenticationAuditEntryBuilder(event)
                .details(toDetailsString(event))
                .build();

        publish(event, auditEntry, Collections.<String>emptySet(),  Priority.LOW);
    }

    private ImmutableMap.Builder<String, String> createDetailsBuilder(AbstractAuthenticationEvent event) {
        return new ImmutableMap.Builder<String, String>()
                .put("authentication-method", event.getAuthenticationMethod());
    }

    private AuditEntryBuilder createAuthenticationAuditEntryBuilder(AbstractAuthenticationEvent event) {
        AuditEntryBuilder builder = createAuditEntryBuilder(event);

        if (event.getUsername() == null) {
            builder.target("<anonymous>");
        } else {
            ApplicationUser user = event.getUser();
            if (user == null || !user.getName().equals(event.getUsername())) {
                user = userService.getUserByName(event.getUsername(), true);
            }
            builder.user(user);
            builder.target(event.getUsername());
        }

        return builder;
    }

    private String toDetailsString(AuthenticationFailureEvent event) throws IOException {
        ImmutableMap.Builder<String, String> detailsBuilder = createDetailsBuilder(event);
        AuthenticationException exception = event.getException();
        if (exception != null && exception.getLocalizedMessage() != null) {
            detailsBuilder.put("error", exception.getLocalizedMessage());
        }

        return convertToJsonString(detailsBuilder.build());
    }

    private String toDetailsString(AuthenticationSuccessEvent event) throws IOException {
        ImmutableMap.Builder<String, String> detailsBuilder = createDetailsBuilder(event);
        if (StringUtils.isNotBlank(event.getTokenDetails())) {
            detailsBuilder.put("tokenDetails", event.getTokenDetails());
        }

        return convertToJsonString(detailsBuilder.build());
    }

}
