/**
 * TeamViewer Web API: Connection Reports (/api/v1/reports/connections)
 */

const CONNECTIONS_URL =
  'https://webapi.teamviewer.com/api/v1/reports/connections';

const FETCH_HEADERS_BASE = {
  Accept: 'application/json',
  'User-Agent': 'SDS-CRM/1.0 (Node.js; TeamViewer reports/connections)',
};

/**
 * TeamViewer akzeptiert ISO-8601-UTC ohne Millisekunden, z. B. 2019-01-31T19:20:30Z.
 * JavaScripts toISOString() liefert .sssZ — das führt zu HTTP 400 „from_date is not valid“.
 */
export function formatTeamViewerDateParam(date) {
  return new Date(date).toISOString().replace(/\.\d{3}Z$/, 'Z');
}

/** Dauer aus TeamViewer-Feldern `start_date` / `end_date` (ISO-Strings, ggf. leer). */
export function computeRemoteDurationSeconds(startIso, endIso) {
  const ts = startIso != null ? String(startIso).trim() : '';
  const te = endIso != null ? String(endIso).trim() : '';
  if (!ts || !te) return null;
  const s = new Date(ts);
  const e = new Date(te);
  if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime()) || e < s) {
    return null;
  }
  return Math.max(0, Math.round((e - s) / 1000));
}

/**
 * Gruppiert nach Benutzer (userid, sonst username), pro Benutzer Geräte mit
 * jüngster Session je Gerät; Anzeigename Gerät = devicename oder sonst deviceid.
 */
/** TeamViewer liefert Feldnamen je nach Version leicht unterschiedlich (userid / user_id / …). */
/** TeamViewer Connection Report: optionales Freitextfeld */
function pickNotes(rec) {
  const raw =
    rec.notes ??
    rec.Notes ??
    rec.connection_notes ??
    rec.session_notes ??
    '';
  const s = String(raw ?? '').trim();
  return s || null;
}

function pickUserIdentity(rec) {
  const uid = String(
    rec.userid ?? rec.user_id ?? rec.userId ?? rec.UserID ?? '',
  ).trim();
  const un = String(
    rec.username ??
      rec.user_name ??
      rec.userName ??
      rec.UserName ??
      '',
  ).trim();
  return { uid, un };
}

function buildSessionsByUser(allRecords) {
  const byUser = new Map();
  for (const rec of allRecords) {
    const { uid, un } = pickUserIdentity(rec);
    const userKey = uid || un || '_unbekannt';
    if (!byUser.has(userKey)) {
      byUser.set(userKey, {
        userid: uid || null,
        username: un || userKey,
        records: [],
      });
    }
    byUser.get(userKey).records.push(rec);
  }

  const users = [];
  for (const [userKey, { userid, username, records }] of byUser) {
    const byDev = new Map();
    for (const rec of records) {
      const did = String(
        rec.deviceid ?? rec.device_id ?? rec.deviceId ?? '',
      ).trim();
      const dn = String(
        rec.devicename ?? rec.device_name ?? rec.deviceName ?? '',
      ).trim();
      const dkey = did || dn;
      if (!dkey) continue;
      if (!byDev.has(dkey)) byDev.set(dkey, []);
      byDev.get(dkey).push(rec);
    }
    const devices = [];
    for (const [, recs] of byDev) {
      recs.sort((a, b) => {
        const eb =
          (b.end_date ?? b.endDate ?? b.EndDate)
            ? new Date(b.end_date ?? b.endDate ?? b.EndDate).getTime()
            : 0;
        const ea =
          (a.end_date ?? a.endDate ?? a.EndDate)
            ? new Date(a.end_date ?? a.endDate ?? a.EndDate).getTime()
            : 0;
        return eb - ea;
      });
      const rec = recs[0];
      const did = String(
        rec.deviceid ?? rec.device_id ?? rec.deviceId ?? '',
      ).trim();
      const dn = String(
        rec.devicename ?? rec.device_name ?? rec.deviceName ?? '',
      ).trim();
      const label = dn || did;
      const idForCrm = did || dn;
      const startRaw = rec.start_date ?? rec.startDate ?? rec.StartDate;
      const endRaw = rec.end_date ?? rec.endDate ?? rec.EndDate;
      const startDate = startRaw ? String(startRaw).trim() : '';
      const endDate = endRaw ? String(endRaw).trim() : '';
      devices.push({
        deviceid: idForCrm,
        label,
        startDate: startDate || null,
        endDate: endDate || null,
        durationSeconds: computeRemoteDurationSeconds(startDate, endDate),
        notes: pickNotes(rec),
      });
    }
    devices.sort((a, b) =>
      a.label.localeCompare(b.label, 'de', { sensitivity: 'base' }),
    );
    users.push({
      userKey,
      userid,
      username,
      devices,
    });
  }
  users.sort((a, b) =>
    a.username.localeCompare(b.username, 'de', { sensitivity: 'base' }),
  );
  return users;
}

