const crypto = require('node:crypto');
const fs = require('node:fs');
const path = require('node:path');

const { logger } = require('generalFunctions');

function stableUid(input) {
  const digest = crypto.createHash('sha1').update(String(input)).digest('hex');
  return digest.slice(0, 12);
}

function slugify(input) {
  return String(input || '')
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/(^-|-$)/g, '')
    .slice(0, 60);
}

function defaultBucketForPosition(positionVsParent) {
  const pos = String(positionVsParent || '').toLowerCase();
  if (pos === 'upstream') return 'lvl1';
  if (pos === 'downstream') return 'lvl3';
  return 'lvl2';
}

function updateTemplatingVar(dashboard, varName, value) {
  const list = dashboard?.templating?.list;
  if (!Array.isArray(list)) return;

  const variable = list.find((v) => v && v.name === varName);
  if (!variable) return;

  variable.current = variable.current || {};
  variable.current.text = value;
  variable.current.value = value;

  if (Array.isArray(variable.options) && variable.options.length > 0) {
    variable.options[0] = variable.options[0] || {};
    variable.options[0].text = value;
    variable.options[0].value = value;
  }

  variable.query = value;
}

class DashboardApi {
  constructor(config = {}) {
    this.config = {
      general: {
        name: config?.general?.name || 'dashboardapi',
        logging: {
          enabled: Boolean(config?.general?.logging?.enabled),
          logLevel: config?.general?.logging?.logLevel || 'info',
        },
      },
      grafanaConnector: {
        protocol: config?.grafanaConnector?.protocol || 'http',
        host: config?.grafanaConnector?.host || 'localhost',
        port: Number(config?.grafanaConnector?.port || 3000),
        bearerToken: config?.grafanaConnector?.bearerToken || '',
      },
      bucketMap: config?.bucketMap || {},
    };

    this.logger = new logger(
      this.config.general.logging.enabled,
      this.config.general.logging.logLevel,
      this.config.general.name
    );
  }

  _templatesDir() {
    return path.join(__dirname, '..', 'config');
  }

  _templateFileForSoftwareType(softwareType) {
    const st = String(softwareType || '').trim();
    const candidates = [
      `${st}.json`,
      `${st.toLowerCase()}.json`,
      st === 'machineGroupControl' ? 'machineGroup.json' : null,
    ].filter(Boolean);

    for (const filename of candidates) {
      const fullPath = path.join(this._templatesDir(), filename);
      if (fs.existsSync(fullPath)) return fullPath;
    }

    throw new Error(`No dashboard template found for softwareType=${st}`);
  }

  loadTemplate(softwareType) {
    const templatePath = this._templateFileForSoftwareType(softwareType);
    const raw = fs.readFileSync(templatePath, 'utf8');
    return JSON.parse(raw);
  }

  grafanaUpsertUrl() {
    const { protocol, host, port } = this.config.grafanaConnector;
    return `${protocol}://${host}:${port}/api/dashboards/db`;
  }

  buildDashboard({ nodeConfig, positionVsParent }) {
    const softwareType =
      nodeConfig?.functionality?.softwareType ||
      nodeConfig?.functionality?.software_type ||
      'measurement';
    const nodeId = nodeConfig?.general?.id || nodeConfig?.general?.name || softwareType;
    const measurementName = `${softwareType}_${nodeId}`;
    const title = nodeConfig?.general?.name || String(nodeId);

    const dashboard = this.loadTemplate(softwareType);
    const uid = stableUid(`${softwareType}:${nodeId}`);

    dashboard.id = null;
    dashboard.uid = uid;
    dashboard.title = title;
    dashboard.tags = Array.from(
      new Set([...(dashboard.tags || []), 'EVOLV', softwareType, String(positionVsParent || '')].filter(Boolean))
    );

    const bucket =
      this.config.bucketMap[String(positionVsParent)] || defaultBucketForPosition(positionVsParent);

    updateTemplatingVar(dashboard, 'measurement', measurementName);
    updateTemplatingVar(dashboard, 'bucket', bucket);

    return { dashboard, uid, title, softwareType, nodeId, measurementName };
  }

  buildUpsertRequest({ dashboard, folderId = 0, overwrite = true }) {
    return { dashboard, folderId, overwrite };
  }

  extractChildren(nodeSource) {
    const out = [];
    const reg = nodeSource?.childRegistrationUtils?.registeredChildren;
    if (reg && typeof reg.values === 'function') {
      for (const entry of reg.values()) {
        const child = entry?.child;
        if (!child?.config) continue;
        out.push({ childSource: child, positionVsParent: entry?.position || child.positionVsParent });
      }
      return out;
    }
    return out;
  }

  generateDashboardsForGraph(rootSource, { includeChildren = true } = {}) {
    if (!rootSource?.config) {
      throw new Error('generateDashboardsForGraph requires a node source with `.config`');
    }

    const rootPosition = rootSource?.positionVsParent || rootSource?.config?.functionality?.positionVsParent;
    const rootDash = this.buildDashboard({ nodeConfig: rootSource.config, positionVsParent: rootPosition });

    const results = [rootDash];

    if (!includeChildren) return results;

    const children = this.extractChildren(rootSource);
    for (const { childSource, positionVsParent } of children) {
      const childDash = this.buildDashboard({ nodeConfig: childSource.config, positionVsParent });
      results.push(childDash);
    }

    // Add links from the root dashboard to children dashboards (when possible)
    if (children.length > 0) {
      rootDash.dashboard.links = Array.isArray(rootDash.dashboard.links) ? rootDash.dashboard.links : [];
      for (const { childSource } of children) {
        const childConfig = childSource.config;
        const childSoftwareType = childConfig?.functionality?.softwareType || 'measurement';
        const childNodeId = childConfig?.general?.id || childConfig?.general?.name || childSoftwareType;
        const childUid = stableUid(`${childSoftwareType}:${childNodeId}`);
        const childTitle = childConfig?.general?.name || String(childNodeId);

        rootDash.dashboard.links.push({
          type: 'link',
          title: childTitle,
          url: `/d/${childUid}/${slugify(childTitle)}`,
          tags: [],
          targetBlank: false,
          keepTime: true,
          keepVariables: true,
        });
      }
    }

    return results;
  }
}

module.exports = DashboardApi;
