package com.r35157.nenjim.service.registry.impl.ref;

import com.fanitas.evelyn.core.Evelyn;
import com.fanitas.evelyn.core.impl.ref.EvelynImpl;
import com.fanitas.evelyn.service.burner.EvelynIOUBurnerService;
import com.fanitas.evelyn.service.burner.impl.ref.EvelynIOUBurnerServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.r35157.assetaz.services.cis.CurrencyTypeIds;
import com.r35157.assetaz.services.ticker.pricesource.PriceSource;
import com.r35157.assetaz.services.ticker.TickerService;
import com.r35157.assetaz.services.ticker.pricesource.impl.hardcoded.HardcodedPriceSource;
import com.r35157.assetaz.services.ticker.pricesource.impl.raydiumpool.RaydiumPoolPriceSource;
import com.r35157.assetaz.services.ticker.impl.ref.TickerServiceImpl;
import com.r35157.assetaz.services.cis.CurrencyIdentityService;
import com.r35157.assetaz.services.cis.impl.hc.HardcodedCurrencyIdentityService;
import com.r35157.cryptowallet.solana.SolanaWallet;
import com.r35157.cryptowallet.solana.impl.ref.SolanaWalletImpl;
import com.r35157.evelyn.emc.EvelynMissionControl;
import com.r35157.evelyn.emc.impl.ref.EvelynMissionControlImpl;
import com.r35157.jupiterperpsalarm.JupiterPerpsAlarm;
import com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl;
import com.r35157.libs.jupiter.perps.JupiterPerpsService;
import com.r35157.libs.jupiter.perps.impl.anchoridl.AnchorIdlJupiterPerpsServiceImpl;
import com.r35157.libs.jupiter.swap.JupiterSwapService;
import com.r35157.libs.jupiter.swap.impl.ref.JupiterSwapServiceImpl;
import com.r35157.libs.objcache.ObjectCache;
import com.r35157.libs.objcache.impl.ref.ObjectCacheImpl;
import com.r35157.libs.solana.impl.cached.CachedSolanaBlockChain;
import com.r35157.libs.valuetypes.basic.MoneyAmount;
import com.r35157.nenjim.component.NenjimApplication;
import com.r35157.nenjim.service.registry.NenjimRegistryService;
import com.r35157.nenjim.service.registry.NenjimRegistryServiceManager;
import com.r35157.nenjim.service.registry.valuetypes.NenjimComponentId;
import com.r35157.libs.raydium.Raydium;
import com.r35157.libs.raydium.impl.ref.RaydiumImpl;
import com.r35157.libs.solana.SolanaBlockChain;
import com.r35157.libs.solana.impl.ref.SolanaBlockChainImpl;
import com.r35157.libs.valuetypes.basic.TradingPair;
import com.r35157.service.notification.BoundNotificationService;
import com.r35157.service.notification.impl.discord.DiscordNotificationServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.net.URI;
import java.net.http.HttpClient;
import java.time.Clock;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.r35157.assetaz.services.cis.CurrencyTypeIds.EVE_ID;
import static com.r35157.assetaz.services.cis.CurrencyTypeIds.USDT_ID;

