package org.jenkinsci.plugins.gitclient.credentials;

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

import com.cloudbees.plugins.credentials.CredentialsDescriptor;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.util.Secret;
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.transport.CredentialItem;
import org.eclipse.jgit.transport.URIish;
import org.jenkinsci.plugins.gitclient.jgit.CredentialsProviderImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class CredentialsProviderImplTest {

    private static final String USER_NAME = "user-name";
    private static final String SECRET_VALUE = "secret-credentials-provider-impl-test";

    private CredentialsProviderImpl provider;
    private URIish uri;

    @BeforeEach
    void setUp() throws Exception {
        uri = new URIish("git://example.com/someone/somewhere.git");
        Secret secret = Secret.fromString(SECRET_VALUE);
        StandardUsernameCredentials cred = new StandardUsernamePasswordCredentialsImpl(USER_NAME, secret);
        provider = new CredentialsProviderImpl(cred);
    }

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

    @Test
    void testSupportsNullItems() {
        CredentialItem.Username nullItem = null;
        assertFalse(provider.supports(nullItem));
    }

    @Test
    void testSupportsUsername() {
        CredentialItem.Username username = new CredentialItem.Username();
        assertNull(username.getValue());
        assertTrue(provider.supports(username));
        assertTrue(provider.get(uri, username));
        assertEquals(USER_NAME, username.getValue());
    }

    @Test
    void testSupportsPassword() {
        CredentialItem.Password password = new CredentialItem.Password();
        assertNull(password.getValue());
        assertTrue(provider.supports(password));
        assertTrue(provider.get(uri, password));
        assertNotNull(password.getValue());
    }

    @Test
    void testSupportsSpecialStringType() {
        CredentialItem.StringType specialStringType = new CredentialItem.StringType("Password: ", false);
        assertNull(specialStringType.getValue());

        /* 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));
        assertTrue(provider.get(uri, specialStringType));
        assertEquals(SECRET_VALUE, specialStringType.getValue());
    }

    @Test
    void testSpecialStringTypeThrowsException() {
        CredentialItem.StringType specialStringType = new CredentialItem.StringType("Bad Password: ", false);
        assertFalse(provider.supports(specialStringType));
        assertThrows(UnsupportedCredentialItem.class, () -> provider.get(uri, specialStringType));
    }

    @Test
    void testThrowsUnsupportedOperationException() {
        CredentialItem.InformationalMessage message = new CredentialItem.InformationalMessage("Some info");
        assertFalse(provider.supports(message));
        assertThrows(UnsupportedCredentialItem.class, () -> provider.get(uri, message));
    }

    @Test
    void testSupportsDisallowed() {
        StandardUsernameCredentials badCred = new MyUsernameCredentialsImpl(USER_NAME);
        CredentialsProviderImpl badProvider = new CredentialsProviderImpl(badCred);
        CredentialItem.Username username = new CredentialItem.Username();
        assertNull(username.getValue());
        assertFalse(badProvider.supports(username));
        assertFalse(badProvider.get(uri, username));
        assertNull(username.getValue());
    }

    private record MyUsernameCredentialsImpl(String username) implements StandardUsernameCredentials {

        @Override
        @NonNull
        public String getDescription() {
            throw new UnsupportedOperationException("Do not call");
        }

        @Override
        @NonNull
        public String getId() {
            throw new UnsupportedOperationException("Do not call");
        }

        @Override
        public CredentialsScope getScope() {
            throw new UnsupportedOperationException("Do not call");
        }

        @Override
        @NonNull
        public CredentialsDescriptor getDescriptor() {
            throw new UnsupportedOperationException("Do not call");
        }

        @Override
        @NonNull
        public String getUsername() {
            return username;
        }
    }
}
