/**
 * POST /api/recommend - AI-powered server recommendation handler
 */

import type {
  Env,
  RecommendRequest,
  Server,
  BenchmarkData,
  VPSBenchmark,
  TechSpec,
  BandwidthEstimate,
  RecommendationResult,
  BenchmarkReference,
  AIRecommendationResponse
} from '../types';
import { i18n, LIMITS } from '../config';
import {
  jsonResponse,
  validateRecommendRequest,
  generateCacheKey,
  estimateBandwidth,
  calculateBandwidthInfo,
  escapeLikePattern,
  isValidServer,
  isValidBenchmarkData,
  isValidVPSBenchmark,
  isValidTechSpec,
  isValidAIRecommendation,
  sanitizeForAIPrompt,
  DEFAULT_REGION_FILTER_SQL
} from '../utils';

export async function handleRecommend(
  request: Request,
  env: Env,
  corsHeaders: Record<string, string>
): Promise<Response> {
  const requestId = crypto.randomUUID();

  try {
    // Check request body size to prevent large payload attacks
    const contentLength = request.headers.get('Content-Length');
    if (contentLength && parseInt(contentLength, 10) > LIMITS.MAX_REQUEST_BODY_BYTES) {
      return jsonResponse(
        { error: 'Request body too large', max_size: '10KB' },
        413,
        corsHeaders
      );
    }

    // Parse and validate request with actual body size check
    const bodyText = await request.text();
    const actualBodySize = new TextEncoder().encode(bodyText).length;

    if (actualBodySize > LIMITS.MAX_REQUEST_BODY_BYTES) {
      return jsonResponse(
        { error: 'Request body too large', max_size: '10KB', actual_size: actualBodySize },
        413,
        corsHeaders
      );
    }

    const body = JSON.parse(bodyText) as RecommendRequest;
    const lang = body.lang || 'en';
    const validationError = validateRecommendRequest(body, lang);
    if (validationError) {
      return jsonResponse(validationError, 400, corsHeaders);
    }

    console.log('[Recommend] Request summary:', {
      tech_stack_count: body.tech_stack.length,
      expected_users: body.expected_users,
      use_case_length: body.use_case.length,
      traffic_pattern: body.traffic_pattern,
      has_region_pref: !!body.region_preference,
      has_budget: !!body.budget_limit,
      has_provider_filter: !!body.provider_filter,
      lang: lang,
    });

    // Generate cache key
    const cacheKey = generateCacheKey(body);
    console.log('[Recommend] Cache key:', cacheKey);

    // Check cache (optional - may not be configured)
    if (env.CACHE) {
      const cached = await env.CACHE.get(cacheKey);
      if (cached) {
        console.log('[Recommend] Cache hit');
        return jsonResponse(
          { ...JSON.parse(cached), cached: true },
          200,
          corsHeaders
        );
      }
    }

    console.log('[Recommend] Cache miss or disabled');

    // Phase 1: Execute independent queries in parallel
    const [techSpecs, benchmarkDataAll] = await Promise.all([
      queryTechSpecs(env.DB, body.tech_stack),
      queryBenchmarkData(env.DB, body.tech_stack).catch(err => {
        console.warn('[Recommend] Benchmark data unavailable:', err.message);
        return [] as BenchmarkData[];
      }),
    ]);
    console.log('[Recommend] Tech specs matched:', techSpecs.length);
    console.log('[Recommend] Benchmark data points (initial):', benchmarkDataAll.length);

    // Calculate minimum memory with proper aggregation
    // Memory-intensive services (Java, Elasticsearch, Redis): sum their memory requirements
    // Non-memory-intensive services: 256MB overhead each
    const memoryIntensiveSpecs = techSpecs.filter(s => s.is_memory_intensive);
    const otherSpecs = techSpecs.filter(s => !s.is_memory_intensive);

    let minMemoryMb: number | undefined;
    if (memoryIntensiveSpecs.length > 0 || otherSpecs.length > 0) {
      // Sum memory-intensive requirements
      const memoryIntensiveSum = memoryIntensiveSpecs.reduce((sum, s) => sum + s.min_memory_mb, 0);
      // Add 256MB overhead per non-memory-intensive service
      const otherOverhead = otherSpecs.length * 256;
      minMemoryMb = memoryIntensiveSum + otherOverhead;

      console.log(`[Recommend] Memory calculation: ${memoryIntensiveSpecs.length} memory-intensive (${(memoryIntensiveSum/1024).toFixed(1)}GB) + ${otherSpecs.length} other services (${(otherOverhead/1024).toFixed(1)}GB) = ${(minMemoryMb/1024).toFixed(1)}GB total`);
    }

    // Calculate minimum vCPU with category-based weighting
    // Different tech categories have different bottleneck characteristics
    let minVcpu: number | undefined;

    // DB workload multiplier based on use_case (databases need different resources based on workload type)
    // Lower multiplier = heavier workload = more resources needed
    const getDbWorkloadMultiplier = (useCase: string): { multiplier: number; type: string } => {
      const lowerUseCase = useCase.toLowerCase();

      // Heavy DB workloads (analytics, big data, reporting) - multiplier 0.3x
      // Note: use \blog(s|ging)?\b to match "log", "logs", "logging" but NOT "blog"
      if (/analytics|warehouse|reporting|dashboard|\bbi\b|olap|\blog(s|ging)?\b|metric|monitoring|time.?series|대시보드|분석|리포트|로그/.test(lowerUseCase)) {
        return { multiplier: 0.3, type: 'heavy (analytics/reporting)' };
      }

      // Medium-heavy DB workloads (e-commerce, ERP, CRM, social) - multiplier 0.5x
      if (/e.?commerce|shop|store|cart|order|payment|erp|crm|inventory|social|community|forum|게시판|쇼핑몰|주문|결제|커뮤니티/.test(lowerUseCase)) {
        return { multiplier: 0.5, type: 'medium-heavy (transactional)' };
      }

      // Medium DB workloads (API, SaaS, app backend) - multiplier 0.7x
      if (/api|saas|backend|service|app|mobile|플랫폼|서비스|앱/.test(lowerUseCase)) {
        return { multiplier: 0.7, type: 'medium (API/SaaS)' };
      }

      // Light DB workloads (blog, landing, portfolio, docs) - multiplier 1.0x (use default)
      if (/blog|landing|portfolio|doc|wiki|static|personal|홈페이지|블로그|포트폴리오|문서/.test(lowerUseCase)) {
        return { multiplier: 1.0, type: 'light (content/read-heavy)' };
      }

      // Default: medium workload
      return { multiplier: 0.7, type: 'default (medium)' };
    };

    const dbWorkload = getDbWorkloadMultiplier(body.use_case);
    console.log(`[Recommend] DB workload inferred from use_case: ${dbWorkload.type} (multiplier: ${dbWorkload.multiplier})`);

    if (techSpecs.length > 0) {
      // Group specs by category
      const categoryWeights: Record<string, number> = {
        'web_server': 0.1,      // nginx, apache: reverse proxy uses minimal resources
        'runtime': 1.0,         // nodejs, php, python: actual computation
        'database': 1.0,        // mysql, postgresql, mongodb: major bottleneck
        'cache': 0.5,           // redis, memcached: supporting role
        'search': 0.8,          // elasticsearch: CPU-intensive but not always primary
        'container': 0.3,       // docker: orchestration overhead
        'messaging': 0.5,       // rabbitmq, kafka: I/O bound
        'default': 0.7          // unknown categories
      };

      // Calculate weighted vCPU requirements per category
      const categoryRequirements = new Map<string, number>();

      for (const spec of techSpecs) {
        const category = spec.category || 'default';
        const weight = categoryWeights[category] || categoryWeights['default'];

        // Apply DB workload multiplier for database category
        // Lower multiplier = heavier workload = higher resource needs (lower vcpu_per_users)
        let effectiveVcpuPerUsers = spec.vcpu_per_users;
        if (category === 'database') {
          effectiveVcpuPerUsers = Math.max(1, Math.floor(spec.vcpu_per_users * dbWorkload.multiplier));
        }

        const vcpuNeeded = Math.ceil(body.expected_users / effectiveVcpuPerUsers);
        const weightedVcpu = vcpuNeeded * weight;

        const existing = categoryRequirements.get(category) || 0;
        // Take max within same category (not additive)
        categoryRequirements.set(category, Math.max(existing, weightedVcpu));

        const dbNote = category === 'database' ? ` (adjusted for ${dbWorkload.type})` : '';
        console.log(`[Recommend] ${spec.name} (${category}): ${vcpuNeeded} vCPU × ${weight} weight = ${weightedVcpu.toFixed(1)} weighted vCPU${dbNote}`);
      }

      // Find bottleneck: use MAX across categories, not SUM
      // Request flow (web_server → runtime → database) means the slowest component is the bottleneck
      // SUM would over-provision since components process the SAME requests sequentially
      let maxWeightedVcpu = 0;
      let bottleneckCategory = '';
      for (const [category, vcpu] of categoryRequirements) {
        console.log(`[Recommend] Category '${category}': ${vcpu.toFixed(1)} weighted vCPU`);
        if (vcpu > maxWeightedVcpu) {
          maxWeightedVcpu = vcpu;
          bottleneckCategory = category;
        }
      }

      minVcpu = Math.max(Math.ceil(maxWeightedVcpu), 1); // At least 1 vCPU
      console.log(`[Recommend] Bottleneck: '${bottleneckCategory}' with ${maxWeightedVcpu.toFixed(1)} weighted vCPU → ${minVcpu} vCPU (for ${body.expected_users} users)`);
    }

    // Calculate bandwidth estimate for provider filtering
    const bandwidthEstimate = estimateBandwidth(body.expected_users, body.use_case, body.traffic_pattern);
    console.log(`[Recommend] Bandwidth estimate: ${bandwidthEstimate.monthly_tb >= 1 ? bandwidthEstimate.monthly_tb + ' TB' : bandwidthEstimate.monthly_gb + ' GB'}/month (${bandwidthEstimate.category})`);

    // Estimate specs for VPS benchmark query (doesn't need exact candidates)
    const estimatedCores = minVcpu || 2;
    const estimatedMemory = minMemoryMb ? Math.ceil(minMemoryMb / 1024) : 4;
    const defaultProviders = bandwidthEstimate?.category === 'very_heavy' ? ['Linode'] : ['Linode', 'Vultr'];

    // Phase 2: Query candidate servers and VPS benchmarks in parallel
    const [candidates, vpsBenchmarks] = await Promise.all([
      queryCandidateServers(env.DB, body, minMemoryMb, minVcpu, bandwidthEstimate, lang),
      queryVPSBenchmarksBatch(env.DB, estimatedCores, estimatedMemory, defaultProviders).catch((err: unknown) => {
        const message = err instanceof Error ? err.message : String(err);
        console.warn('[Recommend] VPS benchmarks unavailable:', message);
        return [] as VPSBenchmark[];
      }),
    ]);

    console.log('[Recommend] Candidate servers:', candidates.length);
    console.log('[Recommend] VPS benchmark data points:', vpsBenchmarks.length);

    if (candidates.length === 0) {
      return jsonResponse(
        {
          error: 'No servers found matching your requirements',
          recommendations: [],
          request_id: requestId,
        },
        200,
        corsHeaders
      );
    }

    // Use initially fetched benchmark data (already filtered by tech stack)
    const benchmarkData = benchmarkDataAll;

    // Use OpenAI GPT-4o-mini to analyze and recommend (techSpecs already queried above)
    const aiResult = await getAIRecommendations(
      env,
      env.OPENAI_API_KEY,
      body,
      candidates,
      benchmarkData,
      vpsBenchmarks,
      techSpecs,
      bandwidthEstimate,
      lang
    );

    console.log('[Recommend] Generated recommendations:', aiResult.recommendations.length);

    const response = {
      recommendations: aiResult.recommendations,
      infrastructure_tips: aiResult.infrastructure_tips || [],
      bandwidth_estimate: {
        monthly_tb: bandwidthEstimate.monthly_tb,
        monthly_gb: bandwidthEstimate.monthly_gb,
        daily_gb: bandwidthEstimate.daily_gb,
        category: bandwidthEstimate.category,
        description: bandwidthEstimate.description,
        active_ratio: bandwidthEstimate.active_ratio,
        calculation_note: `Based on ${body.expected_users} concurrent users with ${Math.round(bandwidthEstimate.active_ratio * 100)}% active ratio`,
      },
      total_candidates: candidates.length,
      cached: false,
    };

    // Cache result only if we have recommendations (don't cache empty/failed results)
    if (env.CACHE && response.recommendations && response.recommendations.length > 0) {
      await env.CACHE.put(cacheKey, JSON.stringify(response), {
        expirationTtl: 300, // 5 minutes (reduced from 1 hour for faster iteration)
      });
    }

    return jsonResponse(response, 200, corsHeaders);
  } catch (error) {
    console.error('[Recommend] Error:', error);
    console.error('[Recommend] Error stack:', error instanceof Error ? error.stack : 'No stack');
    console.error('[Recommend] Error details:', error instanceof Error ? error.message : 'Unknown error');
    return jsonResponse(
      {
        error: 'Failed to generate recommendations',
        request_id: requestId,
      },
      500,
      corsHeaders
    );
  }
}
async function queryCandidateServers(
  db: D1Database,
  req: RecommendRequest,
  minMemoryMb?: number,
  minVcpu?: number,
  bandwidthEstimate?: BandwidthEstimate,
  lang: string = 'en'
): Promise<Server[]> {
  // Select price column based on language
  // Korean → monthly_price_krw (KRW), Others → monthly_price_retail (1.21x USD)
  const priceColumn = lang === 'ko' ? 'pr.monthly_price_krw' : 'pr.monthly_price_retail';
  const currency = lang === 'ko' ? 'KRW' : 'USD';

  // Check if region preference is specified
  const hasRegionPref = req.region_preference && req.region_preference.length > 0;

  let query = `
    SELECT
      it.id,
      p.display_name as provider_name,
      it.instance_id,
      it.instance_name,
      it.vcpu,
      it.memory_mb,
      ROUND(it.memory_mb / 1024.0, 1) as memory_gb,
      it.storage_gb,
      it.network_speed_gbps,
      it.instance_family,
      it.gpu_count,
      it.gpu_type,
      MIN(${priceColumn}) as monthly_price,
      r.region_name as region_name,
      r.region_code as region_code,
      r.country_code as country_code
    FROM instance_types it
    JOIN providers p ON it.provider_id = p.id
    JOIN pricing pr ON pr.instance_type_id = it.id
    JOIN regions r ON pr.region_id = r.id
    WHERE LOWER(p.name) IN ('linode', 'vultr')  -- Linode, Vultr only
  `;

  const params: (string | number)[] = [];

  if (req.budget_limit) {
    // Use same price column as display for budget filtering
    query += ` AND ${priceColumn} <= ?`;
    params.push(req.budget_limit);
  }

  // Filter by minimum memory requirement (from tech specs)
  if (minMemoryMb && minMemoryMb > 0) {
    query += ` AND it.memory_mb >= ?`;
    params.push(minMemoryMb);
    console.log(`[Candidates] Filtering by minimum memory: ${minMemoryMb}MB (${(minMemoryMb/1024).toFixed(1)}GB)`);
  }

  // Filter by minimum vCPU requirement (from expected users + tech specs)
  if (minVcpu && minVcpu > 0) {
    query += ` AND it.vcpu >= ?`;
    params.push(minVcpu);
    console.log(`[Candidates] Filtering by minimum vCPU: ${minVcpu}`);
  }

  // Provider preference based on bandwidth requirements (no hard filtering to avoid empty results)
  // Heavy/Very heavy bandwidth → Prefer Linode (better bandwidth allowance), but allow all providers
  // AI prompt will warn about bandwidth costs for non-Linode providers
  if (bandwidthEstimate) {
    if (bandwidthEstimate.category === 'very_heavy') {
      // >6TB/month: Strongly prefer Linode, but don't exclude others (Linode may not be available in all regions)
      console.log(`[Candidates] Very heavy bandwidth (${bandwidthEstimate.monthly_tb}TB/month): Linode strongly preferred, all providers included`);
    } else if (bandwidthEstimate.category === 'heavy') {
      // 2-6TB/month: Prefer Linode
      console.log(`[Candidates] Heavy bandwidth (${bandwidthEstimate.monthly_tb}TB/month): Linode preferred`);
    }
  }

  // Country name to code mapping for common names
  // Note: Use specific city names to avoid LIKE pattern collisions (e.g., 'de' matches 'Delhi')
  const countryNameToCode: Record<string, string[]> = {
    'korea': ['seoul', 'koreacentral', 'kr', 'ap-northeast-2'],
    'south korea': ['seoul', 'koreacentral', 'kr', 'ap-northeast-2'],
    'japan': ['tokyo', 'osaka', 'ap-northeast-1', 'ap-northeast-3'],
    'singapore': ['singapore', 'ap-southeast-1'],
    'indonesia': ['jakarta', 'ap-southeast-3'],
    'india': ['mumbai', 'delhi', 'bangalore', 'hyderabad', 'ap-south-1'],
    'australia': ['sydney', 'melbourne', 'ap-southeast-2'],
    'germany': ['frankfurt', 'nuremberg', 'falkenstein', 'eu-central-1'],
    'usa': ['us-east', 'us-west', 'virginia', 'oregon', 'ohio'],
    'united states': ['us-east', 'us-west', 'virginia', 'oregon', 'ohio'],
    'uk': ['london', 'manchester', 'eu-west-2'],
    'united kingdom': ['london', 'manchester', 'eu-west-2'],
    'netherlands': ['amsterdam', 'eu-west-1'],
    'france': ['paris', 'eu-west-3'],
    'hong kong': ['hong kong', 'ap-east-1'],
    'taiwan': ['taipei', 'ap-northeast-1'],
    'brazil': ['sao paulo', 'sa-east-1'],
    'canada': ['montreal', 'toronto', 'ca-central-1'],
  };

  // Flexible region matching: region_code, region_name, or country_code
  if (req.region_preference && req.region_preference.length > 0) {
    // User specified region → filter to that region only
    const regionConditions: string[] = [];
    for (const region of req.region_preference) {
      const lowerRegion = region.toLowerCase();

      // Expand country names to their codes/cities
      const expandedRegions = countryNameToCode[lowerRegion] || [lowerRegion];
      const allRegions = [lowerRegion, ...expandedRegions];

      for (const r of allRegions) {
        const escapedRegion = escapeLikePattern(r);
        regionConditions.push(`(
          LOWER(r.region_code) = ? OR
          LOWER(r.region_code) LIKE ? ESCAPE '\\' OR
          LOWER(r.region_name) LIKE ? ESCAPE '\\' OR
          LOWER(r.country_code) = ?
        )`);
        params.push(r, `%${escapedRegion}%`, `%${escapedRegion}%`, r);
      }
    }
    query += ` AND (${regionConditions.join(' OR ')})`;
  } else {
    // No region specified → default to Seoul/Tokyo/Osaka/Singapore
    query += ` AND ${DEFAULT_REGION_FILTER_SQL}`;
  }

  // Filter by provider if specified
  if (req.provider_filter && req.provider_filter.length > 0) {
    const placeholders = req.provider_filter.map(() => '?').join(',');
    query += ` AND (p.name IN (${placeholders}) OR p.display_name IN (${placeholders}))`;
    params.push(...req.provider_filter, ...req.provider_filter);
  }

  // Group by instance + region to show each server per region
  // For heavy/very_heavy bandwidth, prioritize Linode due to generous bandwidth allowance
  const isHighBandwidth = bandwidthEstimate?.category === 'heavy' || bandwidthEstimate?.category === 'very_heavy';
  const orderByClause = isHighBandwidth
    ? `ORDER BY CASE WHEN LOWER(p.name) = 'linode' THEN 0 ELSE 1 END, monthly_price ASC`
    : `ORDER BY monthly_price ASC`;
  query += ` GROUP BY it.id, r.id ${orderByClause} LIMIT 50`;

  const result = await db.prepare(query).bind(...params).all();

  if (!result.success) {
    throw new Error('Failed to query candidate servers');
  }

  // Add currency to each result and validate with type guard
  const serversWithCurrency = (result.results as unknown[]).map(server => {
    if (typeof server === 'object' && server !== null) {
      return { ...server, currency };
    }
    return server;
  });
  const validServers = serversWithCurrency.filter(isValidServer);
  const invalidCount = result.results.length - validServers.length;
  if (invalidCount > 0) {
    console.warn(`[Candidates] Filtered out ${invalidCount} invalid server records`);
  }
  return validServers;
}

