package com.atlassian.stash.internal.validation;

import com.atlassian.stash.internal.project.InternalProject;
import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.project.ProjectService;
import com.atlassian.bitbucket.user.DummySecurityService;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.user.SecurityService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;

import javax.validation.ConstraintValidatorContext;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class ProjectKeyValidatorTest {

    @Mock
    private ConstraintValidatorContext context;
    @Mock
    private ProjectService projectService;
    @Spy
    private SecurityService securityService = new DummySecurityService();
    @InjectMocks
    private ProjectKeyValidator validator;

    @Before
    public void setup() {
        validator.initialize(InternalProject.class.getAnnotation(ProjectKeyUnique.class));
    }

    @Test(expected = NullPointerException.class)
    public void testInitializeWithNullAnnotation() {
        validator.initialize(null);
    }

    @Test
    public void testIsValid() throws Throwable {
        InternalProject project = mock(InternalProject.class);
        when(project.getKey()).thenReturn("KEY");

        assertTrue(validator.isValid(project, context));

        verify(projectService).getByKey(eq("KEY"));
        verifyPromotion();
    }

    @Test
    public void testIsValidAcceptsSameKeyWithSameId() throws Throwable {
        Project found = mock(Project.class);
        when(found.getId()).thenReturn(42);

        InternalProject project = mock(InternalProject.class);
        when(project.getId()).thenReturn(42);
        when(project.getKey()).thenReturn("KEY");

        when(projectService.getByKey(eq("KEY"))).thenReturn(found);

        assertTrue(validator.isValid(project, context));

        verify(found).getId();
        verify(project).getId();
        verify(project, times(2)).getKey();
        verify(projectService).getByKey(eq("KEY"));
        verifyPromotion();
    }

    @Test
    public void testIsValidWithNullProject() {
        assertTrue(validator.isValid(null, context));

        verifyZeroInteractions(projectService);
        verifyZeroInteractions(securityService);
    }

    @Test
    public void testIsValidWithProjectWithDuplicateKey() throws Throwable {
        ConstraintValidatorContext.ConstraintViolationBuilder builder = mock(ConstraintValidatorContext.ConstraintViolationBuilder.class);

        Project found = mock(Project.class);
        when(found.getId()).thenReturn(2);

        InternalProject project = mock(InternalProject.class);
        when(project.getId()).thenReturn(1);
        when(project.getKey()).thenReturn("ME");

        when(context.buildConstraintViolationWithTemplate(eq(message()))).thenReturn(builder);
        when(projectService.getByKey(eq("ME"))).thenReturn(found);

        assertFalse(validator.isValid(project, context));

        InOrder ordered = inOrder(builder);
        ordered.verify(builder).addNode(eq("key"));
        ordered.verify(builder).addConstraintViolation();
        verify(context).disableDefaultConstraintViolation();
        verify(context).buildConstraintViolationWithTemplate(eq(message()));
        verify(projectService).getByKey(eq("ME"));
        verifyPromotion();
    }

    @Test
    public void testIsValidWithProjectWithNullKey() {
        InternalProject project = mock(InternalProject.class);

        assertFalse(validator.isValid(project, context));

        verifyZeroInteractions(projectService);
        verifyZeroInteractions(securityService);
    }

    private void verifyPromotion() throws Throwable {
        verify(securityService).withPermission(same(Permission.PROJECT_VIEW), notNull(String.class));
    }

    private static String message() {
        return InternalProject.class.getAnnotation(ProjectKeyUnique.class).message();
    }
}
