/**
 * PAX8 REST API Client (v1)
 * OAuth2 client-credentials flow for partner/reseller reads.
 * https://devx.pax8.com
 */

import type {
  Pax8Company,
  Pax8PageEnvelope,
  Pax8Subscription,
  Pax8Product,
  Pax8Invoice,
  Pax8InvoiceItem,
} from '@/lib/types/pax8';

export interface Pax8ClientConfig {
  clientId: string;
  clientSecret: string;
}

export class Pax8Client {
  private config: Pax8ClientConfig;
  private accessToken: string | null = null;
  private tokenExpiry: number = 0;

  constructor(config: Pax8ClientConfig) {
    this.config = config;
  }

  private async getToken(): Promise<string> {
    if (this.accessToken && Date.now() < this.tokenExpiry - 60000) {
      return this.accessToken;
    }

    // PAX8 deviation from msgraph-client.ts — JSON body + audience field,
    // NOT application/x-www-form-urlencoded + URLSearchParams.
    const res = await fetch('https://api.pax8.com/v1/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify({
        grant_type: 'client_credentials',
        client_id: this.config.clientId,
        client_secret: this.config.clientSecret,
        audience: 'https://api.pax8.com', // partner/reseller audience — NOT api://provisioning
      }),
    });

    if (!res.ok) {
      const text = await res.text();
      throw new Error(`PAX8 token request failed: ${res.status} ${text}`);
    }

    const data = await res.json();
    // WR-03: a 200 response with an unexpected body shape (proxy/CDN error
    // page reshaped as JSON, a future API version renaming the field, etc.)
    // must not silently set accessToken to undefined and mask the failure
    // behind a confusing 401 from the data endpoint later.
    if (!data.access_token || typeof data.expires_in !== 'number') {
      throw new Error('PAX8 token response missing access_token/expires_in');
    }
    const token: string = data.access_token;
    this.accessToken = token;
    this.tokenExpiry = Date.now() + data.expires_in * 1000;
    return token;
  }

  // Phase 11/12 will extend this with 429-aware Retry-After backoff for the
  // account-wide 1000/min rate limit (10-RESEARCH.md Pitfall 4) — not needed
  // for this phase's single auth-proof call.
  private async fetchJson<T>(path: string, retryCount = 0): Promise<T> {
    const token = await this.getToken();
    const res = await fetch(`https://api.pax8.com/v1${path}`, {
      headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
    });

    if (res.status === 429 && retryCount < 4) {
      const retryAfter = Math.max(30, parseInt(res.headers.get('Retry-After') || '30', 10));
      await new Promise(r => setTimeout(r, retryAfter * 1000));
      return this.fetchJson(path, retryCount + 1);
    }

    if (!res.ok) {
      const text = await res.text();
      throw new Error(`PAX8 API error ${res.status} for ${path}: ${text}`);
    }

    return res.json();
  }

  /** Auth-proof read: list a page of companies. */
  async listCompanies(page = 0, size = 10): Promise<Pax8PageEnvelope<Pax8Company>> {
    return this.fetchJson<Pax8PageEnvelope<Pax8Company>>(`/companies?page=${page}&size=${size}`);
  }

  /**
   * Generic read-only paginate-until-exhausted helper. Loops the given
   * page-fetcher (each call inherits fetchJson's 429/Retry-After backoff),
   * requesting size=200, and stops once page.number >= page.totalPages - 1.
   * GET-only — no method override is ever set, satisfying PAX8-08.
   */
  private async paginateAll<T>(
    fetchPage: (page: number, size: number) => Promise<Pax8PageEnvelope<T>>,
  ): Promise<T[]> {
    const size = 200;
    const items: T[] = [];
    let page = 0;

    while (true) {
      const envelope = await fetchPage(page, size);
      items.push(...envelope.content);
      if (envelope.page.number >= envelope.page.totalPages - 1) break;
      page++;
    }

    return items;
  }

  /** Read-only: page through every company (PAX8-08 — GET only). */
  async listAllCompanies(): Promise<Pax8Company[]> {
    return this.paginateAll<Pax8Company>((page, size) =>
      this.fetchJson<Pax8PageEnvelope<Pax8Company>>(`/companies?page=${page}&size=${size}`),
    );
  }

  /** Read-only: page through every subscription (PAX8-08 — GET only). */
  async listAllSubscriptions(): Promise<Pax8Subscription[]> {
    return this.paginateAll<Pax8Subscription>((page, size) =>
      this.fetchJson<Pax8PageEnvelope<Pax8Subscription>>(`/subscriptions?page=${page}&size=${size}`),
    );
  }

  /** Read-only: page through every product (PAX8-08 — GET only). */
  async listAllProducts(): Promise<Pax8Product[]> {
    return this.paginateAll<Pax8Product>((page, size) =>
      this.fetchJson<Pax8PageEnvelope<Pax8Product>>(`/products?page=${page}&size=${size}`),
    );
  }

  /**
   * Read-only: page through every invoice header (PAX8-08 — GET only).
   * `/invoices` is the partner's own consolidated monthly bill — one row per
   * billing period, not per end-customer (12-RESEARCH.md Pitfall 1). Do NOT
   * add an orders-endpoint call — it is unreliable (504s) for this account
   * (12-RESEARCH.md Pitfall 3).
   */
  async listAllInvoices(): Promise<Pax8Invoice[]> {
    return this.paginateAll<Pax8Invoice>((page, size) =>
      this.fetchJson<Pax8PageEnvelope<Pax8Invoice>>(`/invoices?page=${page}&size=${size}`),
    );
  }

  /**
   * Read-only: page through every line item of a single invoice
   * (PAX8-08 — GET only). Invoice items are a per-invoice child resource —
   * there is no flat `/invoice-items` endpoint (12-RESEARCH.md Pitfall 2).
   */
  async listAllInvoiceItems(invoiceId: string): Promise<Pax8InvoiceItem[]> {
    return this.paginateAll<Pax8InvoiceItem>((page, size) =>
      this.fetchJson<Pax8PageEnvelope<Pax8InvoiceItem>>(
        `/invoices/${invoiceId}/items?page=${page}&size=${size}`,
      ),
    );
  }
}
