package org.jenkinsci.plugins.gitclient.credentials;

import static org.junit.jupiter.api.Assertions.*;

import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import hudson.model.TaskListener;
import hudson.util.Secret;
import hudson.util.StreamTaskListener;
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.transport.CredentialItem;
import org.eclipse.jgit.transport.URIish;
import org.jenkinsci.plugins.gitclient.jgit.SmartCredentialsProvider;
import org.jenkinsci.plugins.gitclient.jgit.StandardUsernameCredentialsCredentialItem;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class SmartCredentialsProviderTest {

    private static final String MASKED_USER_NAME_PROMPT = "masked user name prompt";
    private static final String UNMASKED_USER_NAME_PROMPT = "unmasked user name prompt";
    private static final String MASKED_STRING_TYPE_PROMPT = "masked string type prompt";
    private static final String UNMASKED_STRING_TYPE_PROMPT = "unmasked string type prompt";
    private static final String SPECIAL_STRING_TYPE_PROMPT = "Password: ";

    private TaskListener listener;
    private SmartCredentialsProvider provider;
    private URIish gitURI;
    private URIish gitURISlash;
    private URIish gitURIShort;
    private CredentialItem.Username username;
    private CredentialItem.Password password;
    private CredentialItem.StringType maskedStringType;
    private CredentialItem.StringType unmaskedStringType;
    private CredentialItem.StringType specialStringType;
    private StandardUsernameCredentialsCredentialItem maskedUsername;
    private StandardUsernameCredentialsCredentialItem unmaskedUsername;

    @BeforeEach
    void setUp() throws Exception {
        String baseUri = "git://example.com/someone/somewhere";
        gitURI = new URIish(baseUri + ".git");
        gitURISlash = new URIish(baseUri + ".git/");
        gitURIShort = new URIish(baseUri);

        listener = StreamTaskListener.fromStdout();
        provider = new SmartCredentialsProvider(listener);
        username = new CredentialItem.Username();
        password = new CredentialItem.Password();

        maskedUsername = new StandardUsernameCredentialsCredentialItem(MASKED_USER_NAME_PROMPT, true);
        unmaskedUsername = new StandardUsernameCredentialsCredentialItem(UNMASKED_USER_NAME_PROMPT, false);

        maskedStringType = new CredentialItem.StringType(MASKED_STRING_TYPE_PROMPT, true);
        unmaskedStringType = new CredentialItem.StringType(UNMASKED_STRING_TYPE_PROMPT, false);
        specialStringType = new CredentialItem.StringType(SPECIAL_STRING_TYPE_PROMPT, false);

        assertNull(username.getValue());
        assertNull(password.getValue());
        assertNull(maskedUsername.getValue());
        assertNull(unmaskedUsername.getValue());
        assertNull(maskedStringType.getValue());
        assertNull(unmaskedStringType.getValue());
    }

    @Test
    void testClearCredentials() {
        provider.clearCredentials();

        assertFalse(provider.supports(username, password));
        assertFalse(provider.supports(maskedUsername, unmaskedUsername));
        assertFalse(provider.supports(maskedStringType, unmaskedStringType));
        assertFalse(provider.supports(specialStringType));
        assertFalse(provider.get(gitURI, username, password, maskedUsername, unmaskedUsername));

        assertFalse(username.isValueSecure());
        assertEquals("Username", username.getPromptText());
        assertNull(username.getValue());

        assertTrue(password.isValueSecure());
        assertEquals("Password", password.getPromptText());
        assertNull(password.getValue());

        assertTrue(maskedUsername.isValueSecure());
        assertEquals(MASKED_USER_NAME_PROMPT, maskedUsername.getPromptText());
        assertNull(maskedUsername.getValue());

        assertFalse(unmaskedUsername.isValueSecure());
        assertEquals(UNMASKED_USER_NAME_PROMPT, unmaskedUsername.getPromptText());
        assertNull(unmaskedUsername.getValue());
    }

    @Test
    void testAddCredentials() {
        String expectedUsername = "expected-add-credentials-username";
        String secretValue = "secret-value";
        Secret secret = Secret.fromString(secretValue);
        StandardUsernamePasswordCredentials credentials =
                new StandardUsernamePasswordCredentialsImpl(expectedUsername, secret);

        assertFalse(provider.supports(username, password));
        assertFalse(provider.supports(maskedUsername, unmaskedUsername));
        assertFalse(provider.supports(maskedStringType, unmaskedStringType));
        assertFalse(provider.supports(specialStringType));

        provider.addCredentials(gitURI.toString(), credentials);

        assertTrue(provider.supports(username, password));
        assertTrue(provider.supports(maskedUsername, unmaskedUsername));

        /* The supports() method does not know about the "special" StringType
         * which can be used to return the password plaintext.  Passing a
         * StringType with the prompt text "Password: " will return the plain
         * text password.
         */
        assertFalse(provider.supports(specialStringType));

        assertFalse(provider.supports(maskedStringType, unmaskedStringType));
        /* Check that if any arguments are not supported, method returns false */
        assertFalse(provider.supports(
                username,
                password,
                maskedUsername,
                unmaskedUsername,
                specialStringType,
                maskedStringType,
                unmaskedStringType));
        assertNull(specialStringType.getValue()); /* Expected, since nothing has been assigned */

        assertTrue(provider.get(gitURI, username, password, maskedUsername, unmaskedUsername, specialStringType));

        assertNotNull(username.getValue());
        assertNotNull(password.getValue());
        assertNotNull(maskedUsername.getValue());
        assertNotNull(unmaskedUsername.getValue());
        assertNotNull(specialStringType.getValue());

        assertFalse(username.isValueSecure());
        assertEquals("Username", username.getPromptText());
        assertEquals(expectedUsername, username.getValue());

        assertTrue(password.isValueSecure());
        assertEquals("Password", password.getPromptText());
        assertNotNull(password.getValue());

        assertTrue(maskedUsername.isValueSecure());
        assertEquals(expectedUsername, maskedUsername.getValue().getUsername());
        assertEquals(MASKED_USER_NAME_PROMPT, maskedUsername.getPromptText());

        assertFalse(unmaskedUsername.isValueSecure());
        assertEquals(expectedUsername, unmaskedUsername.getValue().getUsername());
        assertEquals(UNMASKED_USER_NAME_PROMPT, unmaskedUsername.getPromptText());

        assertFalse(specialStringType.isValueSecure());
        assertEquals(SPECIAL_STRING_TYPE_PROMPT, specialStringType.getPromptText());
        assertEquals(secretValue, specialStringType.getValue());
    }

    @Test
    void testAddDefaultCredentials() {
        String expectedUsername = "expected-add-credentials-username";
        String secretValue = "secret-value";
        Secret secret = Secret.fromString(secretValue);
        StandardUsernamePasswordCredentials credentials =
                new StandardUsernamePasswordCredentialsImpl(expectedUsername, secret);

        assertFalse(provider.supports(username, password));
        assertFalse(provider.supports(maskedUsername, unmaskedUsername));
        assertFalse(provider.supports(maskedStringType, unmaskedStringType));
        assertFalse(provider.supports(specialStringType));

        provider.addDefaultCredentials(credentials);

        assertTrue(provider.supports(username, password));
        assertTrue(provider.supports(maskedUsername, unmaskedUsername));

        /* The supports() method does not know about the "special" StringType
         * which can be used to return the password plaintext.  Passing a
         * StringType with the prompt text "Password: " will return the plain
         * text password.
         */
        assertFalse(provider.supports(specialStringType));

        assertFalse(provider.supports(maskedStringType, unmaskedStringType));
        /* Check that if any arguments are not supported, method returns false */
        assertFalse(provider.supports(
                username,
                password,
                maskedUsername,
                unmaskedUsername,
                specialStringType,
                maskedStringType,
                unmaskedStringType));

        assertTrue(provider.get(null, username, password, maskedUsername, unmaskedUsername, specialStringType));

        assertFalse(username.isValueSecure());
        assertEquals("Username", username.getPromptText());
        assertEquals(expectedUsername, username.getValue());

        assertTrue(password.isValueSecure());
        assertEquals("Password", password.getPromptText());
        assertNotNull(password.getValue());

        assertTrue(maskedUsername.isValueSecure());
        assertEquals(expectedUsername, maskedUsername.getValue().getUsername());
        assertEquals(MASKED_USER_NAME_PROMPT, maskedUsername.getPromptText());

        assertFalse(unmaskedUsername.isValueSecure());
        assertEquals(expectedUsername, unmaskedUsername.getValue().getUsername());
        assertEquals(UNMASKED_USER_NAME_PROMPT, unmaskedUsername.getPromptText());

        assertFalse(specialStringType.isValueSecure());
        assertEquals(SPECIAL_STRING_TYPE_PROMPT, specialStringType.getPromptText());
        assertEquals(secretValue, specialStringType.getValue());
    }

    @Test
    void testIsInteractive() {
        assertTrue(provider.isInteractive());
    }

    @Test
    void testClearCredentialsItem() {
        String expectedUsername = "expected-add-credentials-username";
        String secretValue = "secret-value";
        Secret secret = Secret.fromString(secretValue);
        StandardUsernamePasswordCredentials credentials =
                new StandardUsernamePasswordCredentialsImpl(expectedUsername, secret);
        maskedUsername.setValue(credentials);
        assertEquals(credentials, maskedUsername.getValue());
        maskedUsername.clear();
        assertNull(maskedUsername.getValue());
    }

    @Test
    void testGetThrowsException() {
        String expectedUsername = "expected-add-credentials-username";
        Secret secret = Secret.fromString("password-secret");
        StandardUsernamePasswordCredentials credentials =
                new StandardUsernamePasswordCredentialsImpl(expectedUsername, secret);
        provider.addCredentials(gitURI.toString(), credentials);
        assertThrows(
                UnsupportedCredentialItem.class,
                () -> provider.get(gitURI, username, password, maskedUsername, unmaskedUsername, maskedStringType));
    }

    @Test
    void testSimilarUrlsAcceptedForCredentials() {
        String expectedUsername = "expected-add-credentials-username";
        String secretValue = "secret-value";
        Secret secret = Secret.fromString(secretValue);
        StandardUsernamePasswordCredentials credentials =
                new StandardUsernamePasswordCredentialsImpl(expectedUsername, secret);

        assertFalse(provider.get(gitURI, username, password));

        provider.addCredentials(gitURI.toString(), credentials);

        assertTrue(provider.get(gitURI, username, password));
        assertTrue(provider.get(gitURISlash, username, password));
        assertTrue(provider.get(gitURIShort, username, password));
    }
}
