import express from 'express';
const router = express.Router();

const MAX_MATCHES = 500;
const DESCRIPTION_SAMPLE_LIMIT = 8;

// GET /api/spec-lookup?company=...&term=...
// Returns aggregated specs across products matching company (brand) and term (title).
router.get('/', async (req, res) => {
  const company = typeof req.query.company === 'string' ? req.query.company.trim() : '';
  const term = typeof req.query.term === 'string' ? req.query.term.trim() : '';

  if (!company && !term) {
    return res.status(400).json({ error: 'company or term is required' });
  }

  try {
    const pool = req.app.locals.pool;
    const conditions = [];
    const params = [];

    if (company) {
      params.push(`%${company}%`);
      conditions.push(`brand ILIKE $${params.length}`);
    }
    if (term) {
      params.push(`%${term}%`);
      conditions.push(`title ILIKE $${params.length}`);
    }

    params.push(MAX_MATCHES);
    const limitParam = `$${params.length}`;

    const sql = `
      SELECT
        pid::TEXT AS pid,
        title, sku, brand, vendor, artist,
        country_of_origin, harmonized_tariff_code,
        description, categories,
        cost_price, regular_price,
        moq, weight, length, width, height,
        created_at
      FROM products
      WHERE ${conditions.join(' AND ')}
      ORDER BY created_at DESC NULLS LAST
      LIMIT ${limitParam}
    `;

    const { rows } = await pool.query(sql, params);

    // Resolve category cat_ids → names. products.categories is a comma-separated cat_id string.
    const catIds = new Set();
    for (const r of rows) {
      if (!r.categories) continue;
      for (const tok of String(r.categories).split(',')) {
        const trimmed = tok.trim();
        if (trimmed && /^\d+$/.test(trimmed)) catIds.add(trimmed);
      }
    }
    // Map cat_id → {name, type}. Types 10-13 are Section/Category/Subcategory/Sub-Subcategory; 20-21 are Theme/Subtheme.
    const catIdToInfo = new Map();
    if (catIds.size > 0) {
      const { rows: catRows } = await pool.query(
        `SELECT cat_id::TEXT AS cat_id, name, type FROM categories WHERE cat_id = ANY($1::bigint[])`,
        [Array.from(catIds)],
      );
      for (const c of catRows) catIdToInfo.set(c.cat_id, { name: c.name, type: Number(c.type) });
    }

    const products = rows.map(r => ({
      pid: Number(r.pid),
      title: r.title,
      sku: r.sku,
      brand: r.brand,
      vendor: r.vendor,
      artist: r.artist,
      country_of_origin: r.country_of_origin,
      harmonized_tariff_code: r.harmonized_tariff_code,
      description: r.description,
      categories: r.categories,
      cost_price: toNumberOrNull(r.cost_price),
      regular_price: toNumberOrNull(r.regular_price),
      moq: toNumberOrNull(r.moq),
      weight: toNumberOrNull(r.weight),
      length: toNumberOrNull(r.length),
      width: toNumberOrNull(r.width),
      height: toNumberOrNull(r.height),
      created_at: r.created_at,
    }));

    res.json({
      company,
      term,
      total: products.length,
      truncated: products.length === MAX_MATCHES,
      products,
      aggregates: {
        numeric: {
          cost_price: numericAggregate(products, 'cost_price'),
          regular_price: numericAggregate(products, 'regular_price'),
          moq: numericAggregate(products, 'moq'),
          weight: numericAggregate(products, 'weight'),
          length: numericAggregate(products, 'length'),
          width: numericAggregate(products, 'width'),
          height: numericAggregate(products, 'height'),
        },
        categorical: {
          artist: categoricalAggregate(products, 'artist'),
          country_of_origin: categoricalAggregate(products, 'country_of_origin'),
          harmonized_tariff_code: categoricalAggregate(products, 'harmonized_tariff_code'),
        },
        categories: groupedAggregate(products, catIdToInfo, new Set([10, 11, 12, 13])),
        themes: groupedAggregate(products, catIdToInfo, new Set([20, 21])),
        description: descriptionAggregate(products),
      },
    });
  } catch (error) {
    console.error('Error in spec-lookup:', error);
    res.status(500).json({ error: 'Failed to compute spec lookup' });
  }
});

function toNumberOrNull(v) {
  if (v === null || v === undefined) return null;
  const n = Number(v);
  return Number.isFinite(n) ? n : null;
}

