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

import type {
  Env,
  RecommendRequest,
  Server,
  BenchmarkData,
  VPSBenchmark,
  TechSpec,
  BandwidthEstimate,
  RecommendationResult,
  BenchmarkReference,
  AvailableRegion
} from '../types';
import { LIMITS } from '../config';
import {
  jsonResponse,
  validateRecommendRequest,
  generateCacheKey,
  estimateBandwidth,
  isValidBenchmarkData,
  isValidVPSBenchmark,
  isValidTechSpec,
  getExchangeRate
} from '../utils';
import { escapeLikePattern } from '../region-utils';
import { AnvilServerRepository } from '../repositories/AnvilServerRepository';
import {
  getAIRecommendations,
  generateRuleBasedRecommendations,
} from '../services/ai-service';

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
      );
    }

    // Parse JSON with explicit error handling
    let body: RecommendRequest;
    try {
      body = JSON.parse(bodyText) as RecommendRequest;
    } catch (parseError) {
      console.error('[Recommend] JSON parse error:', parseError instanceof Error ? parseError.message : 'Unknown');
      return jsonResponse({
        error: 'Invalid JSON format',
        request_id: requestId,
      }, 400, corsHeaders);
    }

    // Validate body is an object before proceeding
    if (!body || typeof body !== 'object' || Array.isArray(body)) {
      return jsonResponse({
        error: body && 'lang' in body && body.lang === 'ko' ? '요청 본문은 객체여야 합니다' : 'Request body must be an object',
        request_id: requestId,
      }, 400, corsHeaders);
    }

    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_budget: !!body.budget_limit,
      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) {
        try {
          const parsed = JSON.parse(cached);
          // Validate required fields exist
          if (parsed && Array.isArray(parsed.recommendations)) {
            console.log('[Recommend] Cache hit');
            return jsonResponse(
              { ...parsed, cached: true },
              200,
              corsHeaders
            );
          }
          console.warn('[Recommend] Invalid cached data structure, ignoring');
        } catch (parseError) {
          console.warn('[Recommend] Cache parse error, ignoring cached data');
        }
      }
    }

    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 (with CDN options)
    // cdn_enabled가 명시적으로 false면 비활성화, 그 외에는 기본 활성화
    const cdnOptions = {
      enabled: body.cdn_enabled !== false, // undefined면 true (기본값), false면 false
      cacheHitRate: body.cdn_cache_hit_rate
    };
    const bandwidthEstimate = estimateBandwidth(body.expected_users, body.use_case, body.traffic_pattern, cdnOptions);
    console.log(`[Recommend] Bandwidth estimate: Gross ${bandwidthEstimate.gross_monthly_tb}TB → Origin ${bandwidthEstimate.monthly_tb >= 1 ? bandwidthEstimate.monthly_tb + ' TB' : bandwidthEstimate.monthly_gb + ' GB'}/month (${bandwidthEstimate.category}, CDN: ${bandwidthEstimate.cdn_enabled ? `${(bandwidthEstimate.cdn_cache_hit_rate * 100).toFixed(0)}%` : 'disabled'})`);

    // 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: Parallel queries including exchange rate for Korean users
    const exchangeRatePromise = lang === 'ko' ? getExchangeRate(env) : Promise.resolve(1);

    // Use repository to fetch candidate servers
    const repository = new AnvilServerRepository(env.DB);

    const [candidates, vpsBenchmarks, exchangeRate] = await Promise.all([
      repository.findServers({
        minCpu: minVcpu,
        minMemoryGb: minMemoryMb ? minMemoryMb / 1024 : undefined,
        region: body.region_preference,
        budgetLimit: body.budget_limit,
        limit: LIMITS.MAX_AI_CANDIDATES * 3, // Fetch more to allow for filtering
      }),
      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[];
      }),
      exchangeRatePromise,
    ]);

    // Apply exchange rate to candidates if needed (Korean users)
    // 서버 가격: 500원 단위 반올림
    if (lang === 'ko' && exchangeRate !== 1) {
      candidates.forEach(c => {
        c.monthly_price = Math.round((c.monthly_price * exchangeRate) / 500) * 500;
        c.currency = 'KRW';
      });
    }

    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
      );
    }

    // Bandwidth-based filtering: prioritize servers with adequate transfer allowance
    // If estimated bandwidth exceeds typical 1TB allowance by 2x+, prefer higher-tier servers
    let filteredCandidates = candidates;
    let bandwidthWarning: string | undefined;

    const estimatedMonthlyTb = bandwidthEstimate.monthly_tb;
    const typicalIncludedTb = 1; // Most basic plans include 1TB

    if (estimatedMonthlyTb > typicalIncludedTb * 2) {
      // High bandwidth workload - prioritize servers with higher transfer allowance
      const highTransferServers = candidates.filter(c =>
        c.transfer_tb && c.transfer_tb >= estimatedMonthlyTb * 0.8
      );
      const mediumTransferServers = candidates.filter(c =>
        c.transfer_tb && c.transfer_tb >= estimatedMonthlyTb * 0.5
      );

      if (highTransferServers.length >= 3) {
        // Enough high-transfer servers - use those, but include some lower tiers for comparison
        filteredCandidates = [
          ...highTransferServers,
          ...candidates.filter(c => !highTransferServers.includes(c)).slice(0, 5)
        ];
        console.log(`[Recommend] Bandwidth filter: prioritized ${highTransferServers.length} high-transfer servers`);
      } else if (mediumTransferServers.length >= 3) {
        filteredCandidates = [
          ...mediumTransferServers,
          ...candidates.filter(c => !mediumTransferServers.includes(c)).slice(0, 5)
        ];
        console.log(`[Recommend] Bandwidth filter: prioritized ${mediumTransferServers.length} medium-transfer servers`);
      }

      // Add warning for high bandwidth workload
      const overageRatio = estimatedMonthlyTb / typicalIncludedTb;
      bandwidthWarning = lang === 'ko'
        ? `⚠️ CDN 적용 후에도 원본 서버 트래픽(${estimatedMonthlyTb.toFixed(1)}TB)이 기본 포함량(${typicalIncludedTb}TB)의 ${overageRatio.toFixed(1)}배입니다. 상위 플랜을 고려하세요.`
        : `⚠️ Even with CDN, origin traffic (${estimatedMonthlyTb.toFixed(1)}TB) is ${overageRatio.toFixed(1)}x the typical included bandwidth (${typicalIncludedTb}TB). Consider higher-tier plans.`;
      console.log(`[Recommend] Bandwidth warning: ${bandwidthWarning}`);
    }

    // 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)
    // If AI fails, fall back to rule-based recommendations
    let aiResult: { recommendations: RecommendationResult[]; infrastructure_tips?: string[] };
    try {
      aiResult = await getAIRecommendations(
        env,
        env.OPENAI_API_KEY,
        body,
        filteredCandidates, // Use bandwidth-filtered candidates
        benchmarkData,
        vpsBenchmarks,
        techSpecs,
        bandwidthEstimate,
        lang,
        exchangeRate
      );
      console.log('[Recommend] AI recommendations generated:', aiResult.recommendations.length);
    } catch (aiError) {
      console.warn('[Recommend] AI failed, using rule-based fallback:', aiError instanceof Error ? aiError.message : String(aiError));
      const fallbackRecommendations = generateRuleBasedRecommendations(
        filteredCandidates, // Use bandwidth-filtered candidates
        body,
        minVcpu || 1,
        minMemoryMb || 1024,
        bandwidthEstimate,
        lang,
        exchangeRate
      );
      aiResult = {
        recommendations: fallbackRecommendations,
        infrastructure_tips: lang === 'ko'
          ? [
              '⚠️ AI 추천 시스템이 일시적으로 사용 불가능하여 기본 규칙 기반 추천을 제공합니다.',
              '더 정확한 추천을 원하시면 잠시 후 다시 시도해주세요.',
            ]
          : [
              '⚠️ AI recommendation system is temporarily unavailable. Showing rule-based recommendations.',
              'For more accurate recommendations, please try again later.',
            ],
      };
      console.log('[Recommend] Rule-based fallback recommendations:', aiResult.recommendations.length);
    }

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

    // Add bandwidth warning to infrastructure_tips if applicable
    const allTips = [...(aiResult.infrastructure_tips || [])];
    if (bandwidthWarning) {
      allTips.unshift(bandwidthWarning); // Add at the beginning for visibility
    }

    const response = {
      recommendations: aiResult.recommendations,
      infrastructure_tips: allTips,
      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`,
        // CDN breakdown
        cdn_enabled: bandwidthEstimate.cdn_enabled,
        cdn_cache_hit_rate: bandwidthEstimate.cdn_cache_hit_rate,
        gross_monthly_tb: bandwidthEstimate.gross_monthly_tb,
        origin_monthly_tb: bandwidthEstimate.origin_monthly_tb,
      },
      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
    );
  }
}
// queryCandidateServers function removed - now using AnvilServerRepository

/**
 * 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);
}


/**
 * 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
 */
