import { Agent } from 'undici';

const bytesToTb = (bytes) => bytes / 1e12;

const buildBaseUrl = (server) => {
  const rawHost = String(server.host || server.name || '').trim();
  if (!rawHost) {
    throw new Error('Missing host for Proxmox server.');
  }
  const hasScheme = rawHost.startsWith('http://') || rawHost.startsWith('https://');
  const scheme = server?.config?.scheme || 'https';
  const url = new URL(hasScheme ? rawHost : `${scheme}://${rawHost}`);
  if (server.port && !url.port) {
    url.port = String(server.port);
  }
  return url;
};

const getDispatcher = (server) => {
  if (server?.config?.allowInsecure === false) {
    return undefined;
  }
  return new Agent({ connect: { rejectUnauthorized: false } });
};

const getStatus = (percent) => {
  if (percent >= 85) {
    return { status: 'critical', alerts: 2 };
  }
  return { status: 'healthy', alerts: 0 };
};

const requestJson = async (url, dispatcher, headers) => {
  const response = await fetch(url, { headers, dispatcher });
  if (!response.ok) {
    throw new Error(`Proxmox API error (${response.status}).`);
  }
  const payload = await response.json();
  return payload?.data ?? payload;
};

const fetchClusterStorage = async (baseUrl, dispatcher, headers) => {
  const endpoint = new URL('/api2/json/cluster/resources', baseUrl);
  endpoint.searchParams.set('type', 'storage');
  const data = await requestJson(endpoint, dispatcher, headers);
  return Array.isArray(data) ? data : [];
};

const fetchNodes = async (baseUrl, dispatcher, headers) => {
  const endpoint = new URL('/api2/json/nodes', baseUrl);
  const data = await requestJson(endpoint, dispatcher, headers);
  return Array.isArray(data) ? data.map((node) => node.node).filter(Boolean) : [];
};

const fetchNodeStorageList = async (baseUrl, dispatcher, headers, nodes) => {
  const storageLists = await Promise.all(nodes.map(async (node) => {
    const endpoint = new URL(`/api2/json/nodes/${encodeURIComponent(node)}/storage`, baseUrl);
    const data = await requestJson(endpoint, dispatcher, headers);
    const list = Array.isArray(data) ? data : [];
    return list.map((item) => ({ ...item, node }));
  }));
  return storageLists.flat();
};

const fetchNodeStorage = async (baseUrl, dispatcher, headers, node) => {
  const endpoint = new URL(`/api2/json/nodes/${encodeURIComponent(node)}/storage`, baseUrl);
  const data = await requestJson(endpoint, dispatcher, headers);
  const list = Array.isArray(data) ? data : [];
  return list.map((item) => ({ ...item, node }));
};

const fetchStorageConfig = async (baseUrl, dispatcher, headers) => {
  const endpoint = new URL('/api2/json/storage', baseUrl);
  const data = await requestJson(endpoint, dispatcher, headers);
  return Array.isArray(data) ? data : [];
};

const isLocalStorage = (item, configMap, node) => {
  const key = item.storage || item.id || item.name;
  const config = key ? configMap.get(key) : null;
  const shared = config?.shared ?? item.shared;
  if (shared === 1 || shared === true) {
    return false;
  }
  if (node && typeof config?.nodes === 'string') {
    const allowedNodes = config.nodes.split(',').map((entry) => entry.trim()).filter(Boolean);
    if (allowedNodes.length > 0 && !allowedNodes.includes(node)) {
      return false;
    }
  }
  const type = config?.type ?? item.type;
  if (!type) {
    return true;
  }
  const localTypes = new Set(['dir', 'lvm', 'lvmthin', 'zfspool', 'btrfs', 'zfs']);
  return localTypes.has(type);
};

const pickNodeForStorage = (storage, nodes) => {
  if (!nodes.length) {
    return null;
  }
  const nodeList = typeof storage.nodes === 'string'
    ? storage.nodes.split(',').map((node) => node.trim()).filter(Boolean)
    : [];
  const match = nodeList.find((node) => nodes.includes(node));
  return match || nodes[0];
};

