package com.atlassian.stash.internal.repository;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.repository.RepositoryService;
import com.atlassian.bitbucket.user.EscalatedSecurityContext;
import com.atlassian.bitbucket.user.SecurityService;
import com.atlassian.bitbucket.validation.annotation.RequiredString;
import com.atlassian.bitbucket.validation.groups.Create;
import com.atlassian.bitbucket.validation.groups.Update;
import com.atlassian.stash.internal.project.InternalNormalProjectTest;
import com.atlassian.stash.internal.validation.AbstractValidationTest;
import com.atlassian.stash.internal.validation.RepositoryUniqueness;
import com.atlassian.stash.internal.validation.RepositoryUniquenessValidator;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorFactory;

import static org.mockito.Matchers.*;
import static org.mockito.Mockito.when;

/**
 * Tests validation as the annotations are applied to {@link InternalRepository}. Note that this test does <i>not</i>
 * cover uniqueness tests for the slug; those permutations are handled by the unit test for the validator class that
 * actually applies the constraint.
 */
@RunWith(MockitoJUnitRunner.class)
public class InternalRepositoryTest extends AbstractValidationTest {

    private static final String NAME_PATTERN_MESSAGE = getMessage(Repository.class, "getName", RequiredString.class);
    private static final String UNIQUENESS_MESSAGE = getDefaultMessage(RepositoryUniqueness.class);

    @Mock
    private I18nService i18nService;
    @Mock
    private RepositoryService repositoryService;
    @Mock
    private EscalatedSecurityContext securityContext;
    @Mock
    private SecurityService securityService;

    @Before
    public void setup() {
        when(i18nService.getMessage(anyString(), anyVararg())).thenAnswer(new Answer<Object>() {

            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return invocation.getArguments()[0];
            }
        });
        when(securityService.withPermission(eq(Permission.REPO_READ), anyString())).thenReturn(securityContext);
    }

    @Test
    public void testSlugify() throws Exception {
        assertEquals("Slugify Failed", "abc-123", InternalRepository.slugify("ABC 123"));
        assertEquals("Slugify Failed", "abc-123", InternalRepository.slugify("\u00e4b\u00e7 123"));
        assertEquals("Slugify failed", "a-b-c-1_2_3.js", InternalRepository.slugify("a b c-1_2_3.js"));
        assertEquals("Slugify failed", "jose", InternalRepository.slugify("jos\u00e9"));
        assertEquals("Slugify failed", "jose", InternalRepository.slugify("JOS\u00c9"));
    }

    @Test
    public void testValidation() {
        ImmutableMap<String, String> expected = ImmutableMap.<String, String>builder()
                .put("hierarchyId", REQUIRED_MESSAGE)
                .put("name", NAME_PATTERN_MESSAGE)
                .put("scmId", REQUIRED_MESSAGE)
                .put("slug", REQUIRED_MESSAGE)
                .build();

        InternalRepository repository = new InternalRepository.Builder()
                .hierarchyId(StringUtils.repeat("HhH", 7))
                .name(StringUtils.repeat("NnNnNnNn", 16) + "N") //129 character name, slug also too long
                .project(InternalNormalProjectTest.createValidProject())
                .scmId(StringUtils.repeat("SsSs", 64))
                .build();
        validate(repository, expected);
        validate(repository, expected, Create.class);
        validate(repository, expected, Update.class);
    }

    @Test
    public void testValidationMissingProject() {
        ImmutableMap<String, String> expected = ImmutableMap.of("", UNIQUENESS_MESSAGE);

        InternalRepository repository = new InternalRepository.Builder()
                .hierarchyId("wtfbbq")
                .name("Name")
                .scmId("git")
                .build();
        validate(repository); //Default group does not verify "repository uniqueness"
        validate(repository, expected, Create.class);
        validate(repository, expected, Update.class);
    }

    @Override
    protected ConstraintValidatorFactory buildConstraintValidatorFactory() {
        return new ConstraintValidatorFactory() {

            @Override
            public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
                try {
                    T instance = key.newInstance();
                    if (instance instanceof RepositoryUniquenessValidator) {
                        RepositoryUniquenessValidator validator = (RepositoryUniquenessValidator) instance;
                        validator.setI18nService(i18nService);
                        validator.setRepositoryService(repositoryService);
                        validator.setSecurityService(securityService);
                    }
                    return instance;
                } catch (Exception e) {
                    throw new RuntimeException("Problem creating instance of " + key.getCanonicalName(), e);
                }
            }

            @Override
            public void releaseInstance(ConstraintValidator<?, ?> constraintValidator) {
                // Intentionally not implemented
            }
        };
    }
}
