package com.atlassian.stash.internal.profiles;

import com.atlassian.bitbucket.util.DevModeUtils;
import org.junit.Test;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;

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

public class DevModeConditionTest {

    @Test
    public void testMatchesForDev() {
        AnnotatedTypeMetadata metadata = metadataFor(Dev.class);

        assertFalse(isMatch(metadata, null, null)); //Production is the default
        assertFalse(isMatch(metadata, null, Boolean.FALSE));
        assertTrue(isMatch(metadata, null, Boolean.TRUE));
        assertTrue(isMatch(metadata, DevModeCondition.PROFILE_DEV, null));
        assertFalse(isMatch(metadata, DevModeCondition.PROFILE_PROD, null));
        assertTrue(isMatch(metadata, DevModeCondition.PROFILE_DEV, Boolean.FALSE));  //Prefers stash.spring.profiles
        assertFalse(isMatch(metadata, DevModeCondition.PROFILE_PROD, Boolean.TRUE)); //Prefers stash.spring.profiles
    }

    @Test
    public void testMatchesForProd() {
        AnnotatedTypeMetadata metadata = metadataFor(Production.class);

        assertTrue(isMatch(metadata, null, null)); //Production is the default
        assertTrue(isMatch(metadata, null, Boolean.FALSE));
        assertFalse(isMatch(metadata, null, Boolean.TRUE));
        assertFalse(isMatch(metadata, DevModeCondition.PROFILE_DEV, null));
        assertTrue(isMatch(metadata, DevModeCondition.PROFILE_PROD, null));
        assertFalse(isMatch(metadata, DevModeCondition.PROFILE_DEV, Boolean.FALSE)); //Prefers stash.spring.profiles
        assertTrue(isMatch(metadata, DevModeCondition.PROFILE_PROD, Boolean.TRUE));  //Prefers stash.spring.profiles
    }

    @Test
    public void testMatchesWithoutAnnotations() {
        AnnotatedTypeMetadata metadata = mock(AnnotatedTypeMetadata.class);

        //If the bean is not annotated explicitly as @Dev or @Production it will never match
        assertFalse(isMatch(metadata, null, null));
        assertFalse(isMatch(metadata, null, Boolean.FALSE));
        assertFalse(isMatch(metadata, null, Boolean.TRUE));
        assertFalse(isMatch(metadata, DevModeCondition.PROFILE_DEV, null));
        assertFalse(isMatch(metadata, DevModeCondition.PROFILE_DEV, Boolean.FALSE));
        assertFalse(isMatch(metadata, DevModeCondition.PROFILE_DEV, Boolean.TRUE));
        assertFalse(isMatch(metadata, DevModeCondition.PROFILE_PROD, null));
        assertFalse(isMatch(metadata, DevModeCondition.PROFILE_PROD, Boolean.FALSE));
        assertFalse(isMatch(metadata, DevModeCondition.PROFILE_PROD, Boolean.TRUE));
    }

    private static boolean isMatch(AnnotatedTypeMetadata metadata, String profiles, Boolean devMode) {
        Map<String, String> original = new HashMap<>();
        original.put(DevModeUtils.ATLASSIAN_DEV_MODE, System.getProperty(DevModeUtils.ATLASSIAN_DEV_MODE));
        original.put(DevModeCondition.PROP_PROFILES, System.getProperty(DevModeCondition.PROP_PROFILES));

        Map<String, String> desired = new HashMap<>();
        desired.put(DevModeUtils.ATLASSIAN_DEV_MODE, devMode == null ? null : devMode.toString());
        desired.put(DevModeCondition.PROP_PROFILES, profiles);
        try {
            setProperties(desired);

            return new DevModeCondition().matches(null, metadata);
        } finally {
            setProperties(original);
        }
    }

    private static <T extends Annotation> AnnotatedTypeMetadata metadataFor(Class<T> type) {
        AnnotatedTypeMetadata metadata = mock(AnnotatedTypeMetadata.class);
        when(metadata.isAnnotated(eq(type.getName()))).thenReturn(true);

        return metadata;
    }

    private static void setProperties(Map<String, String> properties) {
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            String value = entry.getValue();
            if (value == null) {
                System.getProperties().remove(entry.getKey());
            } else {
                System.setProperty(entry.getKey(), value);
            }
        }
    }
}