/**
 * API endpoint to serve cached images from the database
 * Serves avatars, emojis, attachments, etc. from local cache
 * 
 * Security: Uses HMAC signatures to prevent enumeration of image IDs.
 * Images can only be accessed with a valid signature generated server-side.
 */
import sql from '../../../utils/db.ts';
import crypto from 'crypto';
import {
  checkRateLimit,
  recordRequest,
} from '../../../utils/rateLimit.ts';
import type { APIContext } from 'astro';

// Secret for signing image IDs - prevents enumeration attacks
const IMAGE_CACHE_SECRET = import.meta.env.IMAGE_CACHE_SECRET;
if (!IMAGE_CACHE_SECRET && import.meta.env.PROD) {
  throw new Error('CRITICAL: IMAGE_CACHE_SECRET environment variable is not set in production!');
} else if (!IMAGE_CACHE_SECRET) {
  console.error('WARNING: IMAGE_CACHE_SECRET environment variable is not set!');
}

/**
 * Generate HMAC signature for an image ID
 * @param imageId - The image ID to sign
 * @returns The hex signature
 */
export function signImageId(imageId: string | number): string {
  if (!IMAGE_CACHE_SECRET) {
    throw new Error('IMAGE_CACHE_SECRET not configured');
  }
  const hmac = crypto.createHmac('sha256', IMAGE_CACHE_SECRET);
  hmac.update(String(imageId));
  return hmac.digest('hex').substring(0, 32); // 128-bit signature
}

/**
 * Generate HMAC signature for a source URL
 * @param sourceUrl - The URL to sign
 * @returns The hex signature
 */
export function signSourceUrl(sourceUrl: string): string {
  if (!IMAGE_CACHE_SECRET) {
    throw new Error('IMAGE_CACHE_SECRET not configured');
  }
  const hmac = crypto.createHmac('sha256', IMAGE_CACHE_SECRET);
  hmac.update(sourceUrl);
  return hmac.digest('hex').substring(0, 32); // 128-bit signature
}

/**
 * Verify HMAC signature for an image ID
 * @param imageId - The image ID
 * @param signature - The signature to verify
 * @returns Whether signature is valid
 */
function verifyImageSignature(imageId: string | number, signature: string | null): boolean {
  if (!IMAGE_CACHE_SECRET || !signature) return false;
  const expected = signImageId(imageId);
  // Timing-safe comparison
  try {
    return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
  } catch {
    return false;
  }
}

/**
 * Verify HMAC signature for a source URL
 * @param sourceUrl - The URL
 * @param signature - The signature to verify
 * @returns Whether signature is valid
 */
function verifyUrlSignature(sourceUrl: string, signature: string | null): boolean {
  if (!IMAGE_CACHE_SECRET || !signature) return false;
  const expected = signSourceUrl(sourceUrl);
  // Timing-safe comparison
  try {
    return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
  } catch {
    return false;
  }
}

export async function GET({ request }: APIContext): Promise<Response> {
  // Rate limit check - higher limit for images but still protected
  const rateCheck = checkRateLimit(request, {
    limit: 100,
    windowMs: 1000,
    burstLimit: 500,
    burstWindowMs: 10_000,
  });

  if (!rateCheck.allowed) {
    return new Response('Rate limit exceeded', {
      status: 429,
      headers: { 'Retry-After': '1' },
    });
  }

  recordRequest(request, 1000);

  const url = new URL(request.url);
  const imageId = url.searchParams.get('id');
  const signature = url.searchParams.get('sig');
  const sourceUrl = url.searchParams.get('url');

  if (!imageId && !sourceUrl) {
    return new Response('Missing id or url parameter', { status: 400 });
  }

  // Validate imageId is a valid integer if provided
  if (imageId && !/^\d+$/.test(imageId)) {
    return new Response('Invalid image id', { status: 400 });
  }

  // Require valid signature for ID-based lookups to prevent enumeration
  if (imageId && !verifyImageSignature(imageId, signature)) {
    return new Response('Invalid or missing signature', { status: 403 });
  }

  try {
    let image;
    
    if (imageId) {
      // Look up by image_id (signature already verified above)
      const result = await sql`
        SELECT image_data, content_type, source_url
        FROM image_cache
        WHERE image_id = ${imageId}
      `;
      image = result[0];
    } else if (sourceUrl) {
      // Require signature for URL-based lookups to prevent enumeration
      if (!verifyUrlSignature(sourceUrl, signature)) {
        return new Response('Invalid or missing signature for URL lookup', { status: 403 });
      }
      const result = await sql`
        SELECT image_data, content_type, source_url
        FROM image_cache
        WHERE source_url = ${sourceUrl}
      `;
      image = result[0];
    }

    if (!image) {
      return new Response('Image not found in cache', { status: 404 });
    }

    // image_data is a Buffer (bytea)
    const imageBuffer = image.image_data;
    const contentType = image.content_type || 'image/png';

    return new Response(imageBuffer, {
      status: 200,
      headers: {
        'Content-Type': contentType,
        'Content-Length': imageBuffer.length.toString(),
        'Cache-Control': 'public, max-age=31536000, immutable', // Cache for 1 year since it's immutable
        'X-Content-Type-Options': 'nosniff',
      },
    });

  } catch (error) {
    console.error('Error serving cached image:', error);
    return new Response('Failed to serve image', { status: 500 });
  }
}