// Aggregate a numeric field. Treats null/0 as unset since 0 is the codebase's "no value" sentinel.
// `products` is assumed to be ordered most-recent-first (created_at DESC) so the head of the
// list is also the recency window we use for trend detection.
function numericAggregate(products, field) {
  const values = [];
  // Iterate products in order so we know which values came from the most-recent rows.
  for (const p of products) {
    const v = p[field];
    if (typeof v === 'number' && Number.isFinite(v) && v > 0) values.push(v);
  }

  if (!values.length) {
    return { count: 0, sample_size: products.length, distribution: [] };
  }

  const sorted = [...values].sort((a, b) => a - b);
  const sum = values.reduce((s, v) => s + v, 0);
  const avg = sum / values.length;
  const mid = Math.floor(sorted.length / 2);
  const median = sorted.length % 2 === 0
    ? (sorted[mid - 1] + sorted[mid]) / 2
    : sorted[mid];
  const variance = values.reduce((s, v) => s + (v - avg) ** 2, 0) / values.length;
  const stddev = Math.sqrt(variance);

  const counts = new Map();
  for (const v of values) {
    const key = roundForKey(v);
    counts.set(key, (counts.get(key) || 0) + 1);
  }
  const distribution = Array.from(counts.entries())
    .map(([value, count]) => ({ value, count }))
    .sort((a, b) => b.count - a.count || a.value - b.value);

  const mode = distribution[0]?.value ?? null;
  const mode_count = distribution[0]?.count ?? 0;

  // Trend detection: scan only the most-recent N values. N adapts to sample size so this
  // can never look at more than ~20% of the data when the sample is small.
  const recentN = Math.min(20, Math.max(5, Math.floor(values.length / 4)));
  const recentValues = values.slice(0, recentN);
  let recent_mode = null;
  let recent_mode_count = 0;
  let trending = false;
  if (recentValues.length >= 3) {
    const recentCounts = new Map();
    for (const v of recentValues) {
      const key = roundForKey(v);
      recentCounts.set(key, (recentCounts.get(key) || 0) + 1);
    }
    const recentSorted = Array.from(recentCounts.entries()).sort((a, b) => b[1] - a[1] || a[0] - b[0]);
    recent_mode = recentSorted[0][0];
    recent_mode_count = recentSorted[0][1];
    // Trend = recent mode differs from overall AND dominates the window AND has min absolute support.
    const majority = recent_mode_count >= Math.ceil(recentValues.length * 0.6);
    const minSupport = recent_mode_count >= 3;
    trending = recent_mode !== mode && majority && minSupport;
  }

  return {
    count: values.length,
    sample_size: products.length,
    avg,
    median,
    min: sorted[0],
    max: sorted[sorted.length - 1],
    stddev,
    mode,
    mode_count,
    recent_mode,
    recent_mode_count,
    recent_window: recentValues.length,
    trending,
    distribution,
  };
}

// Round to 4 decimals so JS-FP noise doesn't fragment the histogram.
function roundForKey(v) {
  return Math.round(v * 10000) / 10000;
}

function categoricalAggregate(products, field) {
  const counts = new Map();
  for (const p of products) {
    const v = p[field];
    if (v === null || v === undefined) continue;
    const key = String(v).trim();
    if (!key) continue;
    counts.set(key, (counts.get(key) || 0) + 1);
  }
  return Array.from(counts.entries())
    .map(([value, count]) => ({ value, count }))
    .sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
}

// Aggregate cat_id token counts, including only entries whose category type is in `acceptedTypes`.
function groupedAggregate(products, catIdToInfo, acceptedTypes) {
  const counts = new Map();
  for (const p of products) {
    if (!p.categories) continue;
    const tokens = String(p.categories).split(',').map(t => t.trim()).filter(Boolean);
    for (const t of tokens) {
      const info = catIdToInfo.get(t);
      if (!info || !acceptedTypes.has(info.type)) continue;
      counts.set(info.name, (counts.get(info.name) || 0) + 1);
    }
  }
  return Array.from(counts.entries())
    .map(([value, count]) => ({ value, count }))
    .sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
}

function descriptionAggregate(products) {
  const counts = new Map();
  for (const p of products) {
    if (!p.description) continue;
    const key = String(p.description).trim();
    if (!key) continue;
    counts.set(key, (counts.get(key) || 0) + 1);
  }

  const duplicates = Array.from(counts.entries())
    .filter(([, count]) => count > 1)
    .map(([value, count]) => ({ value, count }))
    .sort((a, b) => b.count - a.count);

  // Recent unique samples (products are already ordered by created_at DESC).
  const seen = new Set();
  const samples = [];
  for (const p of products) {
    const desc = (p.description || '').trim();
    if (!desc || seen.has(desc)) continue;
    seen.add(desc);
    samples.push({ value: desc, title: p.title, pid: p.pid, sku: p.sku });
    if (samples.length >= DESCRIPTION_SAMPLE_LIMIT) break;
  }

  return { duplicates, samples };
}

export default router;
