/**
 * Meta WhatsApp Token Refresh Service
 *
 * Long-lived Meta access tokens expire after ~60 days. This service:
 * 1. Tracks token issue date per org (`whatsappTokenIssuedAt`)
 * 2. Refreshes tokens before they expire (when older than 50 days)
 * 3. Can be called on-demand or via cron
 *
 * The Meta `fb_exchange_token` grant returns a fresh long-lived token from
 * an existing (still-valid) long-lived token, which lets us rotate before
 * the hard expiry without re-running the OAuth flow.
 *
 * Wire this into a daily cron (e.g. BullMQ repeatable job, Convex action,
 * or external scheduler) by calling `refreshAllExpiringTokens()`.
 */

import { encrypt, decrypt } from '@/lib/encryption';
import { logger } from '@/lib/logger';
import { convexMutation, convexQuery } from '@/lib/convex-client';
import { fetchWithTimeout } from '@/lib/fetch-with-timeout';

const META_GRAPH_API = 'https://graph.facebook.com/v19.0';
const REFRESH_THRESHOLD_DAYS = 50;
// Meta long-lived tokens expire after ~60 days (kept here for documentation
// and potential future use in scheduling logic).
export const TOKEN_LIFETIME_DAYS = 60;
const FETCH_TIMEOUT_MS = 10_000;

interface OrgWithToken {
  _id: string;
  whatsappAccessTokenEncrypted?: string;
  whatsappTokenIssuedAt?: number;
}

interface MetaTokenResponse {
  access_token?: string;
  token_type?: string;
  expires_in?: number;
}

/**
 * Refresh a single org's Meta access token if needed.
 *
 * Returns true if the token was refreshed, false if not needed or on error.
 * Errors are logged but never thrown so a single failing org cannot break
 * the batch refresh loop.
 */
export async function refreshOrgTokenIfNeeded(
  organizationId: string,
): Promise<boolean> {
  try {
    // Fetch the org's current token state.
    const org = await convexQuery<OrgWithToken | null>(
      'organizations/queries:getOrganizationByAnyId',
      { organizationId },
    );
    if (!org || !org.whatsappAccessTokenEncrypted) return false;

    const issuedAt = org.whatsappTokenIssuedAt ?? 0;
    const ageDays = (Date.now() - issuedAt) / (24 * 60 * 60 * 1000);

    // Skip if token is still fresh. We treat issuedAt === 0 (unknown) as
    // "needs refresh" so legacy orgs without the field get rotated on the
    // first run.
    if (issuedAt > 0 && ageDays < REFRESH_THRESHOLD_DAYS) {
      return false;
    }

    let currentToken: string;
    try {
      currentToken = decrypt(org.whatsappAccessTokenEncrypted);
    } catch (error) {
      logger.error('[TokenRefresh] Failed to decrypt current token', {
        organizationId,
        error,
      });
      return false;
    }

    // Exchange for a new long-lived token via fb_exchange_token grant.
    const appId = process.env.META_APP_ID;
    const appSecret = process.env.META_APP_SECRET;
    if (!appId || !appSecret) {
      logger.error('[TokenRefresh] META_APP_ID/SECRET not configured');
      return false;
    }

    const url =
      `${META_GRAPH_API}/oauth/access_token` +
      `?grant_type=fb_exchange_token` +
      `&client_id=${encodeURIComponent(appId)}` +
      `&client_secret=${encodeURIComponent(appSecret)}` +
      `&fb_exchange_token=${encodeURIComponent(currentToken)}`;

    const res = await fetchWithTimeout(url, { method: 'GET' }, FETCH_TIMEOUT_MS);

    if (!res.ok) {
      const errorText = await res.text().catch(() => '');
      logger.error('[TokenRefresh] Meta API error', {
        organizationId,
        status: res.status,
        errorText: errorText.slice(0, 500),
      });
      return false;
    }

    const data = (await res.json().catch(() => ({}))) as MetaTokenResponse;
    if (!data.access_token) {
      logger.error('[TokenRefresh] No access_token in response', {
        organizationId,
      });
      return false;
    }

    // Store the new encrypted token and update the issue timestamp.
    await convexMutation(
      'organizations/mutations:updateOrganizationNoAuth',
      {
        organizationId,
        whatsappAccessTokenEncrypted: encrypt(data.access_token),
        whatsappTokenIssuedAt: Date.now(),
      },
    );

    logger.info('[TokenRefresh] Token refreshed', {
      organizationId,
      previousAgeDays: Math.round(ageDays),
    });
    return true;
  } catch (error) {
    logger.error('[TokenRefresh] Refresh failed', { organizationId, error });
    return false;
  }
}

export interface RefreshBatchResult {
  checked: number;
  refreshed: number;
  errors: number;
}

/**
 * Refresh tokens for all orgs that need it.
 *
 * Designed to be called from a daily cron. Iterates every org with a
 * verified WhatsApp webhook (i.e. WhatsApp is actually in use) and rotates
 * any token older than `REFRESH_THRESHOLD_DAYS`.
 */
export async function refreshAllExpiringTokens(): Promise<RefreshBatchResult> {
  let checked = 0;
  let refreshed = 0;
  let errors = 0;

  try {
    // Fetch all orgs with WhatsApp configured. We use the prisma adapter
    // here for parity with the rest of the codebase — it routes through
    // Convex at runtime via lib/prisma-convex-adapter.ts.
    const { default: prisma } = await import('@/lib/db');
    const orgs = (await prisma.organizations.findMany({
      where: { whatsappWebhookVerified: true },
      select: { id: true },
    })) as Array<{ id: string }>;

    for (const org of orgs) {
      checked++;
      try {
        const wasRefreshed = await refreshOrgTokenIfNeeded(org.id);
        if (wasRefreshed) refreshed++;
      } catch (error) {
        errors++;
        logger.error('[TokenRefresh] Org refresh threw', {
          organizationId: org.id,
          error,
        });
      }
    }

    logger.info('[TokenRefresh] Batch complete', {
      checked,
      refreshed,
      errors,
    });
  } catch (error) {
    logger.error('[TokenRefresh] Batch failed', { error });
  }

  return { checked, refreshed, errors };
}
