// Copyright 2021 Cartesi Pte. Ltd.

// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
// this file except in compliance with the License. You may obtain a copy of the
// License at http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.

import log from "loglevel";
import { Provider } from "@ethersproject/providers";
import { formatEther } from "@ethersproject/units";

import * as monitoring from "./monitoring";
import { sleep } from "./util";
import { connect } from "./connection";
import { BlockProducer } from "./block";
import { hire, retire, isPool } from "./worker";
import { POLLING_INTERVAL, BALANCE_THRESHOLD } from "./config";
import { PoolProtocolImpl, ProtocolClient, ProtocolImpl } from "./pos";
import {
    createGasPriceProvider,
    GasPriceProviderType,
} from "./gas-price/gas-price-provider";

const checkBalance = async (provider: Provider, address: string) => {
    // query node wallet ETH balance
    const balance = await provider.getBalance(address);
    const label = formatEther(balance);

    // monitor ETH balance
    monitoring.balance.set(Number.parseFloat(label));

    // print warning if below threshold
    if (balance.lt(BALANCE_THRESHOLD)) {
        log.warn(`low balance: ${label} ETH, transfer more funds`);
    }
};

export const app = async (
    url: string,
    accountIndex: number,
    wallet: string | undefined,
    create: boolean,
    monitor: boolean,
    hostname: string,
    port: number,
    gasPriceProviderType: GasPriceProviderType,
    gasStationAPIKey: string | undefined
) => {
    // connect to node
    const { address, network, pos, provider, workerManager } = await connect(
        url,
        accountIndex,
        wallet,
        create
    );

    // create gas price provider using specified type
    const gasPriceProvider = await createGasPriceProvider(
        provider,
        gasPriceProviderType,
        gasStationAPIKey
    );

    // set labels of metrics
    monitoring.register.setDefaultLabels({
        network: network.name,
        worker: address,
    });

    if (monitor) {
        // monitoring service
        monitoring.start(hostname, port);
    }

    // worker hiring
    const user = await hire(workerManager, gasPriceProvider, address);

    // set labels of metrics
    monitoring.register.setDefaultLabels({
        network: network.name,
        worker: address,
        user,
    });

    if (!user) {
        log.error(`failed to hire`);
        return;
    }

    // check if represented user is a pool
    const pool = await isPool(workerManager, user);
    if (pool) {
        log.info(`worker hired by pool ${user}`);
    } else {
        log.info(`worker hired by user ${user}`);
    }

    // create protocol client (smart contract communication)
    const client: ProtocolClient = pool
        ? new PoolProtocolImpl(pos, user, workerManager, gasPriceProvider)
        : new ProtocolImpl(pos, workerManager, gasPriceProvider);

    // create block producer
    const blockProducer = new BlockProducer(pos.address, client);

    // loop forever
    while (true) {
        try {
            // check if node retired
            if (await retire(workerManager, gasPriceProvider, address, user)) {
                break;
            }

            // check node balance
            await checkBalance(pos.provider, address);

            // try to produce a block
            await blockProducer.produceBlock(user);

            // maintenance calls
            await blockProducer.rebalance();
        } catch (e) {
            // print the error, but continue polling
            log.error(e);

            // increase error counter
            monitoring.errors.inc();
        }

        // go to sleep
        await sleep(POLLING_INTERVAL);
    }
};