package com.intrasoft.ermis.transit.officeofdeparture.service;


import com.intrasoft.ermis.ddntav5152.messages.GuaranteeType06;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.ReferenceDataAdapter;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.CodeList;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.CodeListDomain;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.CodeListItem;
import com.intrasoft.ermis.transit.officeofdeparture.domain.ExchangeRate;
import com.intrasoft.ermis.transit.officeofdeparture.domain.ExchangeRates;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import com.intrasoft.proddev.referencedata.shared.enums.CodelistKeyEnum;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service

@RequiredArgsConstructor
public class CurrencyConverterService {

    public static final String EUR = "EUR";

    // Most of Rate Values in CL048 have 4 decimals
    private static final int RATE_SCALE = 4;

    private static final int CURRENCY_SCALE = 2;

    private static final String RATE_VALUE_ATTRIBUTE_KEY_LITERAL = "RateValue";

    private final ReferenceDataAdapter referenceDataAdapter;

    /**
     * Retrieves from Reference Data and stores in a {@link ExchangeRates} instance all the Exchange Rates for Related currencies,
     * excluding EUR currency if set as so.
     */
    public ExchangeRates getRelatedExchangeRates(List<GuaranteeType06> guaranteeType06List, boolean excludeEuroCurrency) {

        final CodeList codeList = referenceDataAdapter.getCodeList(CodelistKeyEnum.CURRENCY_CODES.getKey(), CodeListDomain.NCTSP5);
        Set<String> relatedCurrencies = extractDistinctCurrenciesFromGuarantees(guaranteeType06List, excludeEuroCurrency);
        List<CodeListItem> filteredCodelistItemList = codeList.getItems()
                                                              .stream()
                                                              .filter(item -> relatedCurrencies.contains(item.getCode().toUpperCase(Locale.ROOT)))
                                                              .collect(Collectors.toList());
        ExchangeRates exchangeRates = new ExchangeRates();

        filteredCodelistItemList.forEach(item -> {
            BigDecimal euro2CurrencyRate = getRateValueFromCodeListItem(item);
            if (euro2CurrencyRate.doubleValue() > 0) {
                exchangeRates.putExchangeRate(ExchangeRate.builder()
                                                          .currencyCL(item.getCode().toUpperCase(Locale.ROOT))
                                                          .euro2CurrencyRate(euro2CurrencyRate)
                                                          .currency2EuroRate(BigDecimal.ONE.divide(euro2CurrencyRate, RATE_SCALE, RoundingMode.HALF_EVEN))
                                                          .build());
            }
        });

        return exchangeRates;
    }

    public BigDecimal convertEuro2Currency(BigDecimal amountInEuro, @NonNull String currencyCLTo,
            ExchangeRates exchangeRates) {
        return amountInEuro.multiply(exchangeRates.getExchangeRate(currencyCLTo).getEuro2CurrencyRate()).setScale(CURRENCY_SCALE, RoundingMode.HALF_EVEN);
    }

    public BigDecimal convertCurrency2Euro(BigDecimal amountInCurrency, @NonNull String currencyCLFrom,
            ExchangeRates exchangeRates) {
        return amountInCurrency.multiply(exchangeRates.getExchangeRate(currencyCLFrom).getCurrency2EuroRate()).setScale(CURRENCY_SCALE, RoundingMode.HALF_EVEN);
    }

    public BigDecimal convertCurrency2Currency(BigDecimal amount, @NonNull String currencyCLFrom,
            @NonNull String currencyCLTo, ExchangeRates exchangeRates) {

        if (currencyCLFrom.equalsIgnoreCase(currencyCLTo))
            return amount;
        // If one of the currencies is EUR avoid the unnecessary double conversion
        if (EUR.equalsIgnoreCase(currencyCLFrom))
            return convertEuro2Currency(amount, currencyCLTo, exchangeRates);
        else if (EUR.equalsIgnoreCase(currencyCLTo))
            return convertCurrency2Euro(amount, currencyCLFrom, exchangeRates);
        else {
            // None of the 2 currencies is EUR
            return convertEuro2Currency(
                    convertCurrency2Euro(amount, currencyCLFrom, exchangeRates), currencyCLTo, exchangeRates);
        }
    }

    private Set<String> extractDistinctCurrenciesFromGuarantees(List<GuaranteeType06> guaranteeType06List, boolean excludeEuroCurrency) {

        return guaranteeType06List.stream()
                                  .flatMap(guaranteeType06 -> guaranteeType06.getGuaranteeReference().stream())
                                  .map(guaranteeReference -> guaranteeReference.getCurrency().toUpperCase(Locale.ROOT))
                                  .filter(currency -> !excludeEuroCurrency || !EUR.equalsIgnoreCase(currency))
                                  .collect(Collectors.toSet());
    }

    private BigDecimal getRateValueFromCodeListItem(CodeListItem codeListItem) {
        return new BigDecimal(
                codeListItem.getAttributes()
                            .stream()
                            .filter(attribute -> attribute.getKey().equals(RATE_VALUE_ATTRIBUTE_KEY_LITERAL))
                            .findFirst()
                            .orElseThrow(() -> new RuntimeException(
                                    "No Attribute: [" + RATE_VALUE_ATTRIBUTE_KEY_LITERAL + "] found in CodeListItem: "
                                            + codeListItem.getCode()))
                            .getValue());
    }
}

package com.intrasoft.ermis.trs.officeofdeparture.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.when;

