import type { AssignedVM } from "../vm/base.ts";
import { postgres } from "./postgres.ts";
import os from "node:os";
import { getRedisCountDay, getRedisCountDayDistinct, redis } from "./redis.ts";
import config from "../config.ts";
import { apps } from "../ecosystem.config.js";

export async function getStats() {
  const now = Date.now();

  let currentUsers = 0;

  // Render each shard metrics as its own object
  const shardMetrics: Record<string, ShardMetric> = {};
  const shardKeys = new Set(
    apps.map((app) => `shardMetrics:${app.env?.SHARD ?? 0}`),
  );
  for (let key of shardKeys) {
    const resp2 = await redis?.get(key);
    if (resp2) {
      shardMetrics[key] = JSON.parse(resp2);
      currentUsers += shardMetrics[key].users;
    }
  }

  // Count these from postgres data
  let currentHttp = 0;
  let currentVBrowser = 0;
  let currentVBrowserLarge = 0;
  let currentScreenShare = 0;
  let currentFileShare = 0;
  const currentRoomSizes: Record<string, number> = {};

  const result = await postgres?.query<{
    roomId: string;
    creationTime: Date;
    lastUpdateTime: Date;
    vanity: string;
    isSubRoom: boolean;
    roomTitle: string;
    roomDescription: string;
    mediaPath: string;
    owner: string;
    password: string;
    video: string;
    videoTS: number;
    vBrowser: AssignedVM;
    creator: string;
    lock: string;
  }>(
    `SELECT "roomId", "creationTime", "lastUpdateTime", vanity, "isSubRoom", "roomTitle", "roomDescription", "mediaPath", owner, password,
    data->'video' as video, data->'videoTS' as "videoTS", data->'vBrowser' as "vBrowser", data->'creator' as creator, data->'lock' as lock
    FROM room
    WHERE "lastUpdateTime" > NOW() - INTERVAL '7 day'
    AND length(data->>'video') > 0
    ORDER BY "creationTime" DESC`,
  );
  const currentRoomData = await Promise.all(
    (result?.rows ?? []).map(async (dbRoom) => {
      const vBrowser = dbRoom.vBrowser;
      if (vBrowser) {
        currentVBrowser += 1;
      }
      if (vBrowser?.large) {
        currentVBrowserLarge += 1;
      }
      const rosterLength = Number(
        await redis?.get(`roomCounts:${dbRoom.roomId}`),
      );
      let roster = [];
      if (rosterLength) {
        currentRoomSizes[rosterLength] =
          (currentRoomSizes[rosterLength] ?? 0) + 1;
        const resp = await redis?.get(`roomRosters:${dbRoom.roomId}`);
        if (resp) {
          roster = JSON.parse(resp);
        }
      }
      const obj = {
        roomId: dbRoom.roomId,
        video: dbRoom.video || undefined,
        videoTS: dbRoom.videoTS || undefined,
        creationTime: dbRoom.creationTime || undefined,
        lastUpdateTime: dbRoom.lastUpdateTime || undefined,
        vanity: dbRoom.vanity || undefined,
        isSubRoom: dbRoom.isSubRoom || undefined,
        owner: dbRoom.owner || undefined,
        password: dbRoom.password || undefined,
        roomTitle: dbRoom.roomTitle || undefined,
        roomDescription: dbRoom.roomDescription || undefined,
        mediaPath: dbRoom.mediaPath || undefined,
        vBrowser,
        vBrowserElapsed: vBrowser?.assignTime && now - vBrowser?.assignTime,
        lock: dbRoom.lock || undefined,
        creator: dbRoom.creator || undefined,
        rosterLength,
        roster,
      };
      if (obj.video?.startsWith("http") && rosterLength) {
        currentHttp += 1;
      }
      if (obj.video?.startsWith("screenshare://") && rosterLength) {
        currentScreenShare += 1;
      }
      if (obj.video?.startsWith("fileshare://") && rosterLength) {
        currentFileShare += 1;
      }
      return obj;
    }),
  );
  // Singleton stats below (same for all shards)
  const currentVideoChat = 0;
  const cpuUsage = os.loadavg()[1] * 100;
  const redisUsage = Number(
    (await redis?.info())
      ?.split("\n")
      .find((line) => line.startsWith("used_memory:"))
      ?.split(":")[1]
      .trim(),
  );
  const postgresUsage = Number(
    (await postgres?.query(`SELECT pg_database_size('postgres');`))?.rows[0]
      .pg_database_size,
  );
  const numPermaRooms = Number(
    (await postgres?.query("SELECT count(1) from room WHERE owner IS NOT NULL"))
      ?.rows[0].count,
  );
  const numAllRooms = Number(
    (await postgres?.query("SELECT count(1) from room"))?.rows[0].count,
  );
  const numSubs = Number(
    (await postgres?.query("SELECT count(1) from subscriber"))?.rows[0].count,
  );
  const discordBotWatch = await getRedisCountDay("discordBotWatch");
  const createRoomErrors = await getRedisCountDay("createRoomError");
  const deleteAccounts = await getRedisCountDay("deleteAccount");
  const chatMessages = await getRedisCountDay("chatMessages");
  const addReactions = await getRedisCountDay("addReaction");
  const hetznerApiRemaining = Number(await redis?.get("hetznerApiRemaining"));
  const vBrowserStarts = await getRedisCountDay("vBrowserStarts");
  const vBrowserLaunches = await getRedisCountDay("vBrowserLaunches");
  const vBrowserFails = await getRedisCountDay("vBrowserFails");
  const vBrowserStagingFails = await getRedisCountDay("vBrowserStagingFails");
  const vBrowserReimages = await getRedisCountDay("vBrowserReimage");
  const vBrowserCleanups = await getRedisCountDay("vBrowserCleanup");
  const vBrowserStopTimeout = await getRedisCountDay(
    "vBrowserTerminateTimeout",
  );
  const vBrowserStopEmpty = await getRedisCountDay("vBrowserTerminateEmpty");
  const vBrowserStopManual = await getRedisCountDay("vBrowserTerminateManual");
  const vBrowserStartMS = await redis?.lrange("vBrowserStartMS", 0, -1);
  const vBrowserStageRetries = await redis?.lrange(
    "vBrowserStageRetries",
    0,
    -1,
  );
  const vBrowserStageFails = await redis?.lrange("vBrowserStageFails", 0, -1);
  const vBrowserSessionMS = await redis?.lrange("vBrowserSessionMS", 0, -1);
  // const vBrowserVMLifetime = await redis?.lrange('vBrowserVMLifetime', 0, -1);
  const proxyReqs = await getRedisCountDay("proxyReqs");
  const urlStarts = await getRedisCountDay("urlStarts");
  const streamStarts = await getRedisCountDay("streamStarts");
  const convertStarts = await getRedisCountDay("convertStarts");
  const playlistAdds = await getRedisCountDay("playlistAdds");
  const screenShareStarts = await getRedisCountDay("screenShareStarts");
  const fileShareStarts = await getRedisCountDay("fileShareStarts");
  const mediasoupStarts = await getRedisCountDay("mediasoupStarts");
  const videoChatStarts = await getRedisCountDay("videoChatStarts");
  const connectStarts = await getRedisCountDay("connectStarts");
  const connectStartsDistinct = await getRedisCountDayDistinct(
    "connectStartsDistinct",
  );
  const subUploads = await getRedisCountDay("subUploads");
  const subDownloadsOS = await getRedisCountDay("subDownloadsOS");
  const subSearchesOS = await getRedisCountDay("subSearchesOS");
  const youtubeSearch = await getRedisCountDay("youtubeSearch");
  const vBrowserClientIDsCard = await redis?.zcard("vBrowserClientIDs");
  const vBrowserUIDsCard = await redis?.zcard("vBrowserUIDs");
  const createRoomPreloads = await getRedisCountDay("createRoomPreload");

  const vBrowserClientIDs = altArrayToObject(
    await redis?.zrevrangebyscore(
      "vBrowserClientIDs",
      "+inf",
      "0",
      "WITHSCORES",
      "LIMIT",
      0,
      20,
    ),
  );
  const vBrowserUIDs = altArrayToObject(
    await redis?.zrevrangebyscore(
      "vBrowserUIDs",
      "+inf",
      "0",
      "WITHSCORES",
      "LIMIT",
      0,
      20,
    ),
  );
  const vBrowserClientIDMinutes = altArrayToObject(
    await redis?.zrevrangebyscore(
      "vBrowserClientIDMinutes",
      "+inf",
      "0",
      "WITHSCORES",
      "LIMIT",
      0,
      20,
    ),
  );
  const vBrowserUIDMinutes = altArrayToObject(
    await redis?.zrevrangebyscore(
      "vBrowserUIDMinutes",
      "+inf",
      "0",
      "WITHSCORES",
      "LIMIT",
      0,
      20,
    ),
  );

  // Fetch VM stats from vmworker
  const resp = await fetch(
    "http://localhost:" + config.VMWORKER_PORT + "/stats",
  );
  const vmManagerStats = await resp.json();

  return {
    ...shardMetrics,
    currentRoomSizes,
    counts: {
      currentUsers,
      currentVideoChat,
      currentVBrowser,
      currentVBrowserLarge,
      currentHttp,
      currentScreenShare,
      currentFileShare,
      cpuUsage,
      redisUsage,
      postgresUsage,
      numPermaRooms,
      numAllRooms,
      numSubs,
      discordBotWatch,
      createRoomErrors,
      createRoomPreloads,
      deleteAccounts,
      chatMessages,
      addReactions,
      proxyReqs,
      urlStarts,
      streamStarts,
      convertStarts,
      playlistAdds,
      screenShareStarts,
      fileShareStarts,
      mediasoupStarts,
      subUploads,
      subDownloadsOS,
      subSearchesOS,
      youtubeSearch,
      videoChatStarts,
      connectStarts,
      connectStartsDistinct,
      hetznerApiRemaining,
      vBrowserStarts,
      vBrowserLaunches,
      vBrowserFails,
      vBrowserStagingFails,
      vBrowserReimages,
      vBrowserCleanups,
      vBrowserStopManual,
      vBrowserStopEmpty,
      vBrowserStopTimeout,
      vBrowserClientIDsCard,
      vBrowserUIDsCard,
    },
    // Stats object from vmWorker (render as JSON)
    vmManagerStats,
    // Array of room data (render as JSON)
    currentRoomData,
    // Arrays of last values (render as one column table)
    vBrowserStartMS,
    vBrowserStageRetries,
    vBrowserStageFails,
    vBrowserSessionMS,
    // Maps of vbrowser users
    vBrowserClientIDs,
    vBrowserClientIDMinutes,
    vBrowserUIDs,
    vBrowserUIDMinutes,
  };
}

function altArrayToObject(arr: string[] | undefined) {
  const result: Record<string, number> = {};
  if (!arr) {
    return result;
  }
  for (let i = 0; i < arr.length; i += 2) {
    const k = arr[i];
    const v = arr[i + 1];
    result[k] = Number(v);
  }
  return result;
}