const fetchStorageDetails = async (baseUrl, dispatcher, headers, node, storage) => {
  const endpoint = new URL(`/api2/json/nodes/${encodeURIComponent(node)}/storage/${encodeURIComponent(storage)}`, baseUrl);
  const data = await requestJson(endpoint, dispatcher, headers);
  return data || null;
};

const normalizeResource = (item) => {
  const rawTotal = item.total ?? item.maxdisk ?? item.size;
  const rawUsed = item.used ?? item.disk;
  const rawAvail = item.avail;

  let total = Number(rawTotal ?? 0);
  let used = Number(rawUsed ?? 0);
  const avail = rawAvail !== undefined ? Number(rawAvail ?? 0) : null;

  if (!Number.isFinite(total)) {
    total = 0;
  }
  if (!Number.isFinite(used)) {
    used = 0;
  }

  if (total === 0 && avail !== null) {
    total = used > 0 ? used + avail : avail;
  }
  if (used === 0 && avail !== null && total > 0) {
    used = Math.max(0, total - avail);
  }

  return {
    storage: item.storage || item.id || item.name,
    node: item.node,
    total,
    used,
    avail: rawAvail
  };
};

const normalizeHost = (rawHost) => {
  if (!rawHost) {
    return '';
  }
  try {
    return new URL(rawHost).hostname;
  } catch {
    return rawHost.split('/')[0].split(':')[0];
  }
};

const resolveNodeForServer = (server, nodes) => {
  const explicit = server?.config?.node;
  if (explicit) {
    return explicit;
  }
  if (nodes.length === 1) {
    return nodes[0];
  }
  const host = normalizeHost(String(server.host || server.name || '').trim()).toLowerCase();
  const hostShort = host.split('.')[0];
  const match = nodes.find((node) => {
    const nodeLower = String(node).toLowerCase();
    const nodeShort = nodeLower.split('.')[0];
    return nodeLower === host || nodeShort === hostShort || nodeLower.startsWith(`${hostShort}.`);
  });
  if (match) {
    return match;
  }
  throw new Error('Cannot map server to Proxmox node. Set config.node.');
};

const dedupeResources = (resources) => {
  const map = new Map();
  resources.forEach((item) => {
    const key = item.storage || item.id || item.name;
    if (!key) {
      return;
    }
    const total = Number(item.total || 0);
    const current = map.get(key);
    if (!current || total > Number(current.total || 0)) {
      map.set(key, item);
    }
  });
  return Array.from(map.values());
};

