import express from 'express';
import cors from 'cors';
import { fetchProxmoxStorage } from './providers/proxmox.js';
import { fetchTrueNasStorage } from './providers/truenas.js';
import { fetchPbsStorage } from './providers/pbs.js';
import { addServer, applyServerLayout, deleteServer, readServers, setPoolsCollapsed, updateServer } from './store/servers.js';
import { detectServerType } from './detect.js';
import { readAlertsConfig, writeAlertsConfig } from './store/alerts.js';
import { evaluateAndSendAlerts, sendTestAlert } from './alerts/evaluate.js';

const app = express();
const port = process.env.PORT || 5001;

const STORAGE_CACHE_TTL_MS = 15000;
let storageCache = null;
let storageRefresh = null;

app.use(cors());
app.use(express.json());

const normalizeType = (type) => {
  if (!type) {
    return null;
  }
  const normalized = String(type).toLowerCase();
  if (normalized === 'truenas-scale' || normalized === 'truenas_scale') {
    return 'truenas';
  }
  if (normalized === 'pbs' || normalized === 'proxmox-backup' || normalized === 'proxmox-backup-server') {
    return 'pbs';
  }
  return normalized;
};

const resolveStorageForServer = async (server) => {
  if (!server?.type) {
    throw new Error('Server type missing.');
  }

  if (server.type === 'proxmox') {
    return fetchProxmoxStorage(server);
  }

  if (server.type === 'truenas') {
    return fetchTrueNasStorage(server);
  }

  if (server.type === 'pbs') {
    return fetchPbsStorage(server);
  }

  throw new Error(`Unsupported server type: ${server.type}`);
};

app.get('/api/health', (_req, res) => {
  res.json({ ok: true, service: 'vault', uptime: process.uptime(), timestamp: new Date().toISOString() });
});

app.get('/api/servers', async (_req, res) => {
  const servers = await readServers();
  res.json({ servers });
});

app.post('/api/servers', async (req, res) => {
  const { name, host, port } = req.body ?? {};
  const trimmedHost = String(host || '').trim();

  if (!trimmedHost) {
    res.status(400).json({ error: 'host is required.' });
    return;
  }

  let numericPort = null;
  if (port !== undefined && port !== null && port !== '') {
    numericPort = Number(port);
    if (!Number.isInteger(numericPort) || numericPort <= 0) {
      res.status(400).json({ error: 'port must be a positive number.' });
      return;
    }
  }

  const detectedType = detectServerType({ host: trimmedHost, port: numericPort });
  const resolvedPort = numericPort ?? (detectedType === 'proxmox' ? 8006 : detectedType === 'pbs' ? 8007 : 443);
  const resolvedName = String(name || '').trim() || trimmedHost;

  const server = await addServer({
    name: resolvedName,
    host: trimmedHost,
    port: resolvedPort,
    type: detectedType,
    state: 'discovered'
  });

  res.status(201).json({ id: server.id, detectedType, server });
});

app.post('/api/servers/:id/config', async (req, res) => {
  const { id } = req.params;
  const { type, config } = req.body ?? {};
  const normalizedType = normalizeType(type);
  const scheme = config?.scheme ? String(config.scheme).toLowerCase() : undefined;
  const allowInsecure = config?.allowInsecure ?? true;

  if (!normalizedType || !['proxmox', 'truenas', 'pbs'].includes(normalizedType)) {
    res.status(400).json({ error: 'type must be proxmox, truenas, or pbs.' });
    return;
  }

  if (scheme && !['http', 'https'].includes(scheme)) {
    res.status(400).json({ error: 'scheme must be http or https.' });
    return;
  }

  if (normalizedType === 'proxmox' || normalizedType === 'pbs') {
    if (!config?.apiTokenId || !config?.apiTokenSecret) {
      res.status(400).json({ error: 'Proxmox requires apiTokenId and apiTokenSecret.' });
      return;
    }
  }

  if (normalizedType === 'truenas') {
    if (!config?.apiKey) {
      res.status(400).json({ error: 'TrueNAS SCALE requires apiKey.' });
      return;
    }
  }

  const updated = await updateServer(id, {
    type: normalizedType,
    config: {
      ...config,
      scheme,
      allowInsecure
    },
    state: 'configured',
    configuredAt: new Date().toISOString()
  });

  if (!updated) {
    res.status(404).json({ error: 'Server not found.' });
    return;
  }

  res.json({ server: updated });
});

