import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { startRequest, endRequest } from "../_shared/logger.ts";

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
};

interface IPLocationResponse {
  country: string;
  countryCode: string;
  measurementSystem: 'metric' | 'imperial';
}

// Simple in-memory rate limiter
const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
const RATE_LIMIT_WINDOW = 60000; // 1 minute in milliseconds
const MAX_REQUESTS = 10; // 10 requests per minute per IP
const MAX_MAP_SIZE = 10000; // Maximum number of IPs to track

// Cleanup failure tracking to prevent silent failures
let cleanupFailureCount = 0;
const MAX_CLEANUP_FAILURES = 5; // Threshold before forcing drastic cleanup
const CLEANUP_FAILURE_RESET_INTERVAL = 300000; // Reset failure count every 5 minutes

function cleanupExpiredEntries() {
  try {
    const now = Date.now();
    let deletedCount = 0;
    const mapSizeBefore = rateLimitMap.size;
    
    for (const [ip, data] of rateLimitMap.entries()) {
      if (now > data.resetAt) {
        rateLimitMap.delete(ip);
        deletedCount++;
      }
    }
    
    // Log cleanup activity for monitoring
    if (deletedCount > 0) {
      console.log(`[Cleanup] Removed ${deletedCount} expired entries. Map size: ${mapSizeBefore} -> ${rateLimitMap.size}`);
    }
    
    // Reset failure count on successful cleanup
    if (cleanupFailureCount > 0) {
      console.log(`[Cleanup] Successful cleanup after ${cleanupFailureCount} previous failures. Resetting failure count.`);
      cleanupFailureCount = 0;
    }
    
  } catch (error: unknown) {
    // CRITICAL: Increment failure counter and log detailed error information
    cleanupFailureCount++;
    
    const errorMessage = error instanceof Error ? error.message : String(error);
    const errorStack = error instanceof Error ? error.stack : 'No stack trace available';
    
    console.error('[Cleanup Error]', {
      attempt: cleanupFailureCount,
      maxAttempts: MAX_CLEANUP_FAILURES,
      error: errorMessage,
      stack: errorStack,
      mapSize: rateLimitMap.size
    });
    
    // FALLBACK MECHANISM: If cleanup fails repeatedly, force clear to prevent memory leak
    if (cleanupFailureCount >= MAX_CLEANUP_FAILURES) {
      console.error(`[Cleanup CRITICAL] Cleanup has failed ${cleanupFailureCount} times consecutively!`);
      console.error(`[Cleanup CRITICAL] Forcing emergency cleanup to prevent memory leak...`);
      
      try {
        // Emergency: Clear oldest 50% of entries to prevent unbounded growth
        const entriesToClear = Math.floor(rateLimitMap.size * 0.5);
        const sortedEntries = Array.from(rateLimitMap.entries())
          .sort((a, b) => a[1].resetAt - b[1].resetAt);
        
        let clearedCount = 0;
        for (let i = 0; i < entriesToClear && i < sortedEntries.length; i++) {
          rateLimitMap.delete(sortedEntries[i][0]);
          clearedCount++;
        }
        
        console.warn(`[Cleanup CRITICAL] Emergency cleanup completed. Cleared ${clearedCount} entries. Map size: ${rateLimitMap.size}`);
        
        // Reset failure count after emergency cleanup
        cleanupFailureCount = 0;
        
      } catch (emergencyError) {
        // Last resort: If even emergency cleanup fails, clear everything
        console.error(`[Cleanup CRITICAL] Emergency cleanup failed! Clearing entire rate limit map.`);
        console.error(`[Cleanup CRITICAL] Emergency error: ${emergencyError}`);
        
        const originalSize = rateLimitMap.size;
        rateLimitMap.clear();
        
        console.warn(`[Cleanup CRITICAL] Cleared entire rate limit map (${originalSize} entries) to prevent memory leak.`);
        cleanupFailureCount = 0;
      }
    }
  }
}

// Reset cleanup failure count periodically to avoid permanent emergency state
setInterval(() => {
  if (cleanupFailureCount > 0) {
    console.warn(`[Cleanup] Resetting failure count (was ${cleanupFailureCount}) after timeout period.`);
    cleanupFailureCount = 0;
  }
}, CLEANUP_FAILURE_RESET_INTERVAL);

function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } {
  const now = Date.now();
  const existing = rateLimitMap.get(ip);

  // Handle existing entries (most common case - early return for performance)
  if (existing && now <= existing.resetAt) {
    if (existing.count >= MAX_REQUESTS) {
      const retryAfter = Math.ceil((existing.resetAt - now) / 1000);
      return { allowed: false, retryAfter };
    }
    existing.count++;
    return { allowed: true };
  }

  // Need to add new entry or reset expired one
  // Only perform cleanup if we're at capacity AND adding a new IP
  if (!existing && rateLimitMap.size >= MAX_MAP_SIZE) {
    // First try cleaning expired entries
    cleanupExpiredEntries();
    
    // If still at capacity after cleanup, remove oldest entries (LRU eviction)
    if (rateLimitMap.size >= MAX_MAP_SIZE) {
      try {
        const toDelete = Math.floor(MAX_MAP_SIZE * 0.3); // Remove 30% of entries
        const sortedEntries = Array.from(rateLimitMap.entries())
          .sort((a, b) => a[1].resetAt - b[1].resetAt);
        
        let deletedCount = 0;
        for (let i = 0; i < toDelete && i < sortedEntries.length; i++) {
          rateLimitMap.delete(sortedEntries[i][0]);
          deletedCount++;
        }
        
        console.warn(`[Rate Limit] Map reached ${MAX_MAP_SIZE} entries. Cleared ${deletedCount} oldest entries. New size: ${rateLimitMap.size}`);
      } catch (evictionError) {
        // CRITICAL: LRU eviction failed - log error and attempt emergency clear
        console.error(`[Rate Limit CRITICAL] LRU eviction failed! Error: ${evictionError}`);
        console.error(`[Rate Limit CRITICAL] Map size: ${rateLimitMap.size}`);
        
        try {
          // Emergency: Clear first 30% of entries without sorting
          const targetSize = Math.floor(MAX_MAP_SIZE * 0.7);
          const keysToDelete: string[] = [];
          
          for (const [key] of rateLimitMap.entries()) {
            if (rateLimitMap.size <= targetSize) break;
            keysToDelete.push(key);
          }
          
          keysToDelete.forEach(key => rateLimitMap.delete(key));
          
          console.warn(`[Rate Limit CRITICAL] Emergency eviction cleared ${keysToDelete.length} entries. New size: ${rateLimitMap.size}`);
        } catch (emergencyError) {
          console.error(`[Rate Limit CRITICAL] Emergency eviction also failed! Clearing entire map. Error: ${emergencyError}`);
          rateLimitMap.clear();
        }
      }
    }
  }
  
  // Create new entry or reset expired entry
  rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW });
  return { allowed: true };
}