/**
 * Query relevant benchmark data for tech stack
 */
async function queryBenchmarkData(
  db: D1Database,
  techStack: string[],
  coreCount?: number
): Promise<BenchmarkData[]> {
  // Map tech stack to relevant benchmark types
  const techToBenchmark: Record<string, string[]> = {
    'node.js': ['pts-node-octane', 'pts-node-express-loadtest'],
    'nodejs': ['pts-node-octane', 'pts-node-express-loadtest'],
    'express': ['pts-node-express-loadtest'],
    'nginx': ['pts-nginx'],
    'apache': ['pts-apache'],
    'php': ['pts-phpbench'],
    'redis': ['pts-redis'],
    'mysql': ['pts-mysqlslap'],
    'postgresql': ['pts-mysqlslap'], // Use MySQL benchmark as proxy
    'docker': ['pts-compress-7zip', 'pts-postmark'], // CPU + I/O for containers
    'mongodb': ['pts-postmark'], // I/O intensive
    'python': ['pts-coremark', 'pts-compress-7zip'],
    'java': ['pts-coremark', 'pts-compress-7zip'],
    'go': ['pts-coremark', 'pts-compress-7zip'],
    'rust': ['pts-coremark', 'pts-compress-7zip'],
  };

  // Find relevant benchmark types
  const relevantBenchmarks = new Set<string>();
  for (const tech of techStack) {
    const benchmarks = techToBenchmark[tech.toLowerCase()] || [];
    benchmarks.forEach(b => relevantBenchmarks.add(b));
  }

  // Always include general CPU benchmark
  relevantBenchmarks.add('pts-compress-7zip');

  if (relevantBenchmarks.size === 0) {
    return [];
  }

  const benchmarkNames = Array.from(relevantBenchmarks);
  const placeholders = benchmarkNames.map(() => '?').join(',');

  // Query benchmark data, optionally filtering by core count
  let query = `
    SELECT
      p.id,
      p.name as processor_name,
      bt.name as benchmark_name,
      bt.category,
      br.score,
      br.percentile,
      p.cores
    FROM benchmark_results br
    JOIN processors p ON br.processor_id = p.id
    JOIN benchmark_types bt ON br.benchmark_type_id = bt.id
    WHERE bt.name IN (${placeholders})
  `;

  const params: (string | number)[] = [...benchmarkNames];

  // If we know core count, filter to similar processors
  if (coreCount && coreCount > 0) {
    query += ` AND (p.cores IS NULL OR (p.cores >= ? AND p.cores <= ?))`;
    params.push(Math.max(1, coreCount - 2), coreCount + 4);
  }

  query += ` ORDER BY br.percentile DESC, br.score DESC LIMIT 50`;

  const result = await db.prepare(query).bind(...params).all();

  if (!result.success) {
    console.warn('[Benchmark] Query failed');
    return [];
  }

  // Validate each result with type guard
  return (result.results as unknown[]).filter(isValidBenchmarkData);
}

