package com.atlassian.stash.internal.plugin;

import com.google.common.collect.Sets;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.GenericApplicationContext;

import java.util.*;

import static org.junit.Assert.*;

@RunWith(MockitoJUnitRunner.class)
public class OsgiSafeHostContainerTest {
    private final A a = new A("a");
    private final A aProxy = new A("aProxy");
    private final B b = new B("b");
    private final C c1 = new C("c1");
    private final C c2 = new C("c2");

    @Mock
    private OsgiSafeProxyProvider proxyProvider;
    private OsgiSafeHostContainer hostContainer;

    @Before
    public void setup() {
        GenericApplicationContext applicationContext = new GenericApplicationContext();
        applicationContext.refresh();

        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        beanFactory.registerSingleton("a", a);
        beanFactory.registerSingleton("b", b);
        beanFactory.registerSingleton("c", c1);
        beanFactory.registerSingleton("d", c2);

        OsgiSafeProxyProvider proxyProvider = new OsgiSafeProxyProvider(applicationContext);
        proxyProvider.register(aProxy, A.class);

        hostContainer = new OsgiSafeHostContainer(applicationContext, proxyProvider);
        beanFactory.registerSingleton("hostContainer", hostContainer);
    }

    @Test
    public void testCreateInjectsProxies() {
        TestBean bean = hostContainer.create(TestBean.class);

        assertNotNull(bean);
        assertSame(aProxy, bean.a);
        assertProxy(b, bean.b);
    }

    @Test
    public void testCreateVargsConstructor() {
        VarargsTestBean bean = hostContainer.create(VarargsTestBean.class);

        assertNotNull(bean);
        Set<C> values = Sets.newHashSet(Arrays.asList(bean.beans));
        assertEquals(2, values.size());

        Iterator<C> it = values.iterator();
        C first, second;
        C value = it.next();
        if (value.getValue().equals(c1.getValue())) {
            first = c1;
            second = c2;
        } else {
            first = c2;
            second = c1;
        }

        assertProxy(first, value);
        value = it.next();
        assertProxy(second, value);
    }

    @Test
    public void testGetComponentsReturnsProxies() {
        Map<String, A> components = hostContainer.getComponentsOfType(A.class);

        // verify that the pre-registered proxy is returned
        assertNotNull(components);
        assertEquals(1, components.size());
        assertSame(aProxy, components.get("a"));

        // B is not pre-registered - an on-the-fly created proxy should be returned
        Map<String, B> rawComponents = hostContainer.getComponentsOfType(B.class);

        assertNotNull(rawComponents);
        assertEquals(1, rawComponents.size());
        assertProxy(b, rawComponents.get("b"));
    }

    private void assertProxy(Bean bean, Bean proxy) {
        assertNotSame(bean, proxy);
        assertEquals(bean.getValue(), proxy.getValue());
    }

    private static class TestBean {
        public final A a;
        public final B b;

        public TestBean() {
            this(null, null);
        }

        public TestBean(A a) {
            this(a, null);
        }

        public TestBean(A a, B b) {
            this.a = a;
            this.b = b;
        }
    }

    private static class VarargsTestBean {
        public final C[] beans;

        private VarargsTestBean(C[] beans) {
            this.beans = beans;
        }
    }

    private static class Bean {
        final String value;

        public Bean(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }
    }

    private static class A extends Bean {
        public A(String value) {
            super(value);
        }
    }

    private static class B extends Bean {
        public B(String value) {
            super(value);
        }
    }

    private static class C extends Bean {
        public C(String value) {
            super(value);
        }
    }

}
