/**
 * Backpressure Signaling Utilities
 *
 * Provides consistent Retry-After header calculation for rate limiting,
 * circuit breakers, and graceful degradation scenarios.
 *
 * RFC 7231 compliant: Retry-After is specified in seconds.
 *
 * @module backpressure
 */

import { NextResponse } from "next/server";
import { logger } from "./logger";

const backpressureLogger = logger.scope("BACKPRESSURE");

/**
 * Backpressure reason codes for tracking and monitoring
 */
export type BackpressureReason =
  | "rate_limit_exceeded"
  | "circuit_breaker_open"
  | "service_degraded"
  | "system_overload"
  | "maintenance";

/**
 * Backpressure signal information
 */
export interface BackpressureSignal {
  /** Reason for backpressure */
  reason: BackpressureReason;
  /** Time until system is expected to recover (in seconds) */
  retryAfterSeconds: number;
  /** HTTP status code (429 or 503) */
  statusCode: 429 | 503;
  /** Human-readable message */
  message: string;
  /** Optional service name that triggered backpressure */
  service?: string;
}

/**
 * Default Retry-After values for different scenarios (in seconds)
 */
export const DEFAULT_RETRY_AFTER = {
  /** Rate limit: typically short window, try again soon */
  RATE_LIMIT: 60,
  /** Circuit breaker open: depends on reset timeout */
  CIRCUIT_BREAKER: 30,
  /** Service degraded: moderate wait */
  SERVICE_DEGRADED: 120,
  /** System overload: longer wait */
  SYSTEM_OVERLOAD: 300,
  /** Maintenance: could be longer */
  MAINTENANCE: 600,
  /** Minimum allowed value */
  MIN: 1,
  /** Maximum allowed value (1 hour) */
  MAX: 3600,
} as const;

/**
 * Calculate Retry-After in seconds from a future timestamp
 *
 * @param resetTimestamp - Future timestamp (ms since epoch) when system will be ready
 * @param minSeconds - Minimum retry time (default: 1)
 * @param maxSeconds - Maximum retry time (default: 3600)
 * @returns Retry-After in seconds, clamped to min/max bounds
 */
export function calculateRetryAfterFromTimestamp(
  resetTimestamp: number,
  minSeconds: number = DEFAULT_RETRY_AFTER.MIN,
  maxSeconds: number = DEFAULT_RETRY_AFTER.MAX,
): number {
  const now = Date.now();
  const diffMs = resetTimestamp - now;
  const diffSeconds = Math.ceil(diffMs / 1000);

  // Clamp to valid range
  return Math.max(minSeconds, Math.min(diffSeconds, maxSeconds));
}

/**
 * Calculate Retry-After in seconds from a duration in milliseconds
 *
 * @param durationMs - Duration in milliseconds
 * @param minSeconds - Minimum retry time (default: 1)
 * @param maxSeconds - Maximum retry time (default: 3600)
 * @returns Retry-After in seconds, clamped to min/max bounds
 */
export function calculateRetryAfterFromDuration(
  durationMs: number,
  minSeconds: number = DEFAULT_RETRY_AFTER.MIN,
  maxSeconds: number = DEFAULT_RETRY_AFTER.MAX,
): number {
  const seconds = Math.ceil(durationMs / 1000);
  return Math.max(minSeconds, Math.min(seconds, maxSeconds));
}

/**
 * Log backpressure event for monitoring
 */
export function logBackpressure(
  signal: BackpressureSignal,
  clientId?: string,
): void {
  backpressureLogger.warn("Backpressure applied", {
    reason: signal.reason,
    retryAfterSeconds: signal.retryAfterSeconds,
    statusCode: signal.statusCode,
    service: signal.service,
    clientId: clientId ?? "unknown",
    timestamp: new Date().toISOString(),
  });
}

/**
 * Create a backpressure signal for rate limiting (429)
 *
 * @param resetTimestamp - When the rate limit window resets (ms since epoch)
 * @param message - Custom error message
 * @returns BackpressureSignal
 */
export function createRateLimitSignal(
  resetTimestamp: number,
  message: string = "Too many requests. Please try again later.",
): BackpressureSignal {
  return {
    reason: "rate_limit_exceeded",
    retryAfterSeconds: calculateRetryAfterFromTimestamp(resetTimestamp),
    statusCode: 429,
    message,
  };
}

/**
 * Create a backpressure signal for circuit breaker (503)
 *
 * @param remainingTimeoutMs - Time until circuit transitions to half-open (ms)
 * @param serviceName - Name of the service with open circuit
 * @param message - Custom error message
 * @returns BackpressureSignal
 */
export function createCircuitBreakerSignal(
  remainingTimeoutMs: number,
  serviceName: string,
  message?: string,
): BackpressureSignal {
  const retryAfterSeconds = calculateRetryAfterFromDuration(remainingTimeoutMs);

  return {
    reason: "circuit_breaker_open",
    retryAfterSeconds,
    statusCode: 503,
    message:
      message ??
      `Service temporarily unavailable. Please retry after ${retryAfterSeconds} seconds.`,
    service: serviceName,
  };
}