/**
 * Get benchmark reference for a server
 */
function getBenchmarkReference(
  benchmarks: BenchmarkData[],
  vcpu: number
): BenchmarkReference | undefined {
  // Find benchmarks from processors with similar core count
  const similarBenchmarks = benchmarks.filter(b =>
    b.cores === null || (b.cores >= vcpu - 2 && b.cores <= vcpu + 4)
  );

  if (similarBenchmarks.length === 0) {
    return undefined;
  }

  // Group by processor and get the best match
  const byProcessor = new Map<string, BenchmarkData[]>();
  for (const b of similarBenchmarks) {
    const existing = byProcessor.get(b.processor_name) || [];
    existing.push(b);
    byProcessor.set(b.processor_name, existing);
  }

  // Find processor with most benchmark data
  let bestProcessor = '';
  let maxBenchmarks = 0;
  for (const [name, data] of byProcessor) {
    if (data.length > maxBenchmarks) {
      maxBenchmarks = data.length;
      bestProcessor = name;
    }
  }

  if (!bestProcessor) {
    return undefined;
  }

  const processorBenchmarks = byProcessor.get(bestProcessor)!;
  return {
    processor_name: bestProcessor,
    benchmarks: processorBenchmarks.map(b => ({
      name: b.benchmark_name,
      category: b.category,
      score: b.score,
      percentile: b.percentile,
    })),
  };
}