app.patch('/api/servers/:id', async (req, res) => {
  const { id } = req.params;
  const { name, alerts } = req.body ?? {};
  let nextName = undefined;
  if (name !== undefined) {
    const trimmed = String(name).trim();
    nextName = trimmed.length ? trimmed : null;
  }

  if (name !== undefined && nextName === undefined) {
    res.status(400).json({ error: 'name must be a string.' });
    return;
  }

  let nextAlerts = undefined;
  if (alerts !== undefined) {
    if (!alerts || typeof alerts !== 'object' || Array.isArray(alerts)) {
      res.status(400).json({ error: 'alerts must be an object.' });
      return;
    }
    nextAlerts = alerts;
  }

  const updated = await updateServer(id, {
    ...(name !== undefined ? { name: nextName } : {}),
    ...(alerts !== undefined ? { alerts: nextAlerts } : {})
  });

  if (!updated) {
    res.status(404).json({ error: 'Server not found.' });
    return;
  }

  res.json({ server: updated });
});

app.delete('/api/servers/:id', async (req, res) => {
  const { id } = req.params;
  const removed = await deleteServer(id);
  if (!removed) {
    res.status(404).json({ error: 'Server not found.' });
    return;
  }
  res.json({ ok: true, id });
});

app.post('/api/servers/order', async (req, res) => {
  const { order, sizes } = req.body ?? {};
  const hasOrder = Array.isArray(order) && order.length > 0;
  const hasSizes = Array.isArray(sizes) && sizes.length > 0;

  if (!hasOrder && !hasSizes) {
    res.status(400).json({ error: 'order or sizes must be provided.' });
    return;
  }

  try {
    const updated = await applyServerLayout({ order, sizes });
    res.json({ servers: updated });
  } catch (error) {
    res.status(400).json({ error: error.message || 'Failed to update server layout.' });
  }
});

app.post('/api/servers/:id/pools-collapsed', async (req, res) => {
  const { id } = req.params;
  const { collapsed } = req.body ?? {};
  const updated = await setPoolsCollapsed(id, collapsed);
  if (!updated) {
    res.status(404).json({ error: 'Server not found.' });
    return;
  }
  res.json({ server: updated });
});

app.get('/api/alerts', async (_req, res) => {
  const config = await readAlertsConfig();
  res.json({ alerts: config });
});

app.post('/api/alerts', async (req, res) => {
  const body = req.body ?? {};
  const patch = {};

  if (Object.prototype.hasOwnProperty.call(body, 'enabled')) {
    patch.enabled = Boolean(body.enabled);
  }

  if (body.pushover && typeof body.pushover === 'object') {
    patch.pushover = {};
    if (Object.prototype.hasOwnProperty.call(body.pushover, 'userKey')) {
      patch.pushover.userKey = String(body.pushover.userKey || '').trim();
    }
    if (Object.prototype.hasOwnProperty.call(body.pushover, 'apiToken')) {
      patch.pushover.apiToken = String(body.pushover.apiToken || '').trim();
    }
  }

  if (body.defaults && typeof body.defaults === 'object') {
    patch.defaults = {};
    const defaults = body.defaults;
    if (Object.prototype.hasOwnProperty.call(defaults, 'enabled')) {
      patch.defaults.enabled = Boolean(defaults.enabled);
    }
    if (Object.prototype.hasOwnProperty.call(defaults, 'warnPercent')) {
      patch.defaults.warnPercent = Number(defaults.warnPercent);
    }
    if (Object.prototype.hasOwnProperty.call(defaults, 'critPercent')) {
      patch.defaults.critPercent = Number(defaults.critPercent);
    }
    if (Object.prototype.hasOwnProperty.call(defaults, 'cooldownMinutes')) {
      patch.defaults.cooldownMinutes = Number(defaults.cooldownMinutes);
    }
    if (Object.prototype.hasOwnProperty.call(defaults, 'once')) {
      patch.defaults.once = Boolean(defaults.once);
    }
    if (Object.prototype.hasOwnProperty.call(defaults, 'includePools')) {
      patch.defaults.includePools = Boolean(defaults.includePools);
    }
  }

  const updated = await writeAlertsConfig(patch);
  res.json({ alerts: updated });
});

