import { __decorate, __metadata } from "tslib";
import DataLoader from 'dataloader';
import { format, subHours } from 'date-fns';
import { GraphQLError } from 'graphql';
import { Injectable, Scope } from 'graphql-modules';
import { sql } from '@pgtyped/runtime';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { CoinMarketCapProvider } from '../../app-providers/coinmarketcap.js';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
const getRateByCurrencyAndDate = sql `
    SELECT *
    FROM accounter_schema.crypto_exchange_rates
    WHERE coin_symbol = $currency
    AND date = $date;`;
const insertRates = sql `
  INSERT INTO accounter_schema.crypto_exchange_rates (date, coin_symbol, value, against, sample_date)
  VALUES $$rates(date, currency, value, against, sampleDate)
  ON CONFLICT (date, coin_symbol, against) DO UPDATE
  SET value = EXCLUDED.value
  RETURNING *;
`;
const getCryptoCurrenciesBySymbol = sql `
    SELECT *
    FROM accounter_schema.crypto_currencies
    WHERE symbol in $$currencySymbols;`;
let CryptoExchangeProvider = class CryptoExchangeProvider {
    db;
    adminContextProvider;
    coinMarketCap;
    constructor(db, adminContextProvider, coinMarketCap) {
        this.db = db;
        this.adminContextProvider = adminContextProvider;
        this.coinMarketCap = coinMarketCap;
    }
    async getCryptoExchangeRatesFromDB(currency, date) {
        return getRateByCurrencyAndDate.run({ currency, date }, this.db);
    }
    async addRates(rates) {
        this.clearCache();
        return insertRates.run({ rates }, this.db);
    }
    addCryptoRateLoader = new DataLoader((keys) => this.addRates(keys.map(rate => ({ ...rate, against: rate.against ?? null }))), {
        cache: false,
    });
    async getCryptoCurrenciesBySymbol(symbols) {
        const currencies = await getCryptoCurrenciesBySymbol.run({
            currencySymbols: symbols,
        }, this.db);
        return symbols.map(symbol => currencies.find(currency => currency.symbol === symbol));
    }
    getCryptoCurrenciesBySymbolLoader = new DataLoader((symbols) => this.getCryptoCurrenciesBySymbol(symbols));
    async getCryptoExchangeRatesFromAPI(currencySymbol, date) {
        // Fetch CoinMarketCap id from DB
        const currencyInfo = await this.getCryptoCurrenciesBySymbolLoader.load(currencySymbol);
        if (!currencyInfo) {
            throw new GraphQLError(`No data found for crypto currency ${currencySymbol}`);
        }
        const coinmarketcapId = currencyInfo?.coinmarketcap_id;
        if (!coinmarketcapId) {
            throw new GraphQLError(`No CoinMarketCap id found for crypto currency ${currencySymbol}`);
        }
        const fromDate = subHours(date, 23);
        const from = Math.floor(fromDate.getTime() / 1000);
        const to = Math.floor(date.getTime() / 1000);
        const ratesObject = await this.coinMarketCap.getExchangeRates(coinmarketcapId, {
            fromTimeStamp: from,
            toTimeStamp: to,
        });
        let timestamp = undefined;
        let rate = undefined;
        for (const [rawTimestamp, rateData] of Object.entries(ratesObject)) {
            const newTimestamp = Number(rawTimestamp);
            if (newTimestamp <= to && rateData?.c?.[0]) {
                if (!timestamp || !rate) {
                    timestamp = newTimestamp;
                    rate = rateData.c[0];
                }
                if (newTimestamp > timestamp) {
                    timestamp = newTimestamp;
                    rate = rateData.c[0];
                }
            }
        }
        if (!rate || !timestamp) {
            throw new GraphQLError(`No suitable rate of ${currencySymbol} found in CoinMarketCap`);
        }
        const sampleDate = new Date(timestamp * 1000);
        const { defaultCryptoConversionFiatCurrency } = await this.adminContextProvider.getVerifiedAdminContext();
        // Add rate to DB
        await this.addCryptoRateLoader.load({
            date,
            currency: currencySymbol,
            value: rate,
            against: defaultCryptoConversionFiatCurrency,
            sampleDate,
        });
        return rate;
    }
    getCryptoExchangeRateLoader = new DataLoader(async (keys) => {
        const { defaultCryptoConversionFiatCurrency } = await this.adminContextProvider.getVerifiedAdminContext();
        const rates = await Promise.all(keys.map(async ({ cryptoCurrency, date, against = defaultCryptoConversionFiatCurrency }) => {
            // Fetch from DB first
            const res = await this.getCryptoExchangeRatesFromDB(cryptoCurrency, date);
            if (res.length > 0) {
                return res[0];
            }
            // If not found in DB, fetch from API
            const rate = await this.getCryptoExchangeRatesFromAPI(cryptoCurrency, date);
            if (rate == null) {
                return new GraphQLError(`No data found for ${cryptoCurrency} on ${format(date, 'dd-MM-yyyy')}`);
            }
            return {
                date,
                coin_symbol: cryptoCurrency,
                value: rate.toString(),
                against,
            };
        }));
        return rates;
    }, {
        cacheKeyFn: ({ cryptoCurrency, date, against }) => `${cryptoCurrency}-${date.getTime()}-${against}`,
    });
    clearCache() {
        this.getCryptoCurrenciesBySymbolLoader.clearAll();
        this.getCryptoExchangeRateLoader.clearAll();
    }
};
CryptoExchangeProvider = __decorate([
    Injectable({
        scope: Scope.Operation,
        global: true,
    }),
    __metadata("design:paramtypes", [TenantAwareDBClient,
        AdminContextProvider,
        CoinMarketCapProvider])
], CryptoExchangeProvider);
export { CryptoExchangeProvider };
//# sourceMappingURL=crypto-exchange.provider.js.map