/**
 * Query VPS benchmarks in a single batched query
 * Consolidates multiple provider-specific queries into one for better performance
 */
async function queryVPSBenchmarksBatch(
  db: D1Database,
  vcpu: number,
  memoryGb: number,
  providers: string[]
): Promise<VPSBenchmark[]> {
  const vcpuMin = Math.max(1, vcpu - 1);
  const vcpuMax = vcpu + 2;
  const memMin = Math.max(1, memoryGb - 2);
  const memMax = memoryGb + 4;

  // Build provider conditions for up to 3 providers
  const providerConditions: string[] = [];
  const params: (string | number)[] = [];

  const limitedProviders = providers.slice(0, 3);
  for (const provider of limitedProviders) {
    const pattern = `%${escapeLikePattern(provider.toLowerCase())}%`;
    providerConditions.push(`(LOWER(provider_name) LIKE ? ESCAPE '\\' OR LOWER(plan_name) LIKE ? ESCAPE '\\')`);
    params.push(pattern, pattern);
  }

  // Build query with provider matching OR spec matching
  const query = `
    SELECT * FROM vps_benchmarks
    WHERE ${providerConditions.length > 0 ? `(${providerConditions.join(' OR ')})` : '1=0'}
       OR (vcpu >= ? AND vcpu <= ? AND memory_gb >= ? AND memory_gb <= ?)
    ORDER BY gb6_single_normalized DESC
    LIMIT 30
  `;

  params.push(vcpuMin, vcpuMax, memMin, memMax);

  const result = await db.prepare(query).bind(...params).all();

  if (!result.success) {
    console.warn('[VPSBenchmarksBatch] Query failed');
    return [];
  }

  // Validate each result with type guard
  return (result.results as unknown[]).filter(isValidVPSBenchmark);
}