app.post('/api/alerts/test', async (_req, res) => {
  const config = await readAlertsConfig();
  if (!config.enabled) {
    res.status(400).json({ error: 'Alerts are disabled.' });
    return;
  }
  try {
    await sendTestAlert(config);
    res.json({ ok: true });
  } catch (error) {
    res.status(400).json({ error: error.message || 'Failed to send test alert.' });
  }
});

const buildStoragePayload = async () => {
  const stored = await readServers();
  const configured = stored.filter((server) => server.state === 'configured');

  if (configured.length === 0) {
    return { source: 'empty', generatedAt: new Date().toISOString(), servers: [] };
  }

  const results = await Promise.allSettled(configured.map((server) => resolveStorageForServer(server)));
  const servers = [];
  const errors = [];

  results.forEach((result, index) => {
    const server = configured[index];
    if (result.status === 'fulfilled' && result.value) {
      const payload = Array.isArray(result.value) ? result.value[0] : result.value;
      if (payload) {
        servers.push({
          ...payload,
          id: server.id,
          position: Number.isFinite(server.position) ? server.position : index,
          span: Number.isFinite(server.span) ? server.span : 1,
          poolsCollapsed: Boolean(server.poolsCollapsed),
          alerts: server.alerts || null,
          name: payload.name || server.name,
          host: server.host
        });
      }
    } else {
      let message = result.status === 'rejected' ? result.reason?.message : 'Failed to load storage.';
      const cause = result.status === 'rejected' ? result.reason?.cause : null;
      if (cause?.code) {
        message = `${cause.code}: ${cause.message || message}`;
      }
      errors.push({ id: server.id, name: server.name, message });
    }
  });

  return {
    source: errors.length ? 'error' : 'live',
    generatedAt: new Date().toISOString(),
    servers,
    errors
  };
};

const refreshStorageCache = async () => {
  try {
    const payload = await buildStoragePayload();
    storageCache = { timestamp: Date.now(), payload };
    try {
      const [storedServers, alertsConfig] = await Promise.all([
        readServers(),
        readAlertsConfig()
      ]);
      queueMicrotask(() => {
        evaluateAndSendAlerts(payload, storedServers, alertsConfig).catch((error) => {
          // eslint-disable-next-line no-console
          console.error('Alert evaluation failed:', error.message || error);
        });
      });
    } catch (error) {
      // eslint-disable-next-line no-console
      console.error('Alert setup failed:', error.message || error);
    }
    return payload;
  } finally {
    storageRefresh = null;
  }
};

app.get('/api/storage', async (_req, res) => {
  try {
    const now = Date.now();
    if (storageCache) {
      const age = now - storageCache.timestamp;
      if (age < STORAGE_CACHE_TTL_MS) {
        res.json({ ...storageCache.payload, cached: true });
        return;
      }
      if (!storageRefresh) {
        storageRefresh = refreshStorageCache();
      }
      res.json({ ...storageCache.payload, cached: true, stale: true });
      return;
    }

    if (!storageRefresh) {
      storageRefresh = refreshStorageCache();
    }
    const payload = await storageRefresh;
    res.json({ ...payload, cached: false });
  } catch (error) {
    res.status(200).json({
      source: 'error',
      generatedAt: new Date().toISOString(),
      servers: [],
      errors: [{ message: 'Failed to load live storage data.' }]
    });
  }
});

app.listen(port, () => {
  console.log(`Vault backend listening on port ${port}`);
});
