"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const BaseService_1 = __importDefault(require("../common/BaseService"));
const Logger_1 = require("../common/Logger");
const config_1 = __importDefault(require("config"));
const Datastore_1 = __importDefault(require("../common/Datastore"));
const axios_1 = __importDefault(require("axios"));
class DealTrackingService extends BaseService_1.default {
    constructor() {
        super(Logger_1.Category.DealTrackingService);
        this.startDealTracking = this.startDealTracking.bind(this);
    }
    start() {
        if (!this.enabled) {
            this.logger.warn('Service is not enabled. Exit now...');
        }
        this.startDealTracking();
    }
    async startDealTracking() {
        await this.dealTracking();
        setTimeout(this.startDealTracking, 600000);
    }
    async dealTracking() {
        this.logger.info('Start update deal tracking');
        const clientStates = await Datastore_1.default.DealTrackingStateModel.find({ stateType: 'client', stateValue: 'track' });
        for (const clientState of clientStates) {
            const client = clientState.stateKey;
            const lastDeal = await Datastore_1.default.DealStateModel.find({ client }).sort({ dealId: -1 }).limit(1);
            try {
                await this.insertDealFromFilscan(client, lastDeal.length > 0 ? lastDeal[0].dealId : 0);
            }
            catch (error) {
                this.logger.error('Encountered an error when importing deals from filescan', error);
            }
            try {
                await this.updateDealFromLotus(client);
            }
            catch (error) {
                this.logger.error('Encountered an error when updating deals from lotus', error);
            }
        }
    }
    /**
     * Read from filscan api for PublishStorageDeal status
     *
     * @param client
     * @param lastDeal
     */
    async insertDealFromFilscan(client, lastDeal) {
        this.logger.debug('updating deals from filscan', { client, lastDeal });
        let page = 0;
        let response;
        do {
            const offset = page * 25;
            let breakOuter = false;
            let url = 'https://api.filscan.io:8700/rpc/v1';
            if (client.startsWith('t')) {
                url = 'https://calibration.filscan.io:8700/rpc/v1';
            }
            this.logger.debug(`Fetching from ${url}`);
            response = await axios_1.default.post(url, {
                id: 1,
                jsonrpc: '2.0',
                params: [client, offset, 25],
                method: 'filscan.GetMarketDeal'
            }, {
                headers: {
                    'content-type': 'application/json'
                }
            });
            if (Array.isArray(response.data['result']['deals'])) {
                const jsonResult = response.data['result'];
                this.logger.debug(`Received ${offset} out of ${jsonResult['total']} deal entries.`);
                for (const deal of jsonResult['deals']) {
                    if (deal['dealid'] <= lastDeal || jsonResult['deals'].length < 25) {
                        breakOuter = true;
                        this.logger.info('Reached to the last page or last checked deal.');
                        break;
                    }
                    const publishedDeal = await Datastore_1.default.DealStateModel.findOne({
                        dealId: deal['dealid']
                    });
                    if (publishedDeal) {
                        continue;
                    }
                    const proposedDeal = await Datastore_1.default.DealStateModel.findOne({
                        pieceCid: deal['piece_cid'],
                        provider: deal['provider'],
                        client: deal['client'],
                        state: 'proposed'
                    });
                    if (proposedDeal) {
                        await Datastore_1.default.DealStateModel.updateOne({
                            _id: proposedDeal._id
                        }, {
                            $set: {
                                dealId: deal['dealid'],
                                state: 'published'
                            }
                        });
                        this.logger.debug(`Deal ${deal['dealid']} was proposed through singularity. Filling in deal ID.`);
                    }
                    else {
                        await Datastore_1.default.DealStateModel.create({
                            client,
                            provider: deal['provider'],
                            dealId: deal['dealid'],
                            dealCid: deal['cid'],
                            pieceCid: deal['piece_cid'],
                            expiration: deal['end_epoch'],
                            duration: deal['end_epoch'] - deal['start_epoch'],
                            state: 'published'
                        });
                        this.logger.debug(`Deal ${deal['dealid']} inserted as published.`);
                    }
                }
                if (breakOuter) {
                    break;
                }
            }
            else {
                this.logger.debug('No result from filscan');
                break;
            }
            page += 1;
        } while (response.data['result']['deals'].length > 0);
    }
    /**
     * @param client
     * @param lastDeal
     */
    /* Temporarily disabled in favor of filscan for more information
    private async insertDealFromFilfox(client: string, lastDeal: number): Promise<void> {
      this.logger.debug('Inserting new deals from filfox', { client, lastDeal });
      let page = 0;
      let response;
      do {
        let breakOuter = false;
        // Exponential retry as filfox can throttle us
        response = await retry(
          async () => {
            const url = `https://filfox.info/api/v1/deal/list?address=${client}&pageSize=100&page=${page}`;
            this.logger.debug(`Fetching from ${url}`);
            let r;
            try {
              r = await axios.get(url);
            } catch (e) {
              this.logger.warn(e);
              throw e;
            }
            return r;
          }, {
          retries: 3,
          minTimeout: 60_000
        }
        );
        this.logger.debug(`Received ${response.data['deals'].length} deal entries.`);
        for (const deal of response.data['deals']) {
          if (deal['id'] <= lastDeal) {
            breakOuter = true;
            break;
          }
          await Datastore.DealStateModel.updateOne({
            dealId: deal['id']
          }, {
            $setOnInsert: {
              client,
              provider: deal['provider'],
              dealId: deal['id'],
              state: 'published'
            }
          }, {
            upsert: true
          });
        }
        if (breakOuter) {
          break;
        }
        page += 1;
      } while (response.data['deals'].length > 0);
    }
    */
    async updateDealFromLotus(client) {
        this.logger.debug('Start update deal state from lotus.', { client });
        const api = config_1.default.get('deal_tracking_service.lotus_api');
        const token = config_1.default.get('deal_tracking_service.lotus_token');
        for await (const dealState of Datastore_1.default.DealStateModel.find({
            client,
            state: 'published'
        })) {
            const headers = {};
            if (token !== '') {
                headers['Authorization'] = `Bearer ${token}`;
            }
            this.logger.debug(`Fetching from ${api}`, { dealId: dealState.dealId });
            const response = await axios_1.default.post(api, {
                id: 1,
                jsonrpc: '2.0',
                method: 'Filecoin.StateMarketStorageDeal',
                params: [dealState.dealId, null]
            }, { headers });
            if (response.data.error && response.data.error.code === 1) {
                await Datastore_1.default.DealStateModel.findByIdAndUpdate(dealState.id, {
                    state: 'slashed'
                });
                return;
            }
            const result = response.data.result;
            const expiration = result.Proposal.EndEpoch;
            const slashed = result.State.SlashEpoch > 0;
            const pieceCid = result.Proposal.PieceCID['/'];
            const dealActive = result.State.SectorStartEpoch > 0;
            if (slashed) {
                await Datastore_1.default.DealStateModel.findByIdAndUpdate(dealState.id, {
                    pieceCid, expiration, state: 'slashed'
                });
                this.logger.warn(`Deal ${dealState.dealId} is slashed.`);
            }
            else if (dealActive) {
                await Datastore_1.default.DealStateModel.findByIdAndUpdate(dealState.id, {
                    pieceCid, expiration, state: 'active'
                });
                this.logger.info(`Deal ${dealState.dealId} is active on chain.`);
            }
        }
    }
}
exports.default = DealTrackingService;
//# sourceMappingURL=DealTrackingService.js.map