// Clean up old entries periodically to prevent memory leak
// Run cleanup more frequently to catch expired entries sooner
setInterval(cleanupExpiredEntries, Math.min(RATE_LIMIT_WINDOW / 2, 30000)); // Every 30 seconds or half the window

serve(async (req) => {
  // Handle CORS preflight requests
  if (req.method === 'OPTIONS') {
    return new Response(null, { headers: corsHeaders });
  }

  const tracking = startRequest('detect-location');

  try {
    // Get the client's IP address
    const forwarded = req.headers.get('x-forwarded-for');
    const realIP = req.headers.get('x-real-ip');
    const clientIP = forwarded?.split(',')[0] || realIP || '8.8.8.8'; // fallback to Google DNS for testing
    
    // Check rate limit
    const rateLimit = checkRateLimit(clientIP);
    if (!rateLimit.allowed) {
      return new Response(
        JSON.stringify({
          error: 'Rate limit exceeded',
          message: 'Too many requests. Please try again later.',
          retryAfter: rateLimit.retryAfter
        }),
        {
          status: 429,
          headers: {
            ...corsHeaders,
            'Content-Type': 'application/json',
            'Retry-After': String(rateLimit.retryAfter || 60)
          }
        }
      );
    }
    
    // PII Note: Do not log full IP addresses in production
    console.log('[Location] Detecting location for request');

    // Use configurable geolocation service with proper error handling
    // Defaults to ip-api.com if not configured
    const geoApiUrl = Deno.env.get('GEOLOCATION_API_URL') || 'http://ip-api.com/json';
    const geoApiFields = Deno.env.get('GEOLOCATION_API_FIELDS') || 'status,country,countryCode';
    
    let geoResponse;
    try {
      geoResponse = await fetch(`${geoApiUrl}/${clientIP}?fields=${geoApiFields}`);
    } catch (fetchError) {
      console.error('Network error fetching location data:', fetchError);
      throw new Error('Network error: Unable to reach geolocation service');
    }
    
    if (!geoResponse.ok) {
      throw new Error(`Geolocation service returned ${geoResponse.status}: ${geoResponse.statusText}`);
    }
    
    let geoData;
    try {
      geoData = await geoResponse.json();
    } catch (parseError) {
      console.error('Failed to parse geolocation response:', parseError);
      throw new Error('Invalid response format from geolocation service');
    }
    
    if (geoData.status !== 'success') {
      throw new Error(`Geolocation failed: ${geoData.message || 'Invalid location data'}`);
    }

    // Countries that primarily use imperial system
    const imperialCountries = ['US', 'LR', 'MM']; // USA, Liberia, Myanmar
    const measurementSystem = imperialCountries.includes(geoData.countryCode) ? 'imperial' : 'metric';

    const result: IPLocationResponse = {
      country: geoData.country,
      countryCode: geoData.countryCode,
      measurementSystem
    };

    console.log('[Location] Location detected:', { 
      country: result.country, 
      countryCode: result.countryCode, 
      measurementSystem: result.measurementSystem,
      requestId: tracking.requestId
    });

    endRequest(tracking);

    return new Response(
      JSON.stringify({ ...result, requestId: tracking.requestId }),
      { 
        headers: { 
          ...corsHeaders, 
          'Content-Type': 'application/json',
          'X-Request-ID': tracking.requestId
        } 
      }
    );

  } catch (error: unknown) {
    // Enhanced error logging for better visibility and debugging
    const errorMessage = error instanceof Error ? error.message : String(error);
    const errorStack = error instanceof Error ? error.stack : 'No stack trace available';
    
    console.error('[Location Detection Error]', {
      error: errorMessage,
      stack: errorStack,
      hasIP: true, // IP removed for PII protection
      requestId: tracking.requestId
    });
    
    endRequest(tracking);

    // Return default (metric) with 500 status to indicate error occurred
    // This allows proper error monitoring while still providing fallback data
    const defaultResult: IPLocationResponse = {
      country: 'Unknown',
      countryCode: 'XX',
      measurementSystem: 'metric'
    };

    return new Response(
      JSON.stringify({
        ...defaultResult,
        error: errorMessage,
        fallback: true,
        requestId: tracking.requestId
      }),
      { 
        headers: { 
          ...corsHeaders, 
          'Content-Type': 'application/json',
          'X-Request-ID': tracking.requestId
        },
        status: 500
      }
    );
  }
});