import webpush from "web-push";
import UserMetadata from "supertokens-node/recipe/usermetadata";
import type { I18n, MessageDescriptor } from "@lingui/core";
import { vapidConfig, isPushConfigured } from "@/lib/config";
import { pbAdmin } from "@/lib/pocketbase/client";
import { logger } from "@/lib/logger";
import { createI18n, resolveLocale } from "@/lib/i18n";
import type { PushSubscriptionRecord } from "@/lib/pocketbase/services/push";

export interface PushPayload {
  title: string;
  body: string;
  /** Path (or absolute URL) to open/focus when the notification is clicked. */
  url?: string;
  /** Optional icon override; defaults to the app icon in the SW handler. */
  icon?: string;
  /** Optional tag to collapse/replace notifications of the same kind. */
  tag?: string;
}

/** A literal string (e.g. the brand name, never translated) or a localizable message descriptor. */
export type PushText = string | MessageDescriptor;

export interface LocalizedPushPayload extends Omit<PushPayload, "title" | "body"> {
  title: PushText;
  body: PushText;
}

function resolvePushText(i18n: I18n, text: PushText): string {
  return typeof text === "string" ? text : i18n._(text);
}

/**
 * sendPushToPlayer only has the recipient's playerId, not a request context,
 * so it resolves their locale from SuperTokens user metadata directly.
 * Falls back to the default locale for players with no linked auth account
 * or no stored locale preference.
 */
async function resolveRecipientLocale(playerId: string) {
  try {
    const player = await pbAdmin.getPlayer(playerId);
    if (!player.auth_id) return resolveLocale(undefined);
    const { metadata } = await UserMetadata.getUserMetadata(player.auth_id);
    return resolveLocale(metadata?.locale);
  } catch {
    return resolveLocale(undefined);
  }
}

let vapidInitialized = false;

function ensureVapid(): boolean {
  if (!isPushConfigured()) {
    logger.warn(
      "Push | VAPID keys not configured (VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY); skipping send"
    );
    return false;
  }
  if (!vapidInitialized) {
    webpush.setVapidDetails(
      vapidConfig.subject,
      vapidConfig.publicKey,
      vapidConfig.privateKey
    );
    vapidInitialized = true;
  }
  return true;
}

/**
 * Low-level: send a single push to one subscription. Throws on transport
 * errors. Callers that persist subscriptions should handle 404/410 (dead
 * endpoint) themselves — see sendPushToPlayer.
 */
export async function sendPush(
  subscription: {
    endpoint: string;
    keys: { p256dh: string; auth: string };
  },
  payload: PushPayload
): Promise<void> {
  if (!ensureVapid()) return;
  await webpush.sendNotification(subscription, JSON.stringify(payload));
}

/**
 * Send a push to every device a player has subscribed. Dead subscriptions
 * (HTTP 404/410 from the push service) are pruned from storage automatically.
 *
 * This is the seam that real product triggers will call LATER, e.g.:
 *   - a match the player is in starts        (src/lib/pocketbase/services/matches.ts)
 *   - a free-agent claim involving the player (src/lib/pocketbase/services/free-agents ...)
 *   - a badge is awarded to the player        (src/lib/pocketbase/services/badges.ts)
 * NONE of these are wired yet — this foundation pass only exposes the seam
 * plus a manual "test notification" path.
 *
 * title/body accept either a literal string (e.g. the brand name) or a
 * msg`` descriptor, resolved against the recipient's own locale.
 */
export async function sendPushToPlayer(
  playerId: string,
  payload: LocalizedPushPayload
): Promise<{ sent: number; failed: number; pruned: number }> {
  if (!ensureVapid()) return { sent: 0, failed: 0, pruned: 0 };

  const locale = await resolveRecipientLocale(playerId);
  const i18n = createI18n(locale);
  const resolvedPayload: PushPayload = {
    ...payload,
    title: resolvePushText(i18n, payload.title),
    body: resolvePushText(i18n, payload.body),
  };

  const subscriptions: PushSubscriptionRecord[] =
    await pbAdmin.listPushSubscriptionsByPlayer(playerId);

  let sent = 0;
  let failed = 0;
  let pruned = 0;

  await Promise.all(
    subscriptions.map(async (sub) => {
      try {
        await sendPush(
          {
            endpoint: sub.endpoint,
            keys: { p256dh: sub.p256dh, auth: sub.auth },
          },
          resolvedPayload
        );
        sent++;
      } catch (error: any) {
        const status = error?.statusCode;
        if (status === 404 || status === 410) {
          // Subscription no longer valid — prune it.
          try {
            await pbAdmin.deletePushSubscriptionById(sub.id);
            pruned++;
          } catch (delError) {
            logger.error("Push | Failed to prune dead subscription", delError);
          }
        } else {
          failed++;
          logger.error(
            `Push | Failed to send to endpoint (status ${status ?? "?"})`,
            error?.body ?? error
          );
        }
      }
    })
  );

  return { sent, failed, pruned };
}