function upstreamError(res, httpStatus, raw) {
  const text = String(raw ?? '').trim();
  if (text.startsWith('<')) {
    return res.status(502).json({
      message:
        'TeamViewer-Web-API lieferte HTML statt JSON. Typisch: Script-Token ungültig/abgelaufen, falsche Berechtigungen (Verbindungsberichte), oder Dienst kurzzeitig nicht erreichbar.',
      hint: 'TeamViewer Management Console → Apps → Script-Token: Zugriff auf Reporting / Connection Reports prüfen.',
      devices: [],
      users: [],
    });
  }
  let detail = text.slice(0, 600);
  try {
    const j = JSON.parse(text);
    const msg =
      (typeof j.error_description === 'string' && j.error_description) ||
      (typeof j.error === 'string' && j.error) ||
      (typeof j.message === 'string' && j.message);
    if (msg) detail = String(msg);
  } catch {
    /* Rohtext */
  }
  return res.status(502).json({
    message: `TeamViewer-API meldet HTTP ${httpStatus}.`,
    detail,
    devices: [],
    users: [],
  });
}

function bodyNotJson(res, raw) {
  const text = String(raw ?? '').trim();
  if (text.startsWith('<')) {
    return upstreamError(res, 200, raw);
  }
  return res.status(502).json({
    message: 'TeamViewer: Antwort ist kein gültiges JSON.',
    detail: text.slice(0, 400),
    devices: [],
    users: [],
  });
}

/** TeamViewer-Antworten: `records` (Reports) oder andere Schlüssel / verschachtelt. */
function extractReportRecords(data) {
  if (!data || typeof data !== 'object') return [];
  if (Array.isArray(data)) return data;
  const candidates = [
    data.records,
    data.Records,
    data.connections,
    data.Connections,
    data.items,
    data.Items,
    data.values,
    data.data?.records,
    data.data?.connections,
    data.report?.records,
  ];
  let fallbackEmpty = null;
  for (const c of candidates) {
    if (Array.isArray(c)) {
      if (c.length > 0) return c;
      if (fallbackEmpty === null) fallbackEmpty = c;
    }
  }
  return fallbackEmpty ?? [];
}

function hasNextPage(data) {
  const next = data?.next_offset ?? data?.nextOffset ?? data?.NextOffset;
  if (next == null) return false;
  const s = String(next).trim();
  return s.length > 0;
}

/** @param {import('express').Router} api @param {() => object} loadIntegrations */
export function registerTeamViewerRoutes(api, loadIntegrations) {
  api.get('/integrations/teamviewer/connections', async (_req, res) => {
    const integ = loadIntegrations();
    const token = String(
      integ.teamviewer?.bearerToken || integ.teamviewer?.apiToken || '',
    ).trim();
    if (!token) {
      return res.status(400).json({
        message:
          'TeamViewer Bearer-Token in den Optionen hinterlegen (Abschnitt TeamViewer).',
        devices: [],
        users: [],
      });
    }
    const now = new Date();
    const from = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
    const fromStr = formatTeamViewerDateParam(from);
    const toStr = formatTeamViewerDateParam(now);

    try {
      const allRecords = [];
      let offset;
      let pagesFetched = 0;
      for (let page = 0; page < 25; page++) {
        pagesFetched += 1;
        const u = new URL(CONNECTIONS_URL);
        u.searchParams.set('from_date', fromStr);
        u.searchParams.set('to_date', toStr);
        if (offset != null && offset !== '') {
          u.searchParams.set('offset', String(offset));
        }
        const r = await fetch(u, {
          headers: {
            ...FETCH_HEADERS_BASE,
            Authorization: `Bearer ${token}`,
          },
        });
        const raw = await r.text();
        if (!r.ok) {
          console.error(
            '[TeamViewer API] upstream HTTP',
            r.status,
            String(raw ?? '').slice(0, 500),
          );
          return upstreamError(res, r.status, raw);
        }
        let data;
        try {
          data = JSON.parse(raw);
        } catch {
          return bodyNotJson(res, raw);
        }
        const recs = extractReportRecords(data);
        for (const rec of recs) {
          allRecords.push(rec);
        }
        /*
         * Wichtig: nicht abbrechen, nur weil eine Seite 0 Zeilen hat — TeamViewer liefert
         * manchmal leere erste Seiten, setzt aber weiter next_offset (vgl. records_remaining).
         * Früher: if (!next || recs.length === 0) break → alle Daten verloren.
         */
        if (!hasNextPage(data)) break;
        offset = data.next_offset ?? data.nextOffset ?? data.NextOffset;
      }

      const users = buildSessionsByUser(allRecords);
      res.json({
        users,
        devices: [],
        meta: {
          recordCount: allRecords.length,
          pagesFetched,
          source: 'reports/connections',
        },
      });
    } catch (e) {
      console.error('[TeamViewer API] interner Fehler', e);
      res.status(500).json({
        message: 'TeamViewer-Verbindungen konnten nicht geladen werden.',
        devices: [],
        users: [],
      });
    }
  });
}
