import { Op } from "sequelize";
import { Block, Transaction, Day } from "./schema";
import { add, differenceInMinutes } from "date-fns";
import { getTodayUTC } from "@src/shared/utils/date";
import { round, uaktToAKT } from "@src/shared/utils/math";
import { isSyncing, syncingStatus } from "@src/akash/akashSync";
import { processingStatus } from "@src/akash/statsProcessor";
import { sleep } from "@src/shared/utils/delay";
import { isSyncingPrices } from "./priceHistoryProvider";

let isCalculatingRevenue = false;
let latestCalculateDate = null;

let cachedRevenue = null;
let cachedRevenueDate = null;

async function computeRevenueForBlocks(startBlockHeight: number, endBlockheight: number) {
  const spannedDays = await Day.findAll({
    attributes: ["aktPrice"],
    where: {
      lastBlockHeightYet: { [Op.gte]: startBlockHeight },
      firstBlockHeight: { [Op.lte]: endBlockheight }
    },
    include: [
      {
        model: Block,
        as: "lastBlockYet",
        attributes: ["totalUAktSpent"],
        required: true
      },
      {
        model: Block,
        as: "firstBlock",
        attributes: ["totalUAktSpent"],
        required: true
      }
    ]
  });

  let revenues = {
    uakt: 0,
    usd: 0
  };

  for (const day of spannedDays) {
    const lastValueOfDay = day.lastBlockYet.totalUAktSpent;
    const firstValueOfDay = day.firstBlock.totalUAktSpent;
    const uaktSpent = lastValueOfDay - firstValueOfDay;
    revenues.uakt += uaktSpent;
    revenues.usd += uaktSpent * day.aktPrice;
    // TODO Handle no price
  }

  return {
    ...revenues,
    akt: uaktToAKT(revenues.uakt, 6)
  };
}

export const getStatus = async () => {
  const latestBlockInDb = await Block.max("height");

  console.time("latestTx");
  const latestTx = await Transaction.findOne({
    attributes: ["hash"],
    order: [["height", "DESC"]]
  });
  console.timeEnd("latestTx");

  return {
    latestBlockInDb,
    latestTx: latestTx.hash,
    latestCalculateDate,
    isCalculatingRevenue,
    isSyncing,
    syncingStatus,
    processingStatus
  };
};

