package com.atlassian.bitbucket.internal.ssh.server;

import com.atlassian.bitbucket.internal.ssh.SshConstants;
import com.atlassian.bitbucket.ssh.SshConfiguration;
import com.atlassian.bitbucket.ssh.SshConfigurationService;
import com.atlassian.bitbucket.ssh.SshKey;
import com.atlassian.bitbucket.ssh.SshKeyService;
import com.atlassian.stash.internal.user.InternalDetailedUser;
import com.atlassian.stash.internal.user.InternalNormalUser;
import com.atlassian.stash.internal.user.InternalServiceUser;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.security.interfaces.RSAPublicKey;

import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class DefaultSshAuthenticationHandlerTest {

    private static final String USERNAME = "username";

    @Mock
    private SshKeyService sshKeyService;
    @Mock
    private RSAPublicKey publicKey;
    @Mock
    private SshKey sshKey;
    @Mock
    private SshConfigurationService sshConfigurationService;
    @Mock
    private SshConfiguration sshConfiguration;

    private DefaultSshAuthenticationHandler publicKeyAuthenticator;
    private DefaultSshAuthenticationContext authenticationContext;

    @Before
    public void setup() {
        publicKeyAuthenticator = new DefaultSshAuthenticationHandler(sshKeyService, sshConfigurationService);
        authenticationContext = new DefaultSshAuthenticationContext(new SshCredentials(USERNAME, publicKey));
    }

    @Test
    public void testAuthenticateUser() {
        when(sshKeyService.getByPublicKey(publicKey)).thenReturn(sshKey);
        InternalNormalUser normalUser = normalUser();
        when(sshKey.getUser()).thenReturn(normalUser);
        when(sshConfigurationService.getConfiguration()).thenReturn(sshConfiguration);
        when(sshConfiguration.isAccessKeysEnabled()).thenReturn(true);

        DefaultSshAuthenticationContext authenticationContext = new DefaultSshAuthenticationContext(
                new SshCredentials(USERNAME, publicKey));
        assertSame(normalUser, publicKeyAuthenticator.authenticate(authenticationContext));
    }

    @Test
    public void testFailToAuthenticateUser() {
        when(sshKeyService.getByPublicKey(publicKey)).thenReturn(sshKey);
        when(sshKey.getUser()).thenReturn(null);

        assertNull(publicKeyAuthenticator.authenticate(authenticationContext));
        verify(sshKeyService).getByPublicKey(publicKey);
    }

    @Test
    public void testFailToAuthenticateAccessKeysDisabled() {
        when(sshKeyService.getByPublicKey(publicKey)).thenReturn(sshKey);
        when(sshKey.getUser()).thenReturn(serviceUser(SshConstants.LABEL_ACCESS_KEY));
        when(sshConfigurationService.getConfiguration()).thenReturn(sshConfiguration);
        when(sshConfiguration.isAccessKeysEnabled()).thenReturn(false);

        assertNull(publicKeyAuthenticator.authenticate(authenticationContext));
        verify(sshKeyService).getByPublicKey(publicKey);
    }

    @Test
    public void testAuthenticateAccessKeysDisabled() {
        when(sshKeyService.getByPublicKey(publicKey)).thenReturn(sshKey);
        InternalServiceUser serviceUser = serviceUser("non-access-key-label");
        when(sshKey.getUser()).thenReturn(serviceUser);
        when(sshConfigurationService.getConfiguration()).thenReturn(sshConfiguration);
        when(sshConfiguration.isAccessKeysEnabled()).thenReturn(false);

        assertSame(serviceUser, publicKeyAuthenticator.authenticate(authenticationContext));
        verify(sshKeyService).getByPublicKey(publicKey);
    }

    @Test
    public void testAuthenticateNormalUserAccessKeysDisabled() {
        when(sshKeyService.getByPublicKey(publicKey)).thenReturn(sshKey);
        InternalNormalUser normalUser = normalUser();
        when(sshKey.getUser()).thenReturn(normalUser);
        when(sshConfigurationService.getConfiguration()).thenReturn(sshConfiguration);
        when(sshConfiguration.isAccessKeysEnabled()).thenReturn(false);

        assertSame(normalUser, publicKeyAuthenticator.authenticate(authenticationContext));
        verify(sshKeyService).getByPublicKey(publicKey);
    }

    @Test
    public void testAuthenticateDetailedUserAccessKeysDisabled() {
        when(sshKeyService.getByPublicKey(publicKey)).thenReturn(sshKey);
        InternalDetailedUser detailedUser = detailedUser();
        when(sshKey.getUser()).thenReturn(detailedUser);
        when(sshConfigurationService.getConfiguration()).thenReturn(sshConfiguration);
        when(sshConfiguration.isAccessKeysEnabled()).thenReturn(false);

        assertSame(detailedUser, publicKeyAuthenticator.authenticate(authenticationContext));
        verify(sshKeyService).getByPublicKey(publicKey);
    }

    private static InternalDetailedUser detailedUser() {
        return new InternalDetailedUser.Builder(normalUser())
                .build();
    }

    private static InternalNormalUser normalUser() {
        return new InternalNormalUser.Builder()
                .name("service-user")
                .build();
    }

    private static InternalServiceUser serviceUser(String label) {
        return new InternalServiceUser.Builder()
                .label(label)
                .name("service-user")
                .build();
    }
}

