import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { normalize, parseEpochSeconds, parseMetricNameFromDisk, parseNumeric } from './zabbix-utils';
import { ZabbixClientService } from './zabbix-client.service';

@Injectable()
export class ZabbixMetricsService {
  constructor(private readonly zabbixClient: ZabbixClientService) {}

  async getHostMetrics(
    orgIdHeader?: string,
    hostName?: string,
    metric?: string,
    from?: string,
    to?: string
  ): Promise<{ host: string; rows: Array<Record<string, string | number>> }> {
    if (!hostName || !hostName.trim()) {
      throw new HttpException('host query param is required', HttpStatus.BAD_REQUEST);
    }

    const host = await this.zabbixClient.requireHostByName(hostName, orgIdHeader);

    const metricMode = normalize(metric) || 'all';
    const timeTo = parseEpochSeconds(to);
    const timeFrom = parseEpochSeconds(from, timeTo - 3600);

    const items = await this.zabbixClient.fetchItems(
      {
        output: ['itemid', 'name', 'key_', 'value_type', 'units'],
        hostids: [host.hostid],
        sortfield: 'name',
      },
      orgIdHeader
    );

    const rows: Array<Record<string, string | number>> = [];

    if (metricMode === 'all' || metricMode === 'cpu') {
      const cpuIdle =
        items.find((item) => normalize(item.name).includes('cpu idle time')) ||
        items.find((item) => {
          const key = normalize(item.key_);
          return key.includes('system.cpu') && key.includes('idle');
        });

      if (cpuIdle) {
        const history = await this.zabbixClient.fetchHistory(cpuIdle, timeFrom, timeTo, orgIdHeader);
        for (const point of history) {
          const idle = parseNumeric(point.value);
          if (idle == null) continue;
          const usage = Math.max(0, Math.min(100, 100 - idle));
          rows.push({
            metric: 'CPU Usage %',
            ts: Number(point.clock),
            value: Number(usage.toFixed(2)),
          });
        }
      }
    }

    if (metricMode === 'all' || metricMode === 'memory') {
      const memUsedPct =
        items.find((item) => item.key_ === 'vm.memory.size[pused]') ||
        items.find((item) => normalize(item.key_).includes('pused'));

      if (memUsedPct) {
        const history = await this.zabbixClient.fetchHistory(memUsedPct, timeFrom, timeTo, orgIdHeader);
        for (const point of history) {
          const usedPct = parseNumeric(point.value);
          if (usedPct == null) continue;
          rows.push({
            metric: 'Memory Used %',
            ts: Number(point.clock),
            value: Number(usedPct.toFixed(2)),
          });
        }
      } else {
        const memFree = items.find((item) => item.key_ === 'vm.memory.size[free]');
        const memTotal = items.find((item) => item.key_ === 'vm.memory.size[total]');

        if (memFree && memTotal) {
          const [freeHistory, totalHistory] = await Promise.all([
            this.zabbixClient.fetchHistory(memFree, timeFrom, timeTo, orgIdHeader),
            this.zabbixClient.fetchHistory(memTotal, timeFrom, timeTo, orgIdHeader),
          ]);

          let currentTotal: number | null = null;
          let totalIndex = 0;
          for (const point of freeHistory) {
            while (totalIndex < totalHistory.length && totalHistory[totalIndex].clock <= point.clock) {
              const totalCandidate = parseNumeric(totalHistory[totalIndex].value);
              if (totalCandidate != null) {
                currentTotal = totalCandidate;
              }
              totalIndex += 1;
            }
            const free = parseNumeric(point.value);
            if (free == null || currentTotal == null || currentTotal === 0) continue;
            const usedPct = Math.max(0, Math.min(100, (1 - free / currentTotal) * 100));
            rows.push({
              metric: 'Memory Used %',
              ts: Number(point.clock),
              value: Number(usedPct.toFixed(2)),
            });
          }
        }
      }
    }

    if (metricMode === 'all' || metricMode === 'disk') {
      const diskFreePctItems = items.filter((item) => {
        const name = normalize(item.name);
        const key = normalize(item.key_);
        return (
          (name.includes('free disk space') && name.includes('percentage')) ||
          key.includes('vfs.fs.pfree')
        );
      });

      for (const item of diskFreePctItems) {
        const history = await this.zabbixClient.fetchHistory(item, timeFrom, timeTo, orgIdHeader);
        const metricName = parseMetricNameFromDisk(item.name);
        for (const point of history) {
          const freePct = parseNumeric(point.value);
          if (freePct == null) continue;
          const usedPct = Math.max(0, Math.min(100, 100 - freePct));
          rows.push({
            metric: metricName,
            ts: Number(point.clock),
            value: Number(usedPct.toFixed(2)),
          });
        }
      }
    }

    return { host: host.host || host.name || hostName, rows };
  }
}