export async function fetchProxmoxStorage(server) {
  const tokenId = server?.config?.apiTokenId;
  const tokenSecret = server?.config?.apiTokenSecret;
  if (!tokenId || !tokenSecret) {
    throw new Error('Proxmox credentials missing.');
  }

  const debugEnabled = ['1', 'true', 'yes'].includes(String(process.env.PROXMOX_DEBUG || '').toLowerCase());
  const debug = (...args) => {
    if (debugEnabled) {
      console.log(...args);
    }
  };
  const summarizeStorage = (list) => list.map((item) => ({
    storage: item.storage || item.id || item.name,
    node: item.node,
    type: item.type,
    total: item.total ?? item.maxdisk ?? item.size,
    used: item.used ?? item.disk,
    avail: item.avail,
    shared: item.shared,
    active: item.active,
    content: item.content
  }));

  const dispatcher = getDispatcher(server);
  const headers = {
    Accept: 'application/json',
    Authorization: `PVEAPIToken=${tokenId}=${tokenSecret}`
  };
  const baseUrl = buildBaseUrl(server);

  let resources = [];
  let storageConfigs = [];

  try {
    storageConfigs = await fetchStorageConfig(baseUrl, dispatcher, headers);
  } catch {
    storageConfigs = [];
  }
  const storageConfigMap = new Map(storageConfigs.map((storage) => [storage.storage, storage]));

  const nodes = await fetchNodes(baseUrl, dispatcher, headers);
  const node = resolveNodeForServer(server, nodes);

  resources = await fetchNodeStorage(baseUrl, dispatcher, headers, node);
  debug('Proxmox node storage raw', { node, storages: summarizeStorage(resources) });
  resources = resources.filter((item) => isLocalStorage(item, storageConfigMap, node));
  debug('Proxmox node storage filtered', { node, storages: summarizeStorage(resources) });

  if (resources.length === 0 && storageConfigs.length > 0) {
    const storages = storageConfigs.filter((storage) => {
      if (storage.disable || storage.disabled) {
        return false;
      }
      if (!isLocalStorage(storage, storageConfigMap, node)) {
        return false;
      }
      const allowedNode = pickNodeForStorage(storage, nodes);
      return !allowedNode || allowedNode === node;
    });
    const detailsList = await Promise.all(storages.map(async (storage) => {
      const details = await fetchStorageDetails(baseUrl, dispatcher, headers, node, storage.storage);
      if (!details) {
        return null;
      }
      debug('Proxmox storage details fallback', {
        node,
        storage: storage.storage,
        total: details.total,
        used: details.used,
        avail: details.avail
      });
      return {
        storage: storage.storage,
        total: details.total,
        used: details.used,
        avail: details.avail,
        node
      };
    }));
    resources = detailsList.filter(Boolean);
  }

  if (resources.length === 0) {
    throw new Error('No Proxmox storage data returned. Check storage permissions (Datastore.Audit) and storage visibility.');
  }

  const uniqueResources = dedupeResources(resources);
  let resourcesForTotals = uniqueResources;

  let totalBytes = 0;
  let usedBytes = 0;
  let poolName = uniqueResources[0]?.storage || 'pve-storage';
  let poolTotal = 0;
  const pools = [];

  const normalizedResources = await Promise.all(uniqueResources.map(async (item) => {
    const normalized = normalizeResource(item);
    if (!item.storage || normalized.total > 0 || normalized.used > 0) {
      return normalized;
    }
    const details = await fetchStorageDetails(baseUrl, dispatcher, headers, node, item.storage);
    if (!details || (Number(details.total || 0) === 0 && Number(details.used || 0) === 0)) {
      return normalized;
    }
    debug('Proxmox storage details', {
      node,
      storage: item.storage,
      total: details.total,
      used: details.used,
      avail: details.avail
    });
    return normalizeResource({
      storage: item.storage,
      node,
      total: details.total,
      used: details.used,
      avail: details.avail
    });
  }));

  normalizedResources.forEach((item) => {
    totalBytes += item.total;
    usedBytes += item.used;
    if (item.total > poolTotal) {
      poolTotal = item.total;
      poolName = item.storage || poolName;
    }
  });

  resourcesForTotals = normalizedResources;
  resourcesForTotals.forEach((item) => {
    pools.push({
      name: item.storage || 'storage',
      totalTb: bytesToTb(item.total),
      usedTb: bytesToTb(item.used),
      totalBytes: item.total,
      usedBytes: item.used
    });
  });

  const totalTb = bytesToTb(totalBytes);
  const usedTb = bytesToTb(usedBytes);
  const usedPercent = totalBytes ? Math.round((usedBytes / totalBytes) * 100) : 0;
  const statusMeta = getStatus(usedPercent);

  return {
    id: server.id,
    name: server.name || server.host,
    platform: 'Proxmox',
    cluster: server.config?.cluster || 'Vault Proxmox',
    location: server.config?.location || server.host,
    pool: poolName,
    totalTb,
    usedTb,
    totalBytes,
    usedBytes,
    pools,
    iops: 0,
    latencyMs: 0,
    status: statusMeta.status,
    alerts: statusMeta.alerts
  };
}
