package com.r35157.nenjim.hubd.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.plugins.pricesource.PriceSource;
import com.r35157.assetaz.services.ticker.TickerService;
import com.r35157.assetaz.services.ticker.plugins.pricesource.impl.hardcoded.HardcodedPriceSource;
import com.r35157.assetaz.services.ticker.plugins.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.composer.NenjimComposer;
import com.r35157.nenjim.composer.impl.ref.NenjimComposerImpl;
import com.r35157.nenjim.hubd.NenjimHub;
import com.r35157.nenjim.npm.NenjimProcessManager;
import com.r35157.nenjim.npm.impl.ref.NenjimProcessManagerImpl;
import com.r35157.nenjim.ntt.NenjimTestTool;
import com.r35157.nenjim.ntt.impl.ref.NenjimTestToolImpl;
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 com.r35157.stm.SodaTaskManager;
import com.r35157.stm.impl.ref.SodaTaskManagerImpl;
import com.r35157.suwimo.hub.client.SuwimoClient;
import com.r35157.suwimo.hub.client.impl.j2fx.SuwimoClientImpl;
import crypto.r35157.nenjim.NenjimProcess;
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.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;

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

public class NenjimHubImpl implements NenjimHub {
    public NenjimHubImpl() {
        log.info("Initializing NenjimHub...");

        initializeAll();

        nextProcessId = 1;
        //processesScope = new StructuredTaskScope.ShutdownOnFailure();
        processes = new HashMap<>();
    }

    @Override
    public void start() throws Exception {

        log.info("Starting:");
        log.info("   Services...");
        tickerService.start();
        //jupiterPerpsAlarm.start();
        evelynServiceProd.start();
        evelynServiceTest.start();
        evelynIOUBurnerServiceProd.start();

        log.info("   Applications...");
        missionControl.start();
        //nenjimComposer.start();
        //processManager.start();
        //testTool.start();
        //sodaTaskManager.start();
        //suwimoClient.start();

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

        waitForAndShutdown();
    }

    @Override
    public HashMap<Integer, NenjimProcess> getRunningProcesses() {
        return processes;
    }

    @Override
    public void noop() {
        System.out.println("NenjimHub command: 'noop'");
    }

    @Override
    public void startProcess(String className) {
        ClassLoader loader = ClassLoader.getSystemClassLoader();

        /*processesScope.fork(() -> {
            Class<?> clazz = loader.loadClass(className);
            if (!NenjimProcess.class.isAssignableFrom(clazz)) {
                throw new IllegalArgumentException("'" + className + "' does NOT implement NenjimProcess");
            }

            NenjimProcess proc = (NenjimProcess) clazz.getDeclaredConstructor().newInstance();

            processes.put(nextProcessId, proc);
            nextProcessId++;

            NenjimContext context = new NenjimContext() { };
            proc.setNenjimProcessContext(context);
            proc.setNenjimHub(this);

            Thread.currentThread().setName(proc.getProcessName());
            System.out.println("   Starter Nenjim process '" + proc.getProcessName() + "'");
            proc.run();

            return null;
        });*/
    }

    private void initializeAll() {
        log.info("   Initializing building blocks...");
        initializeBuildingBlocks();

        log.info("   Initializing plugins...");
        initializePlugins();

        log.info("   Initializing services...");
        initializeServices();

        log.info("   Initializing applications...");
        initializeApplications();
    }

    private void initializeBuildingBlocks() {
        objectCache = new ObjectCacheImpl();
        clockUTC = Clock.systemUTC();
        cis = new HardcodedCurrencyIdentityService();
        httpClient = HttpClient.newHttpClient();
        objectMapper = new ObjectMapper();
        solanaBlockChain = new SolanaBlockChainImpl(httpClient, objectMapper, cis);
        cachedSolanaBlockChain = new CachedSolanaBlockChain(solanaBlockChain, objectCache);
        raydium = new RaydiumImpl(httpClient, objectMapper, cachedSolanaBlockChain, cis);
        eveUSDT = new TradingPair(cis.resolve(EVE_ID), cis.resolve(USDT_ID));
        solanaWalletEvelynPerpsTest = new SolanaWalletImpl(
                "6NyYEoXmJyBXvU4pvv9aoxZNdyqqtkmPBZFEDkRDHcqp",
                "evelyn_perps_test",
                solanaBlockChain
        );
        solanaWalletEvelynPerpsProd = new SolanaWalletImpl(
                "H5Yrdm7B4FHhTxhPw9SVwFzQyTEL6bAPptZuTyRxcepq",
                "evelyn_perps_prod",
                solanaBlockChain
        );
        solanaWalletEvelynBurnerProd = new SolanaWalletImpl(
                "5kjVTc1rebYHg56NyTxSSN1XMKqT2E81eWiaSx1CmzmV",
                "evelyn_burner_prod",
                solanaBlockChain
        );
    }