/**
 * Create a backpressure signal for degraded service (503)
 *
 * @param serviceName - Name of the degraded service
 * @param retryAfterSeconds - Suggested retry time (defaults to SERVICE_DEGRADED)
 * @param message - Custom error message
 * @returns BackpressureSignal
 */
export function createDegradedServiceSignal(
  serviceName: string,
  retryAfterSeconds: number = DEFAULT_RETRY_AFTER.SERVICE_DEGRADED,
  message?: string,
): BackpressureSignal {
  return {
    reason: "service_degraded",
    retryAfterSeconds: Math.max(
      DEFAULT_RETRY_AFTER.MIN,
      Math.min(retryAfterSeconds, DEFAULT_RETRY_AFTER.MAX),
    ),
    statusCode: 503,
    message:
      message ??
      `Service is experiencing issues. Please retry after ${retryAfterSeconds} seconds.`,
    service: serviceName,
  };
}

/**
 * Create HTTP headers for backpressure response
 *
 * @param signal - BackpressureSignal
 * @param additionalHeaders - Any extra headers to include
 * @returns Headers object ready to use with NextResponse
 */
export function createBackpressureHeaders(
  signal: BackpressureSignal,
  additionalHeaders?: Record<string, string>,
): Record<string, string> {
  return {
    "Retry-After": signal.retryAfterSeconds.toString(),
    "X-Backpressure-Reason": signal.reason,
    ...(signal.service ? { "X-Backpressure-Service": signal.service } : {}),
    ...additionalHeaders,
  };
}

/**
 * Create a NextResponse with backpressure headers
 *
 * @param signal - BackpressureSignal
 * @param additionalHeaders - Any extra headers to include
 * @param clientId - Optional client identifier for logging
 * @returns NextResponse with appropriate status and headers
 */
export function createBackpressureResponse(
  signal: BackpressureSignal,
  additionalHeaders?: Record<string, string>,
  clientId?: string,
): NextResponse {
  // Log the backpressure event
  logBackpressure(signal, clientId);

  const headers = createBackpressureHeaders(signal, additionalHeaders);

  return NextResponse.json(
    {
      error: signal.message,
      retryAfter: signal.retryAfterSeconds,
      reason: signal.reason,
      ...(signal.service ? { service: signal.service } : {}),
    },
    {
      status: signal.statusCode,
      headers,
    },
  );
}

/**
 * Create a 429 rate limit response with Retry-After
 *
 * @param resetTimestamp - When the rate limit window resets (ms since epoch)
 * @param message - Custom error message
 * @param limit - The rate limit that was exceeded
 * @param clientId - Optional client identifier for logging
 * @returns NextResponse with 429 status and Retry-After header
 */
export function rateLimitResponse(
  resetTimestamp: number,
  message: string = "Too many requests. Please try again later.",
  limit?: number,
  clientId?: string,
): NextResponse {
  const signal = createRateLimitSignal(resetTimestamp, message);
  logBackpressure(signal, clientId);

  const headers: Record<string, string> = {
    "Retry-After": signal.retryAfterSeconds.toString(),
    "X-Backpressure-Reason": signal.reason,
  };

  if (limit !== undefined) {
    headers["X-RateLimit-Limit"] = limit.toString();
    headers["X-RateLimit-Remaining"] = "0";
    headers["X-RateLimit-Reset"] = resetTimestamp.toString();
  }

  return NextResponse.json(
    {
      error: message,
      retryAfter: signal.retryAfterSeconds,
    },
    {
      status: 429,
      headers,
    },
  );
}

/**
 * Create a 503 service unavailable response with Retry-After
 *
 * @param retryAfterSeconds - Suggested retry time in seconds
 * @param message - Custom error message
 * @param serviceName - Name of the unavailable service
 * @param clientId - Optional client identifier for logging
 * @returns NextResponse with 503 status and Retry-After header
 */
export function serviceUnavailableResponse(
  retryAfterSeconds: number,
  message: string = "Service temporarily unavailable. Please try again later.",
  serviceName?: string,
  clientId?: string,
): NextResponse {
  const signal: BackpressureSignal = {
    reason: serviceName ? "circuit_breaker_open" : "service_degraded",
    retryAfterSeconds: Math.max(
      DEFAULT_RETRY_AFTER.MIN,
      Math.min(retryAfterSeconds, DEFAULT_RETRY_AFTER.MAX),
    ),
    statusCode: 503,
    message,
    service: serviceName,
  };

  logBackpressure(signal, clientId);

  const headers: Record<string, string> = {
    "Retry-After": signal.retryAfterSeconds.toString(),
    "X-Backpressure-Reason": signal.reason,
  };

  if (serviceName) {
    headers["X-Backpressure-Service"] = serviceName;
  }

  return NextResponse.json(
    {
      error: message,
      retryAfter: signal.retryAfterSeconds,
      ...(serviceName ? { service: serviceName } : {}),
    },
    {
      status: 503,
      headers,
    },
  );
}
