import { NextResponse } from "next/server";
import { APICallError } from "ai";
import { checkAndIncrementTierLimit, refundAiCall, TierLimitError } from "@/lib/tiers";
import { ByokDecryptError } from "@/lib/ai/resolve-user-key";

/**
 * Maps an AI-call failure to a clean JSON response instead of letting the
 * raw provider error (which can include internal provider names/payloads)
 * reach the client or crash the route as an uncaught 500.
 */
export function aiErrorResponse(err: unknown): NextResponse {
  if (APICallError.isInstance(err)) {
    return NextResponse.json(
      {
        error: err.isRetryable
          ? "The AI provider is temporarily unavailable. Please try again in a moment."
          : "The AI provider rejected this request. Try switching models in Settings.",
        retryable: err.isRetryable,
      },
      { status: err.isRetryable ? 503 : 502 }
    );
  }

  return NextResponse.json({ error: "AI request failed. Please try again." }, { status: 502 });
}

/**
 * Resolves a BYOK/model config and converts a decrypt failure into a clean
 * 400 response instead of letting it throw uncaught (or letting the caller
 * silently fall back to the platform key/billing).
 */
export async function resolveAiConfigOrError<T>(resolve: () => Promise<T>): Promise<{ ok: true; data: T } | { ok: false; response: NextResponse }> {
  try {
    return { ok: true, data: await resolve() };
  } catch (err) {
    if (err instanceof ByokDecryptError) {
      return { ok: false, response: NextResponse.json({ error: err.message }, { status: 400 }) };
    }
    throw err;
  }
}

type QuotaResult<T> = { ok: true; data: T } | { ok: false; response: NextResponse };

/**
 * Charges one aiCall credit, runs `fn`, and refunds the credit if `fn`
 * throws — so a provider outage never silently burns a user's quota.
 * Centralizes the TierLimitError → 403 and AI-failure → clean-JSON mapping
 * that every AI route needs instead of repeating try/catch per route.
 *
 * Pass `skipQuota: true` when the resolved AiConfig used the caller's own
 * BYOK key (config.isByok) — it's their own credentials/billing, so it
 * shouldn't count against the platform's monthly AI-call limit.
 */
export async function withAiQuota<T>(
  userId: string,
  tier: "free" | "pro" | "family",
  fn: () => Promise<T>,
  opts?: { skipQuota?: boolean }
): Promise<QuotaResult<T>> {
  if (!opts?.skipQuota) {
    try {
      await checkAndIncrementTierLimit(userId, tier, "aiCall");
    } catch (err) {
      if (err instanceof TierLimitError) {
        return { ok: false, response: NextResponse.json({ error: "AI call limit reached for your tier" }, { status: 403 }) };
      }
      throw err;
    }
  }

  try {
    const data = await fn();
    return { ok: true, data };
  } catch (err) {
    if (!opts?.skipQuota) await refundAiCall(userId);
    return { ok: false, response: aiErrorResponse(err) };
  }
}