    private void initializePlugins() {
        hardcodedPriceSource = new HardcodedPriceSource(cis);
        raydiumPoolPriceSource = new RaydiumPoolPriceSource(raydium, EVE_USDT_RAYDIUM_POOL_ID, eveUSDT);
    }

    private void initializeServices() {
        tickerService = new TickerServiceImpl(
                //hardcodedPriceSource,     // Activating this could be dangerous as other services rely on these prices
                raydiumPoolPriceSource
        );

        jupiterServicePerpsProd = new AnchorIdlJupiterPerpsServiceImpl(
                cachedSolanaBlockChain,
                solanaWalletEvelynPerpsProd,
                cis
        );

        jupiterServicePerpsTest = new AnchorIdlJupiterPerpsServiceImpl(
                cachedSolanaBlockChain,
                solanaWalletEvelynPerpsTest,
                cis
        );

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

        evelynServiceProd = new EvelynImpl("Production", tickerService, eveUSDT, clockUTC);

        evelynServiceTest = new EvelynImpl("Test", tickerService, eveUSDT, clockUTC);

        jupiterSwapService = new JupiterSwapServiceImpl(solanaBlockChain, solanaWalletEvelynBurnerProd);
        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)
        );

        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)
        );

        // TODO: Old but more correct way to auto start plugins - but it is currently broken.
        /*
        String[] processesToAutoStart = {
                //"com.r35157.jupiterperpsalarm.impl.ref.JupiterPerpsAlarmImpl"
                //"crypto.r35157.nenjim.NenjimHubSocketAdminAdapter",
                //"crypto.r35157.nenjim.NenjimHubRestAdminAdapter",
                //"crypto.r35157.nenjim.NenjimHubRPCAdminAdapter",
                //"crypto.r35157.nenjim.SuwimoHub",
                //"crypto.r35157.nenjim.SodaTaskManager",
                //"crypto.r35157.assetaz.hub.AssetAZHub"
        };

        for (String processInterfaceName : processesToAutoStart) {
            startProcess(processInterfaceName);
        }
        */
    }

    private void initializeApplications() {
        //nenjimComposer = new NenjimComposerImpl();
        //processManager = new NenjimProcessManagerImpl();
        //testTool = new NenjimTestToolImpl();
        //sodaTaskManager = new SodaTaskManagerImpl();
        missionControl = new EvelynMissionControlImpl(evelynServiceProd, evelynServiceTest);
        //suwimoClient = new SuwimoClientImpl();
    }

    private void waitForAndShutdown() throws InterruptedException {
        /*
        try {
            processesScope.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        processesScope.close();
        */

        shutdownLatch.await();

        System.out.println("Nenjim is now shutdown (stopped all processes)!");
    }

    private static final Logger log = LoggerFactory.getLogger(NenjimHubImpl.class);
    private static final ΩRaydiumLiquidityPoolIdΩ EVE_USDT_RAYDIUM_POOL_ID =
            "8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe";
    private final CountDownLatch shutdownLatch = new CountDownLatch(1);

    private HashMap<Integer, NenjimProcess> processes;
    //private StructuredTaskScope.ShutdownOnFailure processesScope;
    private int nextProcessId;

    // 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 evelynPerpsProd;
    private SolanaWallet solanaWalletEvelynPerpsProd;
    private SolanaWallet solanaWalletEvelynPerpsTest;
    private SolanaWallet solanaWalletEvelynBurnerProd;
    private JupiterPerpsService jupiterServicePerpsProd;
    private JupiterPerpsService jupiterServicePerpsTest;

    // Plugins
    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;

    // Applications
    private EvelynMissionControl missionControl;
    private NenjimComposer nenjimComposer;
    private NenjimProcessManager processManager;
    private NenjimTestTool testTool;
    private SodaTaskManager sodaTaskManager;
    private SuwimoClient suwimoClient;
}
