import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
import type { ZabbixHost, ZabbixItem } from './zabbix-types';

@Injectable()
export class ZabbixClientService {
  constructor(private readonly logger: Logger) {}

  private getGrafanaAuth(orgIdHeader?: string) {
    const baseUrl = (process.env.GRAFANA_BASE_URL ?? '').trim().replace(/\/+$/, '');
    const user = process.env.GRAFANA_BASIC_USER;
    const pass = process.env.GRAFANA_BASIC_PASSWORD;
    const orgId = orgIdHeader || process.env.GRAFANA_ORG_ID || '1';
    const datasourceId = process.env.GRAFANA_ZABBIX_DATASOURCE_ID;

    if (!baseUrl) {
      throw new HttpException('GRAFANA_BASE_URL not configured', HttpStatus.SERVICE_UNAVAILABLE);
    }
    if (!user || !pass) {
      throw new HttpException('Grafana basic auth not configured', HttpStatus.SERVICE_UNAVAILABLE);
    }
    if (!datasourceId) {
      throw new HttpException(
        'GRAFANA_ZABBIX_DATASOURCE_ID not configured',
        HttpStatus.SERVICE_UNAVAILABLE
      );
    }

    let apiUrl: URL;
    try {
      apiUrl = new URL(`/api/datasources/${datasourceId}/resources/zabbix-api`, baseUrl);
    } catch {
      throw new HttpException('Invalid GRAFANA_BASE_URL', HttpStatus.SERVICE_UNAVAILABLE);
    }

    const auth = Buffer.from(`${user}:${pass}`).toString('base64');

    return { apiUrl: apiUrl.toString(), auth, orgId };
  }

  private async request<T>(
    method: string,
    params: Record<string, unknown>,
    orgIdHeader?: string
  ): Promise<T> {
    const { apiUrl, auth, orgId } = this.getGrafanaAuth(orgIdHeader);

    const response = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Basic ${auth}`,
        'X-Grafana-Org-Id': orgId,
      },
      body: JSON.stringify({
        jsonrpc: '2.0',
        method,
        params,
        id: 1,
      }),
    });

    const text = await response.text();
    let payload: unknown = text;
    try {
      payload = text ? JSON.parse(text) : null;
    } catch {
      payload = text;
    }

    if (!response.ok) {
      this.logger.error(
        {
          status: response.status,
          statusText: response.statusText,
          body: text.slice(0, 1000),
        },
        'Grafana proxy Zabbix request failed'
      );
      throw new HttpException('Upstream Grafana query failed', response.status as HttpStatus);
    }

    if (!payload || typeof payload !== 'object') {
      throw new HttpException('Invalid upstream response', HttpStatus.BAD_GATEWAY);
    }

    const payloadRecord = payload as Record<string, unknown>;
    if (payloadRecord.error) {
      this.logger.error({ error: payloadRecord.error }, 'Grafana proxy Zabbix error');
      throw new HttpException('Upstream Grafana query failed', HttpStatus.BAD_GATEWAY);
    }

    return payloadRecord.result as T;
  }

  async fetchHosts(
    params: Record<string, unknown>,
    orgIdHeader?: string
  ): Promise<ZabbixHost[]> {
    return this.request<ZabbixHost[]>('host.get', params, orgIdHeader);
  }

  async fetchItems(
    params: Record<string, unknown>,
    orgIdHeader?: string
  ): Promise<ZabbixItem[]> {
    return this.request<ZabbixItem[]>('item.get', params, orgIdHeader);
  }

  async fetchHistory(
    item: ZabbixItem,
    timeFrom: number,
    timeTill: number,
    orgIdHeader?: string
  ): Promise<Array<{ clock: string; value: string }>> {
    const valueType = item.value_type ?? '0';
    if (valueType !== '0' && valueType !== '3') {
      return [];
    }
    const historyType = valueType === '0' ? 0 : 3;
    return this.request<Array<{ clock: string; value: string }>>(
      'history.get',
      {
        output: ['clock', 'value'],
        history: historyType,
        itemids: [item.itemid],
        time_from: timeFrom,
        time_till: timeTill,
        sortfield: 'clock',
        sortorder: 'ASC',
      },
      orgIdHeader
    );
  }

  async requireHostByName(hostName: string, orgIdHeader?: string): Promise<ZabbixHost> {
    const hosts = await this.fetchHosts(
      {
        output: ['hostid', 'host', 'name'],
        filter: { host: [hostName] },
      },
      orgIdHeader
    );

    if (!hosts.length) {
      throw new HttpException('Host not found', HttpStatus.NOT_FOUND);
    }

    return hosts[0];
  }
}
