/**
 * AppGate SDP Controller REST API client.
 *
 * Implements the subset of v22.5 endpoints Pulse needs:
 *   - POST /admin/login                       (token acquisition)
 *   - GET  /admin/stats/active-sessions/dn    (current sessions)
 *   - GET  /admin/stats/user-logins           (24h login histogram)
 *   - GET  /admin/on-boarded-devices          (device inventory)
 *   - GET  /admin/appliances                  (controllers / gateways)
 *   - GET  /admin/license                     (capacity + expiry)
 *   - GET  /admin/license/users               (per-user license usage)
 *
 * Token caching: AppGate `LoginResponse` returns an explicit `expires`
 * timestamp; we re-authenticate ~60s before it lapses.
 *
 * TLS: AppGate controllers ship with a self-signed cert from the Controller
 * CA.  Pulse goes through Node's `https` module (not global `fetch`) so it can
 * scope `rejectUnauthorized:false` to this client only — other outbound
 * HTTPS calls keep their strict verification.
 */

import * as https from 'node:https';
import { URL } from 'node:url';
import type {
  AppgateActiveSession,
  AppgateAppliance,
  AppgateHourlyLogins,
  AppgateLicense,
  AppgateLoginResponse,
  AppgateOnBoardedDevice,
  AppgateResultList,
  AppgateUserLicense,
} from '@/lib/types/appgate';

export interface AppgateClientConfig {
  baseUrl: string;       // e.g. https://wawnvaagp01.wulfconsulting.com:8443
  username: string;
  password: string;
  providerName: string;  // e.g. "local"
  deviceId: string;      // any stable UUID for this Pulse instance
  insecureTls?: boolean; // default: true (self-signed)
}

interface CachedToken {
  token: string;
  expiresAt: number; // epoch ms
}

const ACCEPT_HEADER = 'application/vnd.appgate.peer-v22+json';
const REFRESH_BUFFER_MS = 60_000;
const REQUEST_TIMEOUT_MS = 20_000;

export class AppgateClient {
  private readonly config: AppgateClientConfig;
  private readonly agent: https.Agent;
  private token: CachedToken | null = null;

  constructor(config: AppgateClientConfig) {
    this.config = config;
    this.agent = new https.Agent({
      rejectUnauthorized: config.insecureTls === false,
      keepAlive: true,
    });
  }

  private rawRequest<T>(
    path: string,
    method: 'GET' | 'POST',
    body?: unknown,
    auth?: { token?: string },
  ): Promise<T> {
    const url = new URL(`${this.config.baseUrl}${path}`);
    const payload = body !== undefined ? JSON.stringify(body) : undefined;
    return new Promise<T>((resolve, reject) => {
      const headers: Record<string, string> = {
        Accept: ACCEPT_HEADER,
        'Content-Type': 'application/json',
      };
      if (auth?.token) headers.Authorization = `Bearer ${auth.token}`;
      if (payload) headers['Content-Length'] = Buffer.byteLength(payload).toString();

      const req = https.request(
        {
          hostname: url.hostname,
          port: url.port || 443,
          path: url.pathname + url.search,
          method,
          headers,
          agent: this.agent,
          timeout: REQUEST_TIMEOUT_MS,
        },
        (res) => {
          const chunks: Buffer[] = [];
          res.on('data', (c) => chunks.push(c));
          res.on('end', () => {
            const text = Buffer.concat(chunks).toString('utf8');
            const status = res.statusCode ?? 0;
            if (status < 200 || status >= 300) {
              reject(new Error(`AppGate ${method} ${path} → ${status}: ${text.slice(0, 300)}`));
              return;
            }
            if (status === 204 || text.length === 0) {
              resolve(undefined as unknown as T);
              return;
            }
            try {
              resolve(JSON.parse(text) as T);
            } catch (e) {
              reject(new Error(`AppGate ${path}: invalid JSON response — ${(e as Error).message}`));
            }
          });
        },
      );
      req.on('error', (e) => reject(new Error(`AppGate ${path}: ${e.message}`)));
      req.on('timeout', () => {
        req.destroy(new Error(`AppGate ${path}: timeout after ${REQUEST_TIMEOUT_MS}ms`));
      });
      if (payload) req.write(payload);
      req.end();
    });
  }

  private async getToken(): Promise<string> {
    if (this.token && this.token.expiresAt - Date.now() > REFRESH_BUFFER_MS) {
      return this.token.token;
    }
    const resp = await this.rawRequest<AppgateLoginResponse>('/admin/login', 'POST', {
      providerName: this.config.providerName,
      username: this.config.username,
      password: this.config.password,
      deviceId: this.config.deviceId,
    });
    if (!resp.token || !resp.expires) {
      throw new Error('AppGate login response missing token/expires');
    }
    this.token = { token: resp.token, expiresAt: new Date(resp.expires).getTime() };
    return this.token.token;
  }

  private async get<T>(path: string): Promise<T> {
    const token = await this.getToken();
    return this.rawRequest<T>(`/admin${path}`, 'GET', undefined, { token });
  }

  // ─── Endpoints ─────────────────────────────────────────────────────────

  async getActiveSessions(): Promise<AppgateActiveSession[]> {
    const r = await this.get<AppgateResultList<AppgateActiveSession>>('/stats/active-sessions/dn');
    return r.data ?? [];
  }

  async getUserLoginsLast24h(): Promise<AppgateHourlyLogins> {
    const r = await this.get<{ data: AppgateHourlyLogins }>('/stats/user-logins');
    return r.data ?? {};
  }

  async getOnBoardedDevices(): Promise<AppgateOnBoardedDevice[]> {
    const r = await this.get<AppgateResultList<AppgateOnBoardedDevice>>('/on-boarded-devices');
    return r.data ?? [];
  }

  async getAppliances(): Promise<AppgateAppliance[]> {
    const r = await this.get<AppgateResultList<AppgateAppliance>>('/appliances');
    return r.data ?? [];
  }

  async getLicense(): Promise<AppgateLicense> {
    return this.get<AppgateLicense>('/license');
  }

  async getLicenseUsers(): Promise<AppgateUserLicense[]> {
    const r = await this.get<AppgateResultList<AppgateUserLicense>>('/license/users');
    return r.data ?? [];
  }

  // Cheap connectivity probe — does not require auth.
  async ping(): Promise<{ ok: true }> {
    await this.rawRequest<unknown>('/admin/identity-providers/names', 'GET');
    return { ok: true };
  }
}
