package com.after_sunrise.cryptocurrency.cryptotrader.framework.impl;

import com.after_sunrise.cryptocurrency.cryptotrader.TestModule;
import com.after_sunrise.cryptocurrency.cryptotrader.core.PropertyManager;
import com.after_sunrise.cryptocurrency.cryptotrader.core.ServiceFactory;
import com.after_sunrise.cryptocurrency.cryptotrader.framework.Context;
import com.after_sunrise.cryptocurrency.cryptotrader.framework.Estimator;
import com.after_sunrise.cryptocurrency.cryptotrader.framework.Estimator.Estimation;
import com.after_sunrise.cryptocurrency.cryptotrader.framework.Request;
import com.google.common.collect.Sets;
import org.mockito.Mockito;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.math.BigDecimal;
import java.util.Map;
import java.util.TreeMap;

import static java.math.BigDecimal.*;
import static org.mockito.Mockito.*;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;

/**
 * @author takanori.takase
 * @version 0.0.1
 */
public class EstimatorImplTest {

    private static final BigDecimal HALF = new BigDecimal("0.5");

    private EstimatorImpl target;

    private TestModule module;

    private Map<String, Estimator> services;

    private Context context;

    private Request request;

    @BeforeMethod
    public void setUp() throws Exception {

        services = new TreeMap<>();
        services.put("id0", mock(Estimator.class));
        services.put("id1", mock(Estimator.class));
        services.put("id2", mock(Estimator.class));
        services.put("id3", mock(Estimator.class));
        services.put("id4", mock(Estimator.class));
        services.put("id5", mock(Estimator.class));
        services.put("id6", mock(Estimator.class));
        services.put("id7", mock(Estimator.class));
        services.forEach((k, v) -> when(v.get()).thenReturn(k));

        module = new TestModule();
        when(module.getMock(ServiceFactory.class).loadMap(Estimator.class)).thenReturn(services);

        context = module.getMock(Context.class);
        request = module.createRequestBuilder().build();

        target = new EstimatorImpl(module.createInjector());

    }

    @Test
    public void testGet() {
        assertEquals(target.get(), "*");
    }

    @Test
    public void testEstimate() throws Exception {

        PropertyManager manager = module.getMock(PropertyManager.class);
        when(manager.getEstimationThreshold(request.getSite(), request.getInstrument())).thenReturn(ZERO);
        when(manager.getEstimators(request.getSite(), request.getInstrument()))
                .thenReturn(Sets.newHashSet("id0", "id1", "id2", "id3", "id4", "id5:0.7", "id6"));

        // Valid Estimation (1st)
        Estimation estimation0 = Estimation.builder().price(TEN).confidence(HALF).build();
        when(services.get("id0").estimate(context, request)).thenReturn(estimation0);

        // Null Estimation
        when(services.get("id1").estimate(context, request)).thenReturn(null);

        // Null Price
        Estimation estimation2 = Estimation.builder().confidence(ONE).build();
        when(services.get("id2").estimate(context, request)).thenReturn(estimation2);

        // Null Confidence
        Estimation estimation3 = Estimation.builder().price(ONE).build();
        when(services.get("id3").estimate(context, request)).thenReturn(estimation3);

        // Execution Failure
        when(services.get("id4").estimate(context, request)).thenThrow(new RuntimeException("test"));

        // Valid Estimation - Under-Weight (2nd)
        Estimation estimation5 = Estimation.builder().price(ONE).confidence(ONE).build();
        when(services.get("id5").estimate(context, request)).thenReturn(estimation5);

        // Valid Estimation (3rd)
        Estimation estimation6 = Estimation.builder().price(ONE).confidence(ZERO).build();
        when(services.get("id6").estimate(context, request)).thenReturn(estimation6);

        // Valid Estimation but filtered out.
        Estimation estimation7 = Estimation.builder().price(ONE).confidence(ONE).build();
        when(services.get("id7").estimate(context, request)).thenReturn(estimation7);

        // Consensus
        // Price = [(10 * 0.5) + (1 * (1 * 1.1)) + (1 * 0)] / (0.5 + (1 * 1.1) + 0) = 6.1 / 1.6 = 3.8125
        // Confidence = (0.5 + 1 + 0) / 3 = 0.5
        Estimation result = target.estimate(context, request);
        assertEquals(result.getPrice(), new BigDecimal("4.7500000000"));
        assertEquals(result.getConfidence(), new BigDecimal("0.4000000000"));
        services.values().stream()
                .filter(e -> !"id7".equals(e.get()))
                .forEach(mock -> Mockito.verify(mock).estimate(context, request));
        services.values().stream()
                .filter(e -> "id7".equals(e.get()))
                .forEach(mock -> Mockito.verify(mock, never()).estimate(context, request));

    }

    @Test
    public void testEstimate_None() throws Exception {

        Estimation result = target.estimate(context, request);

        assertNull(result.getPrice());

        assertNull(result.getConfidence());

        services.values().stream()
                .forEach(mock -> Mockito.verify(mock, never()).estimate(context, request));

    }

}