/**
 * Format VPS benchmark data for AI prompt
 * Uses GB6-normalized scores (GB5 scores converted with ×1.45 factor)
 */
function formatVPSBenchmarkSummary(benchmarks: VPSBenchmark[]): string {
  if (benchmarks.length === 0) {
    return '';
  }

  const lines = ['Real VPS performance data (Geekbench 6 normalized):'];
  for (const b of benchmarks.slice(0, 5)) {
    const versionNote = b.geekbench_version?.startsWith('5.') ? ' [GB5→6]' : '';
    lines.push(
      `- ${b.plan_name} (${b.country_code}): Single=${b.gb6_single_normalized}, Multi=${b.gb6_multi_normalized}${versionNote}, $${b.monthly_price_usd}/mo, Perf/$=${b.performance_per_dollar.toFixed(1)}`
    );
  }

  return lines.join('\n');
}

/**
 * Format benchmark data for AI prompt
 */
function formatBenchmarkSummary(benchmarks: BenchmarkData[]): string {
  if (benchmarks.length === 0) {
    return '';
  }

  // Group by benchmark type
  const byType = new Map<string, BenchmarkData[]>();
  for (const b of benchmarks) {
    const existing = byType.get(b.benchmark_name) || [];
    existing.push(b);
    byType.set(b.benchmark_name, existing);
  }

  const lines: string[] = [];
  for (const [type, data] of byType) {
    // Get top 3 performers for this benchmark
    const top3 = data.slice(0, 3);
    const scores = top3.map(d =>
      `${d.processor_name}${d.cores ? ` (${d.cores} cores)` : ''}: ${d.score} (${d.percentile}th percentile)`
    );
    lines.push(`### ${type} (${data[0].category})`);
    lines.push(scores.join('\n'));
    lines.push('');
  }

  return lines.join('\n');
}

/**
 * Query tech stack specifications from database
 * Matches user's tech_stack against canonical names and aliases
 */
async function queryTechSpecs(
  db: D1Database,
  techStack: string[]
): Promise<TechSpec[]> {
  if (!techStack || techStack.length === 0) {
    return [];
  }

  // Normalize user input
  const normalizedStack = techStack.map(t => t.toLowerCase().trim());

  // Build query that matches both name and aliases (case-insensitive)
  // Using LOWER() for alias matching since aliases are stored as JSON array strings
  const conditions: string[] = [];
  const params: string[] = [];

  for (const tech of normalizedStack) {
    conditions.push(`(LOWER(name) = ? OR LOWER(aliases) LIKE ?)`);
    params.push(tech, `%"${tech}"%`);
  }

  const query = `
    SELECT
      id, name, category,
      vcpu_per_users, vcpu_per_users_max,
      min_memory_mb, max_memory_mb,
      description, aliases,
      is_memory_intensive, is_cpu_intensive
    FROM tech_specs
    WHERE ${conditions.join(' OR ')}
    ORDER BY category, name
  `;

  try {
    const result = await db.prepare(query).bind(...params).all();

    if (!result.success) {
      console.warn('[TechSpecs] Query failed');
      return [];
    }

    // Validate each result with type guard
    const validSpecs = (result.results as unknown[]).filter(isValidTechSpec);
    console.log(`[TechSpecs] Found ${validSpecs.length} specs for: ${normalizedStack.join(', ')}`);
    return validSpecs;
  } catch (error) {
    console.error('[TechSpecs] Error:', error);
    return [];
  }
}

/**
 * Format tech specs for AI prompt
 */
function formatTechSpecsForPrompt(techSpecs: TechSpec[]): string {
  if (!techSpecs || techSpecs.length === 0) {
    return `Tech stack resource guidelines:
- Default: 1 vCPU per 100-300 users, 1-2GB RAM`;
  }

  const lines = ['Tech stack resource guidelines (MUST follow minimum RAM requirements):'];

  for (const spec of techSpecs) {
    const vcpuRange = spec.vcpu_per_users_max
      ? `${spec.vcpu_per_users}-${spec.vcpu_per_users_max}`
      : `${spec.vcpu_per_users}`;

    // Convert MB to GB for readability
    const minMemoryGB = (spec.min_memory_mb / 1024).toFixed(1).replace('.0', '');
    const maxMemoryGB = spec.max_memory_mb ? (spec.max_memory_mb / 1024).toFixed(1).replace('.0', '') : null;
    const memoryRange = maxMemoryGB ? `${minMemoryGB}-${maxMemoryGB}GB` : `${minMemoryGB}GB+`;

    let line = `- ${spec.name}: 1 vCPU per ${vcpuRange} users, MINIMUM ${minMemoryGB}GB RAM`;

    // Add warnings for special requirements
    const warnings: string[] = [];
    if (spec.is_memory_intensive) warnings.push('⚠️ MEMORY-INTENSIVE: must have at least ' + minMemoryGB + 'GB RAM');
    if (spec.is_cpu_intensive) warnings.push('⚠️ CPU-INTENSIVE');
    if (warnings.length > 0) {
      line += ` [${warnings.join(', ')}]`;
    }

    lines.push(line);
  }

  // Add explicit warning for memory-intensive apps
  const memoryIntensive = techSpecs.filter(s => s.is_memory_intensive);
  if (memoryIntensive.length > 0) {
    const maxMinMemory = Math.max(...memoryIntensive.map(s => s.min_memory_mb));
    lines.push('');
    lines.push(`⚠️ CRITICAL: This tech stack includes memory-intensive apps. Servers with less than ${(maxMinMemory / 1024).toFixed(0)}GB RAM will NOT work properly!`);
  }

  return lines.join('\n');
}

