<script setup>

import { useI18n } from 'vue-i18n';
const i18n = useI18n();
const t = i18n.t;

import { ref, onMounted, onUnmounted, useTemplateRef, nextTick } from 'vue';
import { SingleSelect, MultiSelect } from '@cloudron/pankow';
import Section from './Section.vue';
import SystemModel from '../models/SystemModel.js';
import { prettyDecimalSize } from '@cloudron/pankow/utils';
import GraphItem from './GraphItem.vue';
import AppsModel from '../models/AppsModel.js';

const systemModel = SystemModel.create();
const appsModel = AppsModel.create();

const periods = [
  { hours: 0,                        label: t('app.graphs.period.live'), tickFormat: 'hh:mm A', tooltipFormat: 'hh:mm:ss A' },
  { hours: 1,     intervalSecs:  20, label: t('app.graphs.period.1h'),   tickFormat: 'hh:mm A', tooltipFormat: 'hh:mm:ss A' }, // 180 points
  { hours: 6,     intervalSecs:  60, label: t('app.graphs.period.6h'),   tickFormat: 'hh:mm A', tooltipFormat: 'hh:mm A' },
  { hours: 12,    intervalSecs: 240, label: t('app.graphs.period.12h'),  tickFormat: 'hh:mm A', tooltipFormat: 'hh:mm A' },
  { hours: 24,    intervalSecs: 480, label: t('app.graphs.period.24h'),  tickFormat: 'hh:mm A', tooltipFormat: 'hh:mm A' },
  { hours: 24*7,  intervalSecs: 960, label: t('app.graphs.period.7d'),   tickFormat: 'DD MMM',  tooltipFormat: 'DD MMM hh:mm A' },
  { hours: 24*30, intervalSecs: 960, label: t('app.graphs.period.30d'),  tickFormat: 'DD MMM',  tooltipFormat: 'DD MMM hh:mm A' }, // 2700 points
];

const busy = ref(true);
const period = ref(periods[0]);
const cpuGraphItem = useTemplateRef('cpuGraphItem');
const memoryGraphItem = useTemplateRef('memoryGraphItem');
const diskGraphItem = useTemplateRef('diskGraphItem');
const networkGraphItem = useTemplateRef('networkGraphItem');

const networkReadTotal = ref(0);
const networkWriteTotal = ref(0);

const blockReadTotal = ref(0);
const blockWriteTotal = ref(0);

const selectedContainers = ref([]);
const allContainers = ref([]);

// TODO: add redis containers here
const serviceContainers = [
  { label: 'Graphite', id: 'graphite' },
  { label: 'Mail', id: 'mail' },
  { label: 'MySQL', id: 'mysql' },
  { label: 'MongoDB', id: 'mongodb' },
  { label: 'PostgreSQL', id: 'postgresql' },
  { label: 'Turn', id: 'turn' },
];

let systemMemory = {};
let systemCpus = {};
let metricStream = null;

async function liveRefresh() {
  const options = {
    system: true,
    appIds: selectedContainers.value.map(c => c.id),
    serviceIds: []
  };
  const metricIds = options.appIds.concat('system'); // 'system' has to be in the end for the loop below
  metricStream = await systemModel.getMetricStream(options);
  metricStream.onerror = (error) => console.log('event stream error:', error);
  metricStream.onmessage = (message) => {
    const data = JSON.parse(message.data);

    for (const [idx, id] of metricIds.entries()) {
      const metric = data[id]; // metric can be undefined if the container stopped midway

      if (cpuGraphItem.value) cpuGraphItem.value.pushData(idx, metric?.cpu || 0);
      if (memoryGraphItem.value) memoryGraphItem.value.pushData(idx*2, metric?.memory || 0, metric?.swap || []); // apps have no swap
      if (diskGraphItem.value) diskGraphItem.value.pushData(idx*2, metric?.blockReadRate || 0, metric?.blockWriteRate || 0);
      if (networkGraphItem.value) networkGraphItem.value.pushData(idx*2, metric?.networkReadRate || 0, metric?.networkWriteRate || 0);

      if (id === 'system') { // metric is always present
        blockReadTotal.value = prettyDecimalSize(metric.blockReadTotal);
        blockWriteTotal.value = prettyDecimalSize(metric.blockWriteTotal);
        networkReadTotal.value = prettyDecimalSize(metric.networkReadTotal);
        networkWriteTotal.value = prettyDecimalSize(metric.networkWriteTotal);
      }
    }
  };
}