import com.intrasoft.ermis.ddntav5152.messages.GuaranteeReferenceType04;
import com.intrasoft.ermis.ddntav5152.messages.GuaranteeType06;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.ReferenceDataAdapter;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.Attribute;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.CodeList;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.CodeListDomain;
import com.intrasoft.ermis.platform.shared.client.reference.data.core.domain.CodeListItem;
import com.intrasoft.ermis.transit.officeofdeparture.domain.ExchangeRates;
import com.intrasoft.ermis.transit.officeofdeparture.service.CurrencyConverterService;
import com.intrasoft.proddev.referencedata.shared.enums.CodelistKeyEnum;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class CurrencyConverterServiceUT {

    private static final String RATE_VALUE_ATTRIBUTE_KEY_LITERAL = "RateValue";

    private static final int RATE_SCALE = 4;

    @Mock
    private ReferenceDataAdapter referenceDataAdapter;

    @InjectMocks
    private CurrencyConverterService currencyConverterService;

    @ParameterizedTest
    @DisplayName("Test - Get Related Exchange Rates")
    @ValueSource(booleans = {true, false})
    void testGetRelatedExchangeRates(boolean excludeEuroCurrency) {
        // Test data:
        CodeList mockCodelist = buildMockCodelist();
        List<GuaranteeType06> mockGuaranteeList = buildGuaranteeList();
        // Stubs:
        when(referenceDataAdapter.getCodeList(CodelistKeyEnum.CURRENCY_CODES.getKey(), CodeListDomain.NCTSP5)).thenReturn(mockCodelist);
        // Call tested method:
        ExchangeRates result = currencyConverterService.getRelatedExchangeRates(mockGuaranteeList, excludeEuroCurrency);
        // Assertions:
        try {
            result.getExchangeRate("GBP");
            // Fail if above call does not throw exception:
            fail();
        } catch (IllegalArgumentException exception) {
            if (!"Currency code not valid or not supported : 'GBP'".equals(exception.getMessage())) {
                fail();
            }
        }
        if (excludeEuroCurrency) {
            try {
                result.getExchangeRate("EUR");
                // Fail if above call does not throw exception:
                fail();
            } catch (IllegalArgumentException exception) {
                if (!"Currency code not valid or not supported : 'EUR'".equals(exception.getMessage())) {
                    fail();
                }
            }
        } else {
            assertEquals("EUR", result.getExchangeRate("EUR").getCurrencyCL());
            assertEquals(BigDecimal.valueOf(1.0), result.getExchangeRate("EUR").getEuro2CurrencyRate());
            assertEquals(BigDecimal.valueOf(1.0).setScale(RATE_SCALE, RoundingMode.HALF_EVEN), result.getExchangeRate("EUR").getCurrency2EuroRate());
        }
        assertEquals("DKK", result.getExchangeRate("DKK").getCurrencyCL());
        assertEquals(BigDecimal.valueOf(5.0), result.getExchangeRate("DKK").getEuro2CurrencyRate());
        assertEquals(BigDecimal.valueOf(0.2).setScale(RATE_SCALE, RoundingMode.HALF_EVEN), result.getExchangeRate("DKK").getCurrency2EuroRate());
    }

    private CodeList buildMockCodelist() {

        CodeListItem itemDKK = CodeListItem.builder()
                                           .code("DKK")
                                           .attributes(List.of(Attribute.builder().key(RATE_VALUE_ATTRIBUTE_KEY_LITERAL).value("5.0").build()))
                                           .build();

        CodeListItem itemEUR = CodeListItem.builder()
                                           .code("EUR")
                                           .attributes(List.of(Attribute.builder().key(RATE_VALUE_ATTRIBUTE_KEY_LITERAL).value("1.0").build()))
                                           .build();

        CodeListItem itemGBP = CodeListItem.builder()
                                           .code("GBP")
                                           .attributes(List.of(Attribute.builder().key(RATE_VALUE_ATTRIBUTE_KEY_LITERAL).value("2.0").build()))
                                           .build();

        return CodeList.builder()
                       .code(CodelistKeyEnum.CURRENCY_CODES.getKey())
                       .items(List.of(itemDKK, itemEUR, itemGBP))
                       .build();
    }

    private List<GuaranteeType06> buildGuaranteeList() {

        GuaranteeType06 firstGuarantee = new GuaranteeType06();
        GuaranteeReferenceType04 firstGuaranteeReferenceOne = new GuaranteeReferenceType04();
        GuaranteeReferenceType04 firstGuaranteeReferenceTwo = new GuaranteeReferenceType04();
        firstGuaranteeReferenceOne.setCurrency("DKK");
        firstGuaranteeReferenceOne.setAmountConcerned(BigDecimal.TEN);
        firstGuaranteeReferenceTwo.setCurrency("EUR");
        firstGuaranteeReferenceTwo.setAmountConcerned(BigDecimal.ONE);
        firstGuarantee.getGuaranteeReference().addAll(List.of(firstGuaranteeReferenceOne, firstGuaranteeReferenceTwo));

        GuaranteeType06 secondGuarantee = new GuaranteeType06();
        GuaranteeReferenceType04 secondGuaranteeReferenceOne = new GuaranteeReferenceType04();
        secondGuaranteeReferenceOne.setCurrency("DKK");
        secondGuaranteeReferenceOne.setAmountConcerned(BigDecimal.TEN);
        secondGuarantee.getGuaranteeReference().add(secondGuaranteeReferenceOne);

        return new ArrayList<>(List.of(firstGuarantee, secondGuarantee));
    }
}