/**
 * Get AI-powered recommendations using OpenAI GPT-4o-mini
 */
async function getAIRecommendations(
  env: Env,
  apiKey: string,
  req: RecommendRequest,
  candidates: Server[],
  benchmarkData: BenchmarkData[],
  vpsBenchmarks: VPSBenchmark[],
  techSpecs: TechSpec[],
  bandwidthEstimate: BandwidthEstimate,
  lang: string = 'en'
): Promise<{ recommendations: RecommendationResult[]; infrastructure_tips?: string[] }> {
  // Validate API key before making any API calls
  if (!apiKey || !apiKey.trim()) {
    console.error('[AI] OPENAI_API_KEY is not configured or empty');
    throw new Error('OPENAI_API_KEY not configured. Please set the secret via: wrangler secret put OPENAI_API_KEY');
  }
  if (!apiKey.startsWith('sk-')) {
    console.error('[AI] OPENAI_API_KEY has invalid format (should start with sk-)');
    throw new Error('Invalid OPENAI_API_KEY format');
  }
  console.log('[AI] API key validated (format: sk-***)');

  // Build dynamic tech specs prompt from database
  const techSpecsPrompt = formatTechSpecsForPrompt(techSpecs);

  // Ensure lang is valid
  const validLang = ['en', 'zh', 'ja', 'ko'].includes(lang) ? lang : 'en';
  const languageInstruction = i18n[validLang].aiLanguageInstruction;

  // Build system prompt with benchmark awareness
  const systemPrompt = `You are a cloud infrastructure expert focused on COST-EFFECTIVE solutions. Your goal is to recommend the SMALLEST and CHEAPEST server that can handle the user's requirements.

CRITICAL RULES:
1. NEVER over-provision. Recommend the minimum specs needed.
2. Cost efficiency is the PRIMARY factor - cheaper is better if it meets requirements.
3. A 1-2 vCPU server can handle 100-500 concurrent users for most web workloads.
4. Nginx/reverse proxy needs very little resources - 1 vCPU can handle 1000+ req/sec.
5. Provide 3 options: Budget (cheapest viable), Balanced (some headroom), Premium (growth ready).

BANDWIDTH CONSIDERATIONS (VERY IMPORTANT):
- Estimated monthly bandwidth is provided based on concurrent users and use case.
- TOTAL COST = Base server price + Bandwidth overage charges
- Provider bandwidth allowances:
  * Linode: 1TB (1GB plan) to 20TB (192GB plan) included free, $0.005/GB overage
  * Vultr: 1TB-10TB depending on plan, $0.01/GB overage (2x Linode rate)
  * DigitalOcean: 1TB-12TB depending on plan, $0.01/GB overage
- For bandwidth >1TB/month: Linode is often cheaper despite higher base price
- For bandwidth >3TB/month: Linode is STRONGLY preferred (overage savings significant)
- Always mention bandwidth implications in cost_efficiency analysis

${techSpecsPrompt}

Use REAL BENCHMARK DATA to validate capacity estimates.

${languageInstruction}`;

  // Build user prompt with requirements and candidates
  console.log('[AI] Bandwidth estimate:', bandwidthEstimate);

  // Detect high-traffic based on bandwidth estimate (more accurate than keyword matching)
  const isHighTraffic = bandwidthEstimate.category === 'heavy' || bandwidthEstimate.category === 'very_heavy';

  // Format benchmark data for the prompt
  const benchmarkSummary = formatBenchmarkSummary(benchmarkData);
  const vpsBenchmarkSummary = formatVPSBenchmarkSummary(vpsBenchmarks);

  // Pre-filter candidates to reduce AI prompt size and cost
  // Sort by price and limit to top 15 most affordable options
  const topCandidates = candidates
    .sort((a, b) => a.monthly_price - b.monthly_price)
    .slice(0, 15);

  console.log(`[AI] Filtered ${candidates.length} candidates to ${topCandidates.length} for AI analysis`);

  // Sanitize user inputs to prevent prompt injection
  const sanitizedTechStack = req.tech_stack.map(t => sanitizeForAIPrompt(t, 50)).join(', ');
  const sanitizedUseCase = sanitizeForAIPrompt(req.use_case, 200);

  const userPrompt = `Analyze these server options and recommend the top 3 best matches.

## User Requirements
- Tech Stack: ${sanitizedTechStack}
- Expected Concurrent Users: ${req.expected_users} ${req.traffic_pattern === 'spiky' ? '(with traffic spikes)' : req.traffic_pattern === 'growing' ? '(growing user base)' : '(steady traffic)'}
- **Estimated DAU (Daily Active Users)**: ${bandwidthEstimate.estimated_dau_min.toLocaleString()}-${bandwidthEstimate.estimated_dau_max.toLocaleString()}명 (동시 접속 ${req.expected_users}명 기준)
- Use Case: ${sanitizedUseCase}
- Traffic Pattern: ${req.traffic_pattern || 'steady'}
- **Estimated Monthly Bandwidth**: ${bandwidthEstimate.monthly_tb >= 1 ? `${bandwidthEstimate.monthly_tb} TB` : `${bandwidthEstimate.monthly_gb} GB`} (${bandwidthEstimate.category})
${isHighTraffic ? `- ⚠️ HIGH BANDWIDTH WORKLOAD (${bandwidthEstimate.monthly_tb} TB/month): MUST recommend Linode over Vultr. Linode includes 1-6TB/month transfer vs Vultr overage charges ($0.01/GB). Bandwidth cost savings > base price difference.` : ''}
${req.region_preference ? `- Region Preference: ${req.region_preference.join(', ')}` : ''}
${req.budget_limit ? `- Budget Limit: $${req.budget_limit}/month` : ''}

## Real VPS Benchmark Data (Geekbench 6 normalized - actual VPS tests)
${vpsBenchmarkSummary || 'No similar VPS benchmark data available.'}

## CPU Benchmark Reference (from Phoronix Test Suite)
${benchmarkSummary || 'No relevant CPU benchmark data available.'}

## Available Servers (IMPORTANT: Use the server_id value, NOT the list number!)
${topCandidates.map((s) => `
[server_id=${s.id}] ${s.provider_name} - ${s.instance_name}${s.instance_family ? ` (${s.instance_family})` : ''}
   Instance: ${s.instance_id}
   vCPU: ${s.vcpu} | Memory: ${s.memory_gb} GB | Storage: ${s.storage_gb} GB
   Network: ${s.network_speed_gbps ? `${s.network_speed_gbps} Gbps` : 'N/A'}${s.gpu_count > 0 ? ` | GPU: ${s.gpu_count}x ${s.gpu_type || 'Unknown'}` : ' | GPU: None'}
   Price: ${s.currency === 'KRW' ? '₩' : '$'}${s.currency === 'KRW' ? Math.round(s.monthly_price).toLocaleString() : s.monthly_price.toFixed(2)}/month (${s.currency}) | Region: ${s.region_name} (${s.region_code})
`).join('\n')}

Return ONLY a valid JSON object (no markdown, no code blocks) with this exact structure:
{
  "recommendations": [
    {
      "server_id": 2045,  // Use the actual server_id from [server_id=XXXX] above, NOT list position!
      "score": 95,
      "analysis": {
        "tech_fit": "Why this server fits the tech stack",
        "capacity": "MUST mention: '동시 접속 X명 요청 (DAU A-B명), 최대 동시 Y명까지 처리 가능' format",
        "cost_efficiency": "MUST include: base price + bandwidth cost estimate. Example: '$5/month + ~$X bandwidth = ~$Y total'",
        "scalability": "Scalability potential including bandwidth headroom"
      },
      "estimated_capacity": {
        "max_concurrent_users": 7500,
        "requests_per_second": 1000
      }
    }
  ],
  "infrastructure_tips": [
    "Practical tip 1",
    "Practical tip 2"
  ]
}

Provide exactly 3 recommendations:
1. BUDGET option: Cheapest TOTAL cost (base + bandwidth) that can handle the load (highest score if viable)
2. BALANCED option: Some headroom for traffic spikes
3. PREMIUM option: Ready for 2-3x growth

SCORING (100 points total):
- Total Cost Efficiency (40%): Base price + estimated bandwidth overage. Lower total = higher score.
- Capacity Fit (30%): Can it handle the concurrent users and bandwidth?
- Scalability (30%): Room for growth in CPU, memory, AND bandwidth allowance.

The option with the LOWEST TOTAL MONTHLY COST (including bandwidth) should have the HIGHEST score.`;

  // Use AI Gateway if configured (bypasses regional restrictions like HKG)
  // AI Gateway URL format: https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_name}/openai
  const useAIGateway = !!env.AI_GATEWAY_URL;
  const apiEndpoint = useAIGateway
    ? `${env.AI_GATEWAY_URL}/chat/completions`
    : 'https://api.openai.com/v1/chat/completions';

  console.log(`[AI] Sending request to ${useAIGateway ? 'AI Gateway → ' : ''}OpenAI GPT-4o-mini`);
  if (useAIGateway) {
    console.log('[AI] Using Cloudflare AI Gateway to bypass regional restrictions');
  }

  // Create AbortController with 30 second timeout
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 30000);

  try {
    const openaiResponse = await fetch(apiEndpoint, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model: 'gpt-4o-mini',
        messages: [
          { role: 'system', content: systemPrompt },
          { role: 'user', content: userPrompt },
        ],
        max_tokens: 2000,
        temperature: 0.3,
      }),
      signal: controller.signal,
    });

    clearTimeout(timeoutId);

    if (!openaiResponse.ok) {
      const errorText = await openaiResponse.text();

      // Parse error details for better debugging
      let errorDetails = '';
      try {
        const errorObj = JSON.parse(errorText);
        errorDetails = errorObj?.error?.message || errorObj?.error?.type || '';
      } catch {
        errorDetails = errorText.slice(0, 200);
      }

      // Sanitize API keys from error messages
      const sanitized = errorDetails.replace(/sk-[a-zA-Z0-9-_]+/g, 'sk-***');

      // Enhanced logging for specific error codes
      if (openaiResponse.status === 403) {
        const isRegionalBlock = errorDetails.includes('Country') || errorDetails.includes('region') || errorDetails.includes('territory');
        if (isRegionalBlock && !useAIGateway) {
          console.error('[AI] ❌ REGIONAL BLOCK (403) - OpenAI blocked this region');
          console.error('[AI]   Worker is running in a blocked region (e.g., HKG)');
          console.error('[AI]   FIX: Set AI_GATEWAY_URL secret to use Cloudflare AI Gateway');
          console.error('[AI]   1. Create AI Gateway: https://dash.cloudflare.com → AI → AI Gateway');
          console.error('[AI]   2. Run: wrangler secret put AI_GATEWAY_URL');
          console.error('[AI]   3. Enter: https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_name}/openai');
        } else {
          console.error('[AI] ❌ AUTH FAILED (403) - Possible causes:');
          console.error('[AI]   1. Invalid or expired OPENAI_API_KEY');
          console.error('[AI]   2. API key not properly set in Cloudflare secrets');
          console.error('[AI]   3. Account billing issue or quota exceeded');
        }
        console.error('[AI] Error details:', sanitized);
      } else if (openaiResponse.status === 429) {
        console.error('[AI] ⚠️ RATE LIMITED (429) - Too many requests');
        console.error('[AI] Error details:', sanitized);
      } else if (openaiResponse.status === 401) {
        console.error('[AI] ❌ UNAUTHORIZED (401) - API key invalid');
        console.error('[AI] Error details:', sanitized);
      } else {
        console.error('[AI] OpenAI API error:', openaiResponse.status, sanitized);
      }

      throw new Error(`OpenAI API error: ${openaiResponse.status}`);
    }

    const openaiResult = await openaiResponse.json() as {
      choices: Array<{ message: { content: string } }>;
    };

    const response = openaiResult.choices[0]?.message?.content || '';

    console.log('[AI] Response received from OpenAI, length:', response.length);
    console.log('[AI] Raw response preview:', response.substring(0, 500));

    // Parse AI response
    const aiResult = parseAIResponse(response);
    console.log('[AI] Parsed recommendations count:', aiResult.recommendations.length);

    // Pre-index VPS benchmarks by provider for O(1) lookups
    const vpsByProvider = new Map<string, VPSBenchmark[]>();
    for (const vps of vpsBenchmarks) {
      const providerKey = vps.provider_name.toLowerCase();
      const existing = vpsByProvider.get(providerKey) || [];
      existing.push(vps);
      vpsByProvider.set(providerKey, existing);
    }

    // Map AI recommendations to full results
    const results: RecommendationResult[] = [];

    for (const aiRec of aiResult.recommendations) {
      // Handle both string and number server_id from AI
      const serverId = Number(aiRec.server_id);
      const server = candidates.find((s) => s.id === serverId);
      if (!server) {
        console.warn('[AI] Server not found:', aiRec.server_id);
        continue;
      }

      // Get benchmark reference for this server's CPU count
      const benchmarkRef = getBenchmarkReference(benchmarkData, server.vcpu);

      // Find matching VPS benchmark using pre-indexed data
      const providerName = server.provider_name.toLowerCase();
      let matchingVPS: VPSBenchmark | undefined;

      // Try to find from indexed provider benchmarks
      for (const [providerKey, benchmarks] of vpsByProvider.entries()) {
        if (providerKey.includes(providerName) || providerName.includes(providerKey)) {
          // First try exact or close vCPU match
          matchingVPS = benchmarks.find(
            (v) => v.vcpu === server.vcpu || (v.vcpu >= server.vcpu - 1 && v.vcpu <= server.vcpu + 1)
          );
          // Fallback to any from this provider
          if (!matchingVPS && benchmarks.length > 0) {
            matchingVPS = benchmarks[0];
          }
          if (matchingVPS) break;
        }
      }

      // Final fallback: similar specs from any provider
      if (!matchingVPS) {
        matchingVPS = vpsBenchmarks.find(
          (v) => v.vcpu === server.vcpu || (v.vcpu >= server.vcpu - 1 && v.vcpu <= server.vcpu + 1)
        );
      }

      // Calculate bandwidth info for this server
      const bandwidthInfo = calculateBandwidthInfo(server, bandwidthEstimate);

      results.push({
        server: server,
        score: aiRec.score,
        analysis: aiRec.analysis,
        estimated_capacity: aiRec.estimated_capacity,
        bandwidth_info: bandwidthInfo,
        benchmark_reference: benchmarkRef,
        vps_benchmark_reference: matchingVPS
          ? {
              plan_name: matchingVPS.plan_name,
              geekbench_single: matchingVPS.geekbench_single,
              geekbench_multi: matchingVPS.geekbench_multi,
              monthly_price_usd: matchingVPS.monthly_price_usd,
              performance_per_dollar: matchingVPS.performance_per_dollar,
            }
          : undefined,
      });

      if (results.length >= 3) break;
    }

    return {
      recommendations: results,
      infrastructure_tips: aiResult.infrastructure_tips,
    };
  } catch (error) {
    clearTimeout(timeoutId);
    // Handle timeout specifically
    if (error instanceof Error && error.name === 'AbortError') {
      console.error('[AI] Request timed out after 30 seconds');
      throw new Error('AI request timed out - please try again');
    }
    console.error('[AI] Error:', error);
    throw new Error(`AI processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
  }
}

/**
 * Parse AI response and extract JSON
 */
function parseAIResponse(response: unknown): AIRecommendationResponse {
  try {
    // Handle different response formats
    let content: string;

    if (typeof response === 'string') {
      content = response;
    } else if (typeof response === 'object' && response !== null) {
      // Type guard for response object with different structures
      const resp = response as Record<string, unknown>;

      if (typeof resp.response === 'string') {
        content = resp.response;
      } else if (typeof resp.result === 'object' && resp.result !== null) {
        const result = resp.result as Record<string, unknown>;
        if (typeof result.response === 'string') {
          content = result.response;
        } else {
          throw new Error('Unexpected AI response format');
        }
      } else if (Array.isArray(resp.choices) && resp.choices.length > 0) {
        const choice = resp.choices[0] as Record<string, unknown>;
        const message = choice?.message as Record<string, unknown>;
        if (typeof message?.content === 'string') {
          content = message.content;
        } else {
          throw new Error('Unexpected AI response format');
        }
      } else {
        console.error('[AI] Unexpected response format:', response);
        throw new Error('Unexpected AI response format');
      }
    } else {
      console.error('[AI] Unexpected response format:', response);
      throw new Error('Unexpected AI response format');
    }

    // Remove markdown code blocks if present
    content = content.replace(/```json\s*/g, '').replace(/```\s*/g, '').trim();

    // Find JSON object in response
    const jsonMatch = content.match(/\{[\s\S]*\}/);
    if (!jsonMatch) {
      throw new Error('No JSON found in AI response');
    }

    const parsed = JSON.parse(jsonMatch[0]);

    if (!parsed.recommendations || !Array.isArray(parsed.recommendations)) {
      throw new Error('Invalid recommendations structure');
    }

    // Validate each recommendation with type guard
    const validRecommendations = parsed.recommendations.filter(isValidAIRecommendation);
    if (validRecommendations.length === 0 && parsed.recommendations.length > 0) {
      console.warn('[AI] All recommendations failed validation, raw:', JSON.stringify(parsed.recommendations[0]).slice(0, 200));
      throw new Error('AI recommendations failed validation');
    }

    return {
      recommendations: validRecommendations,
      infrastructure_tips: Array.isArray(parsed.infrastructure_tips) ? parsed.infrastructure_tips : [],
    } as AIRecommendationResponse;
  } catch (error) {
    console.error('[AI] Parse error:', error);
    console.error('[AI] Response was:', response);
    throw new Error(`Failed to parse AI response: ${error instanceof Error ? error.message : 'Unknown error'}`);
  }
}