public final class NenjimRegistryServiceManagerImpl implements
        NenjimRegistryServiceManager,
        NenjimApplication {
    @Override
    public void start() throws Exception {
        if (!startAttempted.compareAndSet(false, true)) {
            throw new IllegalStateException(
                    "Nenjim Registry service manager start has already been attempted");
        }

        log.info("Initializing Nenjim Registry and components...");
        NenjimRegistryServiceImpl registry = new NenjimRegistryServiceImpl();
        NenjimRegistryService registryService = registry;
        NenjimRegistryServiceAdmin registryAdmin = registry;
        NenjimApplication registryApplication = registry;

        registryApplication.start();
        registryAdmin.registerComponent(REGISTRY_SERVICE_ID, registry);
        registryAdmin.registerComponent(REGISTRY_SERVICE_MANAGER_ID, this);
        initializeAndRegisterComponents(registryService, registryAdmin);

        log.info("Starting explicitly enabled Nenjim applications...");
        startApplication(registryService, TICKER_SERVICE_ID);
        startApplication(registryService, EVELYN_SERVICE_PROD_ID);
        startApplication(registryService, EVELYN_SERVICE_TEST_ID);
        startApplication(registryService, EVELYN_IOU_BURNER_PROD_ID);
        startApplication(registryService, EVELYN_MISSION_CONTROL_ID);

        System.out.println("Done - Now online!");

        waitForAndShutdown();
    }

    private void initializeAndRegisterComponents(
            NenjimRegistryService registryService,
            NenjimRegistryServiceAdmin registryAdmin) {
        initializeBuildingBlocks(registryAdmin);
        initializePriceSources(registryAdmin);
        initializeServices(registryService, registryAdmin);
        initializeApplications(registryAdmin);
    }

    private void initializeBuildingBlocks(
            NenjimRegistryServiceAdmin registryAdmin) {
        objectCache = new ObjectCacheImpl();
        registryAdmin.registerComponent(OBJECT_CACHE_ID, objectCache);

        clockUTC = Clock.systemUTC();
        cis = new HardcodedCurrencyIdentityService();
        registryAdmin.registerComponent(CURRENCY_IDENTITY_SERVICE_ID, cis);

        httpClient = HttpClient.newHttpClient();
        objectMapper = new ObjectMapper();
        solanaBlockChain = new SolanaBlockChainImpl(httpClient, objectMapper, cis);
        registryAdmin.registerComponent(SOLANA_BLOCKCHAIN_DIRECT_ID, solanaBlockChain);

        cachedSolanaBlockChain = new CachedSolanaBlockChain(solanaBlockChain, objectCache);
        registryAdmin.registerComponent(SOLANA_BLOCKCHAIN_CACHED_ID, cachedSolanaBlockChain);

        raydium = new RaydiumImpl(httpClient, objectMapper, cachedSolanaBlockChain, cis);
        registryAdmin.registerComponent(RAYDIUM_SERVICE_ID, raydium);

        eveUSDT = new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID));
        solanaWalletEvelynPerpsTest = new SolanaWalletImpl(
                "6NyYEoXmJyBXvU4pvv9aoxZNdyqqtkmPBZFEDkRDHcqp",
                "evelyn_perps_test",
                solanaBlockChain
        );
        registryAdmin.registerComponent(
                SOLANA_WALLET_EVELYN_PERPS_TEST_ID,
                solanaWalletEvelynPerpsTest);

        solanaWalletEvelynPerpsProd = new SolanaWalletImpl(
                "H5Yrdm7B4FHhTxhPw9SVwFzQyTEL6bAPptZuTyRxcepq",
                "evelyn_perps_prod",
                solanaBlockChain
        );
        registryAdmin.registerComponent(
                SOLANA_WALLET_EVELYN_PERPS_PROD_ID,
                solanaWalletEvelynPerpsProd);

        solanaWalletEvelynBurnerProd = new SolanaWalletImpl(
                "5kjVTc1rebYHg56NyTxSSN1XMKqT2E81eWiaSx1CmzmV",
                "evelyn_burner_prod",
                solanaBlockChain
        );
        registryAdmin.registerComponent(
                SOLANA_WALLET_EVELYN_BURNER_PROD_ID,
                solanaWalletEvelynBurnerProd);
    }

    private void initializePriceSources(
            NenjimRegistryServiceAdmin registryAdmin) {
        hardcodedPriceSource = new HardcodedPriceSource(cis);
        registryAdmin.registerComponent(
                HARDCODED_PRICE_SOURCE_ID,
                hardcodedPriceSource);

        raydiumPoolPriceSource = new RaydiumPoolPriceSource(raydium, EVE_USDT_RAYDIUM_POOL_ID, eveUSDT);
        registryAdmin.registerComponent(
                RAYDIUM_POOL_PRICE_SOURCE_ID,
                raydiumPoolPriceSource);
    }

    private void initializeServices(
            NenjimRegistryService registryService,
            NenjimRegistryServiceAdmin registryAdmin) {
        tickerService = new TickerServiceImpl(
                registryService,
                RAYDIUM_POOL_PRICE_SOURCE_ID
        );
        registryAdmin.registerComponent(TICKER_SERVICE_ID, tickerService);

        jupiterServicePerpsProd = new AnchorIdlJupiterPerpsServiceImpl(
                cachedSolanaBlockChain,
                solanaWalletEvelynPerpsProd,
                cis
        );
        registryAdmin.registerComponent(
                JUPITER_PERPS_EVELYN_PROD_ID,
                jupiterServicePerpsProd);

        jupiterServicePerpsTest = new AnchorIdlJupiterPerpsServiceImpl(
                cachedSolanaBlockChain,
                solanaWalletEvelynPerpsTest,
                cis
        );
        registryAdmin.registerComponent(
                JUPITER_PERPS_EVELYN_TEST_ID,
                jupiterServicePerpsTest);

        jupiterPerpsAlarm = new JupiterPerpsAlarmImpl(
                solanaWalletEvelynPerpsTest,
                jupiterServicePerpsTest,
                "--config=conf/alarms.conf"
        );
        registryAdmin.registerComponent(
                JUPITER_PERPS_ALARM_ID,
                jupiterPerpsAlarm);

        evelynServiceProd = new EvelynImpl("Production", tickerService, eveUSDT, clockUTC);
        registryAdmin.registerComponent(EVELYN_SERVICE_PROD_ID, evelynServiceProd);

        evelynServiceTest = new EvelynImpl("Test", tickerService, eveUSDT, clockUTC);
        registryAdmin.registerComponent(EVELYN_SERVICE_TEST_ID, evelynServiceTest);

        jupiterSwapService = new JupiterSwapServiceImpl(solanaBlockChain, solanaWalletEvelynBurnerProd);
        registryAdmin.registerComponent(
                JUPITER_SWAP_EVELYN_BURNER_PROD_ID,
                jupiterSwapService);
        discordAssetAZTokenNotificationService = new DiscordNotificationServiceImpl(
                URI.create("https://discordapp.com/api/webhooks/1537799046025187458/RtoNR5HnGmnutsMfM8AKP8mLDVqP1AxpEQ3p2IvBiaqVOjuVWNcShMkIh2D9uT27Mt4A"),
                "DiscordBot (https://git.r35157.com/r35157/com_r35157_nenjim-hubd-impl_ref, 0.1)",
                Duration.ofSeconds(10)
        );

        registryAdmin.registerComponent(
                DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID,
                discordAssetAZTokenNotificationService);

        List<MoneyAmount> burnTokenCandidates = List.of(new MoneyAmount(new ΩAmountΩ("0.1"), cis.resolve(CurrencyTypeIds.USDT_ID)));

        ΩUSDCAmountΩ maximumSwapAmountPerIterationInUSD = new ΩUSDCAmountΩ("0.25");

        evelynIOUBurnerServiceProd = new EvelynIOUBurnerServiceImpl(
                solanaWalletEvelynBurnerProd,
                jupiterSwapService,
                solanaBlockChain,
                tickerService,
                cis,
                discordAssetAZTokenNotificationService,
                burnTokenCandidates,
                maximumSwapAmountPerIterationInUSD,
                200,
                2*1000*60L,
                Duration.ofSeconds(10),
                Duration.ofHours(1)
        );
        registryAdmin.registerComponent(
                EVELYN_IOU_BURNER_PROD_ID,
                evelynIOUBurnerServiceProd);
    }

    private void initializeApplications(
            NenjimRegistryServiceAdmin registryAdmin) {
        missionControl = new EvelynMissionControlImpl(evelynServiceProd, evelynServiceTest);
        registryAdmin.registerComponent(
                EVELYN_MISSION_CONTROL_ID,
                missionControl);
    }

    private static void startApplication(
            NenjimRegistryService registryService,
            NenjimComponentId componentId) throws Exception {
        NenjimApplication application = registryService.getComponent(
                componentId,
                NenjimApplication.class);
        if (application == null) {
            throw new IllegalStateException(
                    "Required Nenjim application '" + componentId.value()
                            + "' is not registered as "
                            + NenjimApplication.class.getName());
        }
        application.start();
    }

    private void waitForAndShutdown() throws InterruptedException {
        shutdownLatch.await();

        System.out.println("Nenjim is now shut down!");
    }

    private static final Logger log = LoggerFactory.getLogger(
            NenjimRegistryServiceManagerImpl.class);
    private static final NenjimComponentId REGISTRY_SERVICE_ID =
            new NenjimComponentId("nenjim.registry.service");
    private static final NenjimComponentId REGISTRY_SERVICE_MANAGER_ID =
            new NenjimComponentId("nenjim.registry.service-manager");
    private static final NenjimComponentId OBJECT_CACHE_ID =
            new NenjimComponentId("nenjim.object-cache.default");
    private static final NenjimComponentId CURRENCY_IDENTITY_SERVICE_ID =
            new NenjimComponentId("assetaz.currency-identity.hardcoded");
    private static final NenjimComponentId SOLANA_BLOCKCHAIN_DIRECT_ID =
            new NenjimComponentId("solana.blockchain.direct");
    private static final NenjimComponentId SOLANA_BLOCKCHAIN_CACHED_ID =
            new NenjimComponentId("solana.blockchain.cached");
    private static final NenjimComponentId RAYDIUM_SERVICE_ID =
            new NenjimComponentId("raydium.service.default");
    private static final NenjimComponentId SOLANA_WALLET_EVELYN_PERPS_TEST_ID =
            new NenjimComponentId("solana.wallet.evelyn-perps-test");
    private static final NenjimComponentId SOLANA_WALLET_EVELYN_PERPS_PROD_ID =
            new NenjimComponentId("solana.wallet.evelyn-perps-prod");
    private static final NenjimComponentId SOLANA_WALLET_EVELYN_BURNER_PROD_ID =
            new NenjimComponentId("solana.wallet.evelyn-burner-prod");
    private static final NenjimComponentId HARDCODED_PRICE_SOURCE_ID =
            new NenjimComponentId("assetaz.price-source.hardcoded");
    private static final NenjimComponentId RAYDIUM_POOL_PRICE_SOURCE_ID =
            new NenjimComponentId(
                    "assetaz.price-source.raydium-pool.eve-usdt");
    private static final NenjimComponentId TICKER_SERVICE_ID =
            new NenjimComponentId("assetaz.ticker.default");
    private static final NenjimComponentId JUPITER_PERPS_EVELYN_PROD_ID =
            new NenjimComponentId("jupiter.perps.evelyn-prod");
    private static final NenjimComponentId JUPITER_PERPS_EVELYN_TEST_ID =
            new NenjimComponentId("jupiter.perps.evelyn-test");
    private static final NenjimComponentId JUPITER_PERPS_ALARM_ID =
            new NenjimComponentId("jupiter-perps-alarm.default");
    private static final NenjimComponentId EVELYN_SERVICE_PROD_ID =
            new NenjimComponentId("evelyn.service.prod");
    private static final NenjimComponentId EVELYN_SERVICE_TEST_ID =
            new NenjimComponentId("evelyn.service.test");
    private static final NenjimComponentId JUPITER_SWAP_EVELYN_BURNER_PROD_ID =
            new NenjimComponentId("jupiter.swap.evelyn-burner-prod");
    private static final NenjimComponentId DISCORD_ASSETAZ_TOKEN_NOTIFICATION_ID =
            new NenjimComponentId("notification.discord.assetaz-token");
    private static final NenjimComponentId EVELYN_IOU_BURNER_PROD_ID =
            new NenjimComponentId("evelyn.iou-burner.prod");
    private static final NenjimComponentId EVELYN_MISSION_CONTROL_ID =
            new NenjimComponentId("evelyn.mission-control.default");
    private static final ΩRaydiumLiquidityPoolIdΩ EVE_USDT_RAYDIUM_POOL_ID =
            "8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe";
    private final CountDownLatch shutdownLatch = new CountDownLatch(1);
    private final AtomicBoolean startAttempted = new AtomicBoolean();

    // Dependency components
    private ObjectCache objectCache;
    private Clock clockUTC;
    private CurrencyIdentityService cis;
    private SolanaBlockChain solanaBlockChain;
    private SolanaBlockChain cachedSolanaBlockChain;
    private Raydium raydium;
    private TradingPair eveUSDT;
    private HttpClient httpClient;
    private ObjectMapper objectMapper;
    private SolanaWallet solanaWalletEvelynPerpsProd;
    private SolanaWallet solanaWalletEvelynPerpsTest;
    private SolanaWallet solanaWalletEvelynBurnerProd;
    private JupiterPerpsService jupiterServicePerpsProd;
    private JupiterPerpsService jupiterServicePerpsTest;

    // Price-source components
    private PriceSource hardcodedPriceSource;
    private PriceSource raydiumPoolPriceSource;

    // Services
    private TickerService tickerService;
    private JupiterPerpsAlarm jupiterPerpsAlarm;
    private Evelyn evelynServiceProd;
    private Evelyn evelynServiceTest;
    private JupiterSwapService jupiterSwapService;
    private BoundNotificationService discordAssetAZTokenNotificationService;
    private EvelynIOUBurnerService evelynIOUBurnerServiceProd;

    // Application components
    private EvelynMissionControl missionControl;
}