export const getWeb3IndexRevenue = async (debug: boolean) => {
  if (!debug && cachedRevenue && cachedRevenueDate && Math.abs(differenceInMinutes(cachedRevenueDate, new Date())) < 30) {
    return cachedRevenue;
  }

  while (isCalculatingRevenue || isSyncingPrices) {
    await sleep(5000);
  }

  const dailyNetworkRevenues = await getDailyRevenue();
  // console.log(dailyNetworkRevenues[0]);
  let days = dailyNetworkRevenues.map((r) => ({
    date: r.date.getTime() / 1000,
    revenue: round(r.usd, 2),
    revenueUAkt: r.uakt,
    aktPrice: r.aktPrice,
    dateStr: r.date
  }));

  const today = getTodayUTC();
  const oneDayAgo = add(today, { days: -1 });
  const twoDaysAgo = add(today, { days: -2 });
  const oneWeekAgo = add(today, { weeks: -1 });
  const twoWeeksAgo = add(today, { weeks: -2 });
  const thirtyDaysAgo = add(today, { days: -30 });
  const sixtyDaysAgo = add(today, { days: -60 });
  const ninetyDaysAgo = add(today, { days: -90 });
  let totalRevenue: number = 0,
    oneDayAgoRevenue: number = 0,
    twoDaysAgoRevenue: number = 0,
    oneWeekAgoRevenue: number = 0,
    twoWeeksAgoRevenue: number = 0,
    thirtyDaysAgoRevenue: number = 0,
    sixtyDaysAgoRevenue: number = 0,
    ninetyDaysAgoRevenue: number = 0;
  let totalRevenueUAkt: number = 0,
    oneDayAgoRevenueUAkt: number = 0,
    twoDaysAgoRevenueUAkt: number = 0,
    oneWeekAgoRevenueUAkt: number = 0,
    twoWeeksAgoRevenueUAkt: number = 0,
    thirtyDaysAgoRevenueUAkt: number = 0,
    sixtyDaysAgoRevenueUAkt: number = 0,
    ninetyDaysAgoRevenueUAkt: number = 0;

  days.forEach((b) => {
    const date = new Date(b.date * 1000);

    if (date <= ninetyDaysAgo) {
      ninetyDaysAgoRevenue += b.revenue;
      ninetyDaysAgoRevenueUAkt += b.revenueUAkt;
    }
    if (date <= sixtyDaysAgo) {
      sixtyDaysAgoRevenue += b.revenue;
      sixtyDaysAgoRevenueUAkt += b.revenueUAkt;
    }
    if (date <= thirtyDaysAgo) {
      thirtyDaysAgoRevenue += b.revenue;
      thirtyDaysAgoRevenueUAkt += b.revenueUAkt;
    }
    if (date <= twoWeeksAgo) {
      twoWeeksAgoRevenue += b.revenue;
      twoWeeksAgoRevenueUAkt += b.revenueUAkt;
    }
    if (date <= oneWeekAgo) {
      oneWeekAgoRevenue += b.revenue;
      oneWeekAgoRevenueUAkt += b.revenueUAkt;
    }
    if (date <= twoDaysAgo) {
      twoDaysAgoRevenue += b.revenue;
      twoDaysAgoRevenueUAkt += b.revenueUAkt;
    }
    if (date <= oneDayAgo) {
      oneDayAgoRevenue += b.revenue;
      oneDayAgoRevenueUAkt += b.revenueUAkt;
    }

    totalRevenue += b.revenue;
    totalRevenueUAkt += b.revenueUAkt;
  }, 0);

  if (!debug) {
    days = days.map(({ dateStr, revenueUAkt, aktPrice, ...others }) => others) as any;
  }

  let revenueStats = {
    now: round(totalRevenue),
    oneDayAgo: round(oneDayAgoRevenue),
    twoDaysAgo: round(twoDaysAgoRevenue),
    oneWeekAgo: round(oneWeekAgoRevenue),
    twoWeeksAgo: round(twoWeeksAgoRevenue),
    thirtyDaysAgo: round(thirtyDaysAgoRevenue),
    sixtyDaysAgo: round(sixtyDaysAgoRevenue),
    ninetyDaysAgo: round(ninetyDaysAgoRevenue)
  };

  if (debug) {
    revenueStats = {
      ...revenueStats,
      nowAkt: uaktToAKT(totalRevenueUAkt, 6),
      oneDayAgoAkt: uaktToAKT(oneDayAgoRevenueUAkt, 6),
      twoDaysAgoAkt: uaktToAKT(twoDaysAgoRevenueUAkt, 6),
      oneWeekAgoAkt: uaktToAKT(oneWeekAgoRevenueUAkt, 6),
      twoWeeksAgAkt: uaktToAKT(twoWeeksAgoRevenueUAkt, 6),
      thirtyDaysAgoAkt: uaktToAKT(thirtyDaysAgoRevenueUAkt, 6),
      sixtyDaysAgoAkt: uaktToAKT(sixtyDaysAgoRevenueUAkt, 6),
      ninetyDaysAgoAkt: uaktToAKT(ninetyDaysAgoRevenueUAkt, 6)
    } as any;
  }

  const responseObj = {
    revenue: revenueStats,
    days
  };

  cachedRevenue = responseObj;
  cachedRevenueDate = new Date();

  return responseObj;
};

async function getDailyRevenue() {
  const result = await Day.findAll({
    attributes: ["date", "aktPrice"],
    include: [
      {
        model: Block,
        as: "lastBlockYet",
        attributes: ["totalUAktSpent"],
        required: true
      }
    ],
    order: [["date", "ASC"]]
  });

  let stats = result.map((day) => ({
    date: day.date,
    totalUAktSpent: day.lastBlockYet.totalUAktSpent,
    aktPrice: day.aktPrice // TODO handle no price
  }));

  let relativeStats = stats.reduce((arr, dataPoint, index) => {
    arr[index] = {
      date: dataPoint.date,
      uakt: dataPoint.totalUAktSpent - (index > 0 ? stats[index - 1].totalUAktSpent : 0),
      aktPrice: dataPoint.aktPrice
    };

    return arr;
  }, []);

  return relativeStats.map((x) => ({
    date: x.date,
    uakt: x.uakt,
    akt: uaktToAKT(x.uakt, 6),
    usd: uaktToAKT(x.uakt, 6) * x.aktPrice,
    aktPrice: x.aktPrice
  }));
}