function generateConsistentColors(n, saturation = 90, lightness = 90) {
  const baseHue = 204;           // from #9ad0f5 → hsl(204,82%,78%)
  const colors = [];
  const step = 360 / n;

  for (let i = 0; i < n; i++) {
    const hue = Math.round((baseHue + step * i) % 360); // rotate hue, wrap at 360
    colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`);
  }

  return colors;
}

function createDatasets() {
    const colors = generateConsistentColors((selectedContainers.value.length+1)*2); // 1 for the 'system'

    const datasets = {
      cpu: [],
      memory: [],
      disk: [],
      network: [],
    };
    const appIds = selectedContainers.value.map(c => c.id);
    for (const [idx, id] of appIds.concat(['system']).entries()) { // live stream code depends on this concat order!
      const prefix = id === 'system' ? 'System' : selectedContainers.value[idx].label;

      datasets.cpu.push({ label: `${prefix} CPU`, color: colors[idx*2], stack: `${prefix}-cpu`, data: [] });
      datasets.memory.push({ label: `${prefix} Memory`, color: colors[idx*2], stack: `${prefix}-memswap`, data: [] });
      datasets.memory.push({ label: `${prefix} Swap`, color: colors[idx*2 + 1], stack: `${prefix}-memswap`, data: [] });

      datasets.disk.push({ label: `${prefix} Read`, color: colors[idx*2], stack: `${prefix}-read`, data: [] });
      datasets.disk.push({ label: `${prefix} Write`, color: colors[idx*2 + 1], stack: `${prefix}-write`, prefix, data: [] });

      datasets.network.push({ label: `${prefix} RX`, color: colors[idx*2], stack: `${prefix}-rx`, data: [] });
      datasets.network.push({ label: `${prefix} TX`, color: colors[idx*2 + 1], stack: `${prefix}-tx`, data: [] });
    }

    return datasets;
}

async function rebuild() {
  if (metricStream) {
    metricStream.close();
    metricStream = null;
  }

  const datasets = createDatasets();

  if (period.value.hours !== 0) {
    const options = {
      fromSecs: period.value.hours * 60 * 60,
      intervalSecs: period.value.intervalSecs,
      system: true,
      appIds: selectedContainers.value.map(c => c.id),
      serviceIds: []
    };
    const [error, metrics] = await systemModel.getMetrics(options);
    if (error) return console.error(error);

    const appIds = selectedContainers.value.map(c => c.id);
    for (const [idx, id] of appIds.concat(['system']).entries()) {
      if (!metrics[id]) continue;
      datasets.cpu[idx].data = metrics[id].cpu;
      datasets.memory[idx*2].data = metrics[id].memory;
      datasets.memory[idx*2 + 1].data = metrics[id].swap || []; // apps have no swap
      datasets.disk[idx*2].data = metrics[id].blockReadRate;
      datasets.disk[idx*2 + 1].data = metrics[id].blockWriteRate;
      datasets.network[idx*2].data = metrics[id].networkReadRate;
      datasets.network[idx*2 + 1].data = metrics[id].networkWriteRate;
    }

    networkReadTotal.value = prettyDecimalSize(metrics.system.networkReadTotal);
    networkWriteTotal.value = prettyDecimalSize(metrics.system.networkWriteTotal);
    blockReadTotal.value = prettyDecimalSize(metrics.system.blockReadTotal);
    blockWriteTotal.value = prettyDecimalSize(metrics.system.blockWriteTotal);
  }

  if (cpuGraphItem.value) cpuGraphItem.value.setDatasets(datasets.cpu);
  if (memoryGraphItem.value) memoryGraphItem.value.setDatasets(datasets.memory);
  if (diskGraphItem.value) diskGraphItem.value.setDatasets(datasets.disk);
  if (networkGraphItem.value) networkGraphItem.value.setDatasets(datasets.network);

  if (period.value.hours === 0) return await liveRefresh();
}

onMounted(async () => {
  let error, result;

  [error, result] = await systemModel.memory();
  if (error) return console.error(error);
  systemMemory = result;

  [error, result] = await systemModel.cpus();
  if (error) return console.error(error);
  systemCpus = result;

  [error, result] = await appsModel.list();
  if (error) return console.error(error);
  allContainers.value = result.map(a => { return { label: a.label || a.fqdn, id: a.id }; });
  if (result.length) allContainers.value.push({ separator: true });
  allContainers.value.push(...serviceContainers);
  selectedContainers.value = [];

  busy.value = false;
  await nextTick();

  await rebuild();
});

onUnmounted(async () => {
  if (metricStream) metricStream.close();
});

</script>

<template>
  <Section :title="$t('system.graphs.title')">
    <template #header-buttons>
      <MultiSelect @select="rebuild()" v-model="selectedContainers" :options="allContainers" option-label="label" :search-threshold="20" select-all-label="Select All"/>
      <SingleSelect @select="rebuild()" v-model="period" :options="periods" option-label="label"/>
    </template>

    <div class="graphs" v-if="!busy">
      <GraphItem ref="cpuGraphItem"
        :title="$t('system.cpuUsage.title')"
        :period="period"
        yscale="cpu"
        :cpu-count="systemCpus.length"
        >
      </GraphItem>

      <GraphItem ref="memoryGraphItem"
        :title="$t('system.systemMemory.title')"
        :period="period"
        yscale="memory"
        :memory="systemMemory.memory + systemMemory.swap"
        >
      </GraphItem>

      <GraphItem ref="diskGraphItem"
        title="Disk I/O"
        :footer="$t('app.graphs.diskIOTotal', { read: blockReadTotal, write: blockWriteTotal })"
        :period="period"
        yscale="disk"
        >
      </GraphItem>

      <GraphItem ref="networkGraphItem"
        title="Network I/O"
        :footer="$t('app.graphs.networkIOTotal', { inbound: networkReadTotal, outbound: networkWriteTotal })"
        :period="period"
        yscale="network"
        >
      </GraphItem>
    </div>
  </Section>
</template>
