import express from 'express';
import { formatDateCol } from '../../shared/business-time/index.js';
import { PurchaseOrderStatus, ReceivingStatus } from '../types/status-codes.js';

const router = express.Router();

// Get unique brands
router.get('/brands', async (req, res) => {
    console.log('Brands endpoint hit:', {
        url: req.url,
        method: req.method,
        headers: req.headers,
        path: req.path
    });
    
    try {
        const pool = req.app.locals.pool;
        console.log('Fetching brands from database...');
        
        const { rows } = await pool.query(`
            SELECT DISTINCT COALESCE(p.brand, 'Unbranded') as brand
            FROM products p
            WHERE p.visible = true
            ORDER BY COALESCE(p.brand, 'Unbranded')
        `);
        
        console.log(`Found ${rows.length} brands:`, rows.slice(0, 3));
        res.json(rows.map(r => r.brand));
    } catch (error) {
        console.error('Error fetching brands:', error);
        res.status(500).json({ error: 'Failed to fetch brands' });
    }
});

// Get all products with pagination, filtering, and sorting
// Whitelist of allowed sort keys → SQL column expressions. Used to gate
// `?sort=` against direct interpolation into ORDER BY (the previous code
// dropped req.query.sort straight into the query string — SQL injection sink).
// Keys are the camelCase identifiers the frontend ProductMetricColumnKey union
// emits. Anything not in the map falls back to `p.title`.
const SORT_COLUMN_MAP = {
  pid: 'p.pid',
  title: 'p.title',
  sku: 'p.sku',
  barcode: 'p.barcode',
  brand: 'p.brand',
  line: 'p.line',
  subline: 'p.subline',
  artist: 'p.artist',
  vendor: 'p.vendor',
  vendorReference: 'p.vendor_reference',
  notionsReference: 'p.notions_reference',
  harmonizedTariffCode: 'p.harmonized_tariff_code',
  countryOfOrigin: 'p.country_of_origin',
  location: 'p.location',
  moq: 'p.moq',
  weight: 'p.weight',
  rating: 'p.rating',
  reviews: 'p.reviews',
  baskets: 'p.baskets',
  notifies: 'p.notifies',
  preorderCount: 'p.preorder_count',
  notionsInvCount: 'p.notions_inv_count',
  dateCreated: 'p.created_at',
  dateLastSold: 'p.date_last_sold',
  stock: 'p.stock_quantity',
  stockQuantity: 'p.stock_quantity',
  price: 'p.price',
  costPrice: 'p.cost_price',
  totalSold: 'p.total_sold',
  // product_metrics columns (current schema names; camelCase aliases the
  // frontend uses are mapped to the canonical SQL column)
  dailySalesAvg: 'pm.avg_sales_per_day_30d',
  weeklySalesAvg: 'pm.sales_7d',
  monthlySalesAvg: 'pm.avg_sales_per_month_30d',
  margin: 'pm.margin_30d',
  gmroi: 'pm.gmroi_30d',
  gmroi30d: 'pm.gmroi_30d',
  inventoryValue: 'pm.current_stock_cost',
  costOfGoodsSold: 'pm.cogs_30d',
  grossProfit: 'pm.profit_30d',
  turnoverRate: 'pm.stockturn_30d',
  stockturn30d: 'pm.stockturn_30d',
  leadTime: 'pm.config_lead_time',
  currentLeadTime: 'pm.config_lead_time',
  targetLeadTime: 'pm.config_lead_time',
  stockCoverage: 'pm.stock_cover_in_days',
  daysOfStock: 'pm.stock_cover_in_days',
  reorderPoint: 'pm.replenishment_units',
  safetyStock: 'pm.config_safety_stock',
  abcClass: 'pm.abc_class',
  status: 'pm.status',
  ageDays: 'pm.age_days',
  sales7d: 'pm.sales_7d',
  sales30d: 'pm.sales_30d',
  sales365d: 'pm.sales_365d',
  revenue7d: 'pm.revenue_7d',
  revenue30d: 'pm.revenue_30d',
  revenue365d: 'pm.revenue_365d',
  sellThrough30d: 'pm.sell_through_30d',
  salesGrowth30dVsPrev: 'pm.sales_growth_30d_vs_prev',
};

router.get('/', async (req, res) => {
  const pool = req.app.locals.pool;
  try {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 50;
    const offset = (page - 1) * limit;
    const sortColumn = SORT_COLUMN_MAP[req.query.sort] || 'p.title';
    const sortDirection = req.query.order === 'desc' ? 'DESC' : 'ASC';

    const conditions = ['p.visible = true'];
    const params = [];
    let paramCounter = 1;

    // Add default replenishable filter unless explicitly showing non-replenishable
    if (req.query.showNonReplenishable !== 'true') {
      conditions.push('p.replenishable = true');
    }

    // Handle search filter
    if (req.query.search) {
      conditions.push(`(p.title ILIKE $${paramCounter} OR p.SKU ILIKE $${paramCounter} OR p.barcode ILIKE $${paramCounter})`);
      const searchTerm = `%${req.query.search}%`;
      params.push(searchTerm);
      paramCounter++;
    }

    // Handle text filters for specific fields
    if (req.query.barcode) {
      conditions.push(`p.barcode ILIKE $${paramCounter}`);
      params.push(`%${req.query.barcode}%`);
      paramCounter++;
    }

    if (req.query.vendor_reference) {
      conditions.push(`p.vendor_reference ILIKE $${paramCounter}`);
      params.push(`%${req.query.vendor_reference}%`);
      paramCounter++;
    }

    // Add new text filters for the additional fields
    if (req.query.description) {
      conditions.push(`p.description ILIKE $${paramCounter}`);
      params.push(`%${req.query.description}%`);
      paramCounter++;
    }

    if (req.query.harmonized_tariff_code) {
      conditions.push(`p.harmonized_tariff_code ILIKE $${paramCounter}`);
      params.push(`%${req.query.harmonized_tariff_code}%`);
      paramCounter++;
    }

    if (req.query.notions_reference) {
      conditions.push(`p.notions_reference ILIKE $${paramCounter}`);
      params.push(`%${req.query.notions_reference}%`);
      paramCounter++;
    }

    if (req.query.line) {
      conditions.push(`p.line ILIKE $${paramCounter}`);
      params.push(`%${req.query.line}%`);
      paramCounter++;
    }

    if (req.query.subline) {
      conditions.push(`p.subline ILIKE $${paramCounter}`);
      params.push(`%${req.query.subline}%`);
      paramCounter++;
    }

    if (req.query.artist) {
      conditions.push(`p.artist ILIKE $${paramCounter}`);
      params.push(`%${req.query.artist}%`);
      paramCounter++;
    }

    if (req.query.country_of_origin) {
      conditions.push(`p.country_of_origin ILIKE $${paramCounter}`);
      params.push(`%${req.query.country_of_origin}%`);
      paramCounter++;
    }

    if (req.query.location) {
      conditions.push(`p.location ILIKE $${paramCounter}`);
      params.push(`%${req.query.location}%`);
      paramCounter++;
    }

    // Handle numeric filters with operators. Mapped to current product_metrics
    // column names; frontend keys (camelCase) preserved for compatibility.
    const numericFields = {
      stock: 'p.stock_quantity',
      price: 'p.price',
      costPrice: 'p.cost_price',
      dailySalesAvg: 'pm.avg_sales_per_day_30d',
      weeklySalesAvg: 'pm.sales_7d',
      monthlySalesAvg: 'pm.avg_sales_per_month_30d',
      margin: 'pm.margin_30d',
      gmroi: 'pm.gmroi_30d',
      inventoryValue: 'pm.current_stock_cost',
      costOfGoodsSold: 'pm.cogs_30d',
      grossProfit: 'pm.profit_30d',
      turnoverRate: 'pm.stockturn_30d',
      leadTime: 'pm.config_lead_time',
      currentLeadTime: 'pm.config_lead_time',
      targetLeadTime: 'pm.config_lead_time',
      stockCoverage: 'pm.stock_cover_in_days',
      daysOfStock: 'pm.stock_cover_in_days',
      reorderPoint: 'pm.replenishment_units',
      safetyStock: 'pm.config_safety_stock',
      // Add new numeric fields
      preorderCount: 'p.preorder_count',
      notionsInvCount: 'p.notions_inv_count',
      rating: 'p.rating',
      reviews: 'p.reviews',
      weight: 'p.weight',
      totalSold: 'p.total_sold',
      baskets: 'p.baskets',
      notifies: 'p.notifies'
    };

    Object.entries(req.query).forEach(([key, value]) => {
      const field = numericFields[key];
      if (field) {
        const operator = req.query[`${key}_operator`] || '=';
        if (operator === 'between') {
          try {
            const [min, max] = JSON.parse(value);
            conditions.push(`${field} BETWEEN $${paramCounter} AND $${paramCounter + 1}`);
            params.push(min, max);
            paramCounter += 2;
          } catch (e) {
            console.error(`Invalid between value for ${key}:`, value);
          }
        } else {
          conditions.push(`${field} ${operator} $${paramCounter}`);
          params.push(parseFloat(value));
          paramCounter++;
        }
      }
    });

    // Handle date filters
    const dateFields = {
      firstSaleDate: 'pm.first_sale_date',
      lastSaleDate: 'pm.last_sale_date',
      lastPurchaseDate: 'pm.last_purchase_date',
      firstReceivedDate: 'pm.first_received_date',
      lastReceivedDate: 'pm.last_received_date'
    };

    Object.entries(req.query).forEach(([key, value]) => {
      const field = dateFields[key];
      if (field) {
        conditions.push(`${field}::TEXT LIKE $${paramCounter}`);
        params.push(`${value}%`); // Format like '2023-01%' to match by month or '2023-01-01' for exact date
        paramCounter++;
      }
    });

    // Handle select filters
    if (req.query.vendor) {
      conditions.push(`p.vendor = $${paramCounter}`);
      params.push(req.query.vendor);
      paramCounter++;
    }

    if (req.query.brand) {
      conditions.push(`p.brand = $${paramCounter}`);
      params.push(req.query.brand);
      paramCounter++;
    }

    if (req.query.category) {
      conditions.push(`p.categories ILIKE $${paramCounter}`);
      params.push(`%${req.query.category}%`);
      paramCounter++;
    }

    if (req.query.stockStatus && req.query.stockStatus !== 'all') {
      conditions.push(`pm.stock_status = $${paramCounter}`);
      params.push(req.query.stockStatus);
      paramCounter++;
    }

    if (req.query.abcClass) {
      conditions.push(`pm.abc_class = $${paramCounter}`);
      params.push(req.query.abcClass);
      paramCounter++;
    }

    if (req.query.leadTimeStatus) {
      conditions.push(`pm.lead_time_status = $${paramCounter}`);
      params.push(req.query.leadTimeStatus);
      paramCounter++;
    }

    if (req.query.replenishable !== undefined) {
      conditions.push(`p.replenishable = $${paramCounter}`);
      params.push(req.query.replenishable === 'true');
      paramCounter++;
    }

    if (req.query.managingStock !== undefined) {
      conditions.push(`p.managing_stock = $${paramCounter}`);
      params.push(req.query.managingStock === 'true');
      paramCounter++;
    }

    // Combine all conditions with AND
    const whereClause = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';

    // Get total count for pagination
    const countQuery = `
      SELECT COUNT(DISTINCT p.pid) as total 
      FROM products p
      LEFT JOIN product_metrics pm ON p.pid = pm.pid
      ${whereClause}
    `;
    const { rows: [countResult] } = await pool.query(countQuery, params);
    const total = countResult.total;

    // Get available filters
    const { rows: categories } = await pool.query(
      'SELECT name FROM categories ORDER BY name'
    );
    const { rows: vendors } = await pool.query(
      'SELECT DISTINCT vendor FROM products WHERE visible = true AND vendor IS NOT NULL AND vendor != \'\' ORDER BY vendor'
    );
    const { rows: brands } = await pool.query(
      'SELECT DISTINCT COALESCE(brand, \'Unbranded\') as brand FROM products WHERE visible = true ORDER BY brand'
    );

    // Main query with all fields. Aliases new product_metrics column names back to
    // the legacy names the frontend ProductRow type still uses — same pattern as the
    // /:id detail route below.
    const query = `
      SELECT
        p.*,
        COALESCE(p.brand, 'Unbranded') as brand,
        string_agg(DISTINCT (c.cat_id || ':' || c.name), ',') as categories,
        pm.avg_sales_per_day_30d AS daily_sales_avg,
        pm.sales_7d AS weekly_sales_avg,
        pm.avg_sales_per_month_30d AS monthly_sales_avg,
        pm.date_first_sold AS first_sale_date,
        pm.date_last_sold AS last_sale_date,
        pm.stock_cover_in_days AS days_of_inventory,
        pm.replenishment_units AS reorder_point,
        pm.config_safety_stock AS safety_stock,
        pm.margin_30d AS avg_margin_percent,
        CAST(pm.lifetime_revenue AS DECIMAL(15,3)) as total_revenue,
        CAST(pm.current_stock_cost AS DECIMAL(15,3)) as inventory_value,
        CAST(pm.cogs_30d AS DECIMAL(15,3)) as cost_of_goods_sold,
        CAST(pm.profit_30d AS DECIMAL(15,3)) as gross_profit,
        pm.gmroi_30d AS gmroi,
        pm.avg_lead_time_days,
        pm.date_last_received AS last_received_date,
        pm.abc_class,
        pm.status AS stock_status,
        pm.stockturn_30d AS turnover_rate,
        p.date_last_sold
      FROM products p
      LEFT JOIN product_metrics pm ON p.pid = pm.pid
      LEFT JOIN product_categories pc ON p.pid = pc.pid
      LEFT JOIN categories c ON pc.cat_id = c.cat_id
      ${whereClause}
      GROUP BY p.pid, pm.pid
      ORDER BY ${sortColumn} ${sortDirection}
      LIMIT $${paramCounter} OFFSET $${paramCounter + 1}
    `;
    
    params.push(limit, offset);
    const { rows: products } = await pool.query(query, params);

    res.json({
      products,
      pagination: {
        total,
        pages: Math.ceil(total / limit),
        currentPage: page,
        limit
      },
      filters: {
        categories: categories.map(c => c.name),
        vendors: vendors.map(v => v.vendor),
        brands: brands.map(b => b.brand)
      }
    });
  } catch (error) {
    console.error('Error fetching products:', error);
    res.status(500).json({ error: 'Failed to fetch products' });
  }
});

// Get trending products
router.get('/trending', async (req, res) => {
  const pool = req.app.locals.pool;
  try {
    // First check if we have any data
    const { rows } = await pool.query(`
      SELECT COUNT(*) as count,
             MAX(lifetime_revenue) as max_revenue,
             MAX(avg_sales_per_day_30d) as max_daily_sales,
             COUNT(DISTINCT pid) as products_with_metrics
      FROM product_metrics
      WHERE lifetime_revenue > 0 OR avg_sales_per_day_30d > 0
    `);
    console.log('Product metrics stats:', rows[0]);

    if (parseInt(rows[0].count) === 0) {
      console.log('No products with metrics found');
      return res.json([]);
    }

    // Get trending products. growth_rate uses sales_growth_30d_vs_prev — a
    // pre-computed % delta of the last 30d window vs the prior 30d window.
    // (The old formula compared a per-day rate against a 7-day total, which
    // mixed units and produced nonsense after the metrics-schema rename.)
    const { rows: trendingProducts } = await pool.query(`
      SELECT
        p.pid,
        p.sku,
        p.title,
        COALESCE(pm.avg_sales_per_day_30d, 0) as daily_sales_avg,
        COALESCE(pm.sales_7d, 0) as weekly_sales_avg,
        COALESCE(pm.sales_growth_30d_vs_prev, 0) as growth_rate,
        COALESCE(pm.lifetime_revenue, 0) as total_revenue
      FROM products p
      INNER JOIN product_metrics pm ON p.pid = pm.pid
      WHERE (pm.lifetime_revenue > 0 OR pm.avg_sales_per_day_30d > 0)
        AND p.visible = true
      ORDER BY growth_rate DESC NULLS LAST
      LIMIT 50
    `);

    console.log('Trending products:', trendingProducts);
    res.json(trendingProducts);
  } catch (error) {
    console.error('Error fetching trending products:', error);
    res.status(500).json({ error: 'Failed to fetch trending products' });
  }
});

// Quick product search for the product editor (uses local Postgres)
router.get('/search', async (req, res) => {
  const pool = req.app.locals.pool;
  const q = req.query.q;
  if (!q || !q.trim()) {
    return res.status(400).json({ error: 'Search query is required' });
  }

  try {
    const terms = q.trim().split(/\s+/).filter(Boolean);
    // Each term must match at least one of: title, sku, barcode, brand, vendor, vendor_reference, notions_reference, line, subline, artist
    const conditions = terms.map((_, i) => {
      const p = i * 10;
      return `(p.title ILIKE $${p + 1} OR p.sku ILIKE $${p + 2} OR p.barcode ILIKE $${p + 3} OR p.brand ILIKE $${p + 4} OR p.vendor ILIKE $${p + 5} OR p.vendor_reference ILIKE $${p + 6} OR p.notions_reference ILIKE $${p + 7} OR p.line ILIKE $${p + 8} OR p.subline ILIKE $${p + 9} OR p.artist ILIKE $${p + 10})`;
    });
    const params = terms.flatMap(t => {
      const like = `%${t}%`;
      return [like, like, like, like, like, like, like, like, like, like];
    });

    const whereClause = conditions.join(' AND ');
    const searchParams = [...params, `%${q.trim()}%`];

    const [{ rows }, { rows: countRows }] = await Promise.all([
      pool.query(`
        SELECT pid, title, sku, barcode, brand, line, regular_price, image_175
        FROM products p
        WHERE ${whereClause}
        ORDER BY
          CASE WHEN p.sku ILIKE $${params.length + 1} THEN 0
               WHEN p.barcode ILIKE $${params.length + 1} THEN 1
               WHEN p.title ILIKE $${params.length + 1} THEN 2
               ELSE 3
          END,
          p.total_sold DESC NULLS LAST
        LIMIT 100
      `, searchParams),
      pool.query(`
        SELECT COUNT(*)::int AS total
        FROM products p
        WHERE ${whereClause}
      `, params),
    ]);

    res.json({ results: rows, total: countRows[0].total });
  } catch (error) {
    console.error('Error searching products:', error);
    res.status(500).json({ error: 'Search failed' });
  }
});

// Batch lookup of product display data by pid list (used by Create PO page)
// Accepts ?pids=1,2,3 — comma-separated; de-duped server-side; capped at 500.
// Optional ?supplierId=<n> — when present, current_cost_price is computed via
// the same fallback chain the legacy PHP backend uses in clsPO::_product_add()
// (po.class.php:189-209):
//     supplier_id == 92 (Notions) → products.notions_cost_each
//     else / fallback              → products.supplier_cost_each
//     final fallback               → most recent receivings.cost_each (>0)
// Returns rows in the same order as the deduped input pids; missing pids are silently dropped.
const NOTIONS_SUPPLIER_ID = 92;

router.get('/batch', async (req, res) => {
  const pool = req.app.locals.pool;
  const raw = req.query.pids;
  if (!raw || typeof raw !== 'string') {
    return res.status(400).json({ error: 'pids query parameter is required' });
  }

  const pids = Array.from(new Set(
    raw.split(',')
      .map(s => parseInt(s.trim(), 10))
      .filter(n => Number.isInteger(n) && n > 0)
  )).slice(0, 500);

  if (pids.length === 0) {
    return res.status(400).json({ error: 'No valid pids provided' });
  }

  const supplierIdRaw = req.query.supplierId;
  const supplierId = supplierIdRaw != null && /^\d+$/.test(String(supplierIdRaw))
    ? parseInt(String(supplierIdRaw), 10)
    : null;

  try {
    const { rows } = await pool.query(`
      SELECT
        p.pid,
        p.title,
        p.image_175 AS image_url,
        p.barcode,
        p.vendor_reference,
        p.notions_reference,
        p.notions_inv_count,
        pm.current_stock,
        p.baskets,
        pm.on_order_qty,
        p.total_sold,
        p.notions_cost_each,
        p.supplier_cost_each,
        (
          SELECT r.cost_each
          FROM receivings r
          WHERE r.pid = p.pid
            AND r.cost_each > 0
            AND r.status <> 'canceled'
          ORDER BY r.received_date DESC
          LIMIT 1
        ) AS last_received_cost,
        pm.date_last_sold,
        pm.date_first_received,
        p.moq
      FROM products p
      LEFT JOIN product_metrics pm ON pm.pid = p.pid
      WHERE p.pid = ANY($1::bigint[])
    `, [pids]);

    const pickCost = (r) => {
      // Treat 0 as "unset" the same way the PHP code does (`if (!$cost_each)`).
      const notions = Number(r.notions_cost_each) || 0;
      const supplier = Number(r.supplier_cost_each) || 0;
      const lastReceived = Number(r.last_received_cost) || 0;
      if (supplierId === NOTIONS_SUPPLIER_ID && notions > 0) return notions;
      if (supplier > 0) return supplier;
      if (lastReceived > 0) return lastReceived;
      return null;
    };

    // products.pid is BIGINT, which the pg driver returns as a STRING by
    // default (to preserve precision for values > 2^53). Coerce to Number
    // so the JSON response has numeric pids and Map lookups work.
    const normalized = rows.map(r => ({
      ...r,
      pid: Number(r.pid),
      notions_cost_each: r.notions_cost_each != null ? Number(r.notions_cost_each) : null,
      supplier_cost_each: r.supplier_cost_each != null ? Number(r.supplier_cost_each) : null,
      last_received_cost: r.last_received_cost != null ? Number(r.last_received_cost) : null,
      // current_cost_price = the value that will land on the PO if this product
      // is added with the given supplier (or non-Notions default when omitted).
      current_cost_price: pickCost(r),
    }));
    const byPid = new Map(normalized.map(r => [r.pid, r]));
    // Preserve the requested order so the frontend can append rows in input order
    const ordered = pids.map(pid => byPid.get(pid)).filter(Boolean);
    res.json(ordered);
  } catch (error) {
    console.error('Error fetching batch products:', error);
    res.status(500).json({ error: 'Failed to fetch products' });
  }
});

// Bulk resolve a list of identifiers (UPC / SKU / supplier # / notions # / pid)
// to candidate products in ONE query. Used by the Create PO paste/upload flow.
// Body: { identifiers: string[] }
// Response: { results: Array<{ identifier: string, candidates: Candidate[] }> }
// Results are returned in the same order as the input identifiers, with
// duplicates preserved (so the caller can pair results back to input rows
// positionally).
router.post('/resolve-identifiers', async (req, res) => {
  const pool = req.app.locals.pool;
  const body = req.body || {};
  const raw = Array.isArray(body.identifiers) ? body.identifiers : null;

  if (!raw) {
    return res.status(400).json({ error: 'identifiers array is required' });
  }

  // Clean and cap. Cleaned keeps ORIGINAL order (duplicates preserved) so the
  // response aligns with the caller's input rows positionally.
  const cleaned = raw
    .map(s => (typeof s === 'string' ? s.trim() : ''))
    .filter(s => s.length > 0)
    .slice(0, 1000);

  if (cleaned.length === 0) {
    return res.json({ results: [] });
  }

  // Dedupe for the DB lookup, and split numeric-looking values off for a
  // separate bigint equality check (so the pid index can be used).
  const uniqueTextIds = Array.from(new Set(cleaned));
  const numericPids = Array.from(new Set(
    uniqueTextIds
      .filter(s => /^\d+$/.test(s) && s.length <= 18) // safe for Number()
      .map(s => Number(s))
      .filter(n => Number.isSafeInteger(n) && n > 0)
  ));

  try {
    const { rows } = await pool.query(`
      SELECT
        p.pid,
        p.title,
        p.sku,
        p.barcode,
        p.vendor_reference,
        p.notions_reference,
        p.brand
      FROM products p
      WHERE p.sku              = ANY($1::text[])
         OR p.barcode          = ANY($1::text[])
         OR p.vendor_reference = ANY($1::text[])
         OR p.notions_reference = ANY($1::text[])
         OR p.pid              = ANY($2::bigint[])
    `, [uniqueTextIds, numericPids]);

    // Normalize pid to Number once (products.pid is BIGINT → pg returns string)
    const products = rows.map(r => ({
      pid: Number(r.pid),
      title: r.title,
      sku: r.sku,
      barcode: r.barcode,
      vendor_reference: r.vendor_reference,
      notions_reference: r.notions_reference,
      brand: r.brand,
    }));

    // Group per-input-identifier. A product counts as a match for an
    // identifier if any of its indexable fields equals the identifier string
    // (or the pid matches when the identifier is numeric). The comparison is
    // done in JS against the fetched products — cheap because the product
    // count is bounded by the DB result set.
    const results = cleaned.map(identifier => {
      const candidates = products.filter(p => (
        p.sku === identifier ||
        p.barcode === identifier ||
        p.vendor_reference === identifier ||
        p.notions_reference === identifier ||
        String(p.pid) === identifier
      ));
      return { identifier, candidates };
    });

    res.json({ results });
  } catch (error) {
    console.error('Error resolving identifiers:', error);
    res.status(500).json({ error: 'Failed to resolve identifiers' });
  }
});

// Get a single product
router.get('/:id', async (req, res) => {
  try {
    const pool = req.app.locals.pool;
    const id = parseInt(req.params.id);

    // Common CTE for category paths
    const categoryPathCTE = `
      WITH RECURSIVE category_path AS (
        SELECT 
          c.cat_id,
          c.name,
          c.parent_id,
          c.name::text as path
        FROM categories c
        WHERE c.parent_id IS NULL
        
        UNION ALL
        
        SELECT 
          c.cat_id,
          c.name,
          c.parent_id,
          (cp.path || ' > ' || c.name)::text
        FROM categories c
        JOIN category_path cp ON c.parent_id = cp.cat_id
      )
    `;

    // Get product details with category paths
    const { rows: productRows } = await pool.query(`
      SELECT 
        p.*,
        pm.avg_sales_per_day_30d AS daily_sales_avg,
        pm.sales_7d AS weekly_sales_avg,
        pm.avg_sales_per_month_30d AS monthly_sales_avg,
        pm.stock_cover_in_days AS days_of_inventory,
        pm.replenishment_units AS reorder_point,
        pm.config_safety_stock AS safety_stock,
        pm.status AS stock_status,
        pm.abc_class,
        pm.margin_30d AS avg_margin_percent,
        pm.lifetime_revenue AS total_revenue,
        pm.current_stock_cost AS inventory_value,
        pm.stockturn_30d AS turnover_rate,
        pm.gmroi_30d AS gmroi,
        pm.cogs_30d AS cost_of_goods_sold,
        pm.profit_30d AS gross_profit,
        pm.avg_lead_time_days,
        pm.config_lead_time AS current_lead_time,
        pm.config_lead_time AS target_lead_time,
        pm.to_order_units AS reorder_qty,
        pm.overstocked_units AS overstocked_amt
      FROM products p
      LEFT JOIN product_metrics pm ON p.pid = pm.pid
      WHERE p.pid = $1
    `, [id]);

    if (!productRows.length) {
      return res.status(404).json({ error: 'Product not found' });
    }

    // Get categories and their paths separately to avoid GROUP BY issues
    const { rows: categoryRows } = await pool.query(`
      WITH RECURSIVE 
        category_path AS (
          SELECT 
            c.cat_id,
            c.name,
            c.parent_id,
            c.name::text as path
          FROM categories c
          WHERE c.parent_id IS NULL
          
          UNION ALL
          
          SELECT 
            c.cat_id,
            c.name,
            c.parent_id,
            (cp.path || ' > ' || c.name)::text
          FROM categories c
          JOIN category_path cp ON c.parent_id = cp.cat_id
        ),
        product_leaf_categories AS (
          -- Find categories assigned to this product that aren't parents
          -- of other categories assigned to this product
          SELECT pc.cat_id
          FROM product_categories pc
          WHERE pc.pid = $1
          AND NOT EXISTS (
            -- Check if there are any child categories also assigned to this product
            SELECT 1 
            FROM categories child
            JOIN product_categories child_pc ON child.cat_id = child_pc.cat_id
            WHERE child.parent_id = pc.cat_id
            AND child_pc.pid = pc.pid
          )
        )
      SELECT 
        c.cat_id,
        c.name as category_name,
        cp.path as full_path
      FROM product_categories pc
      JOIN categories c ON pc.cat_id = c.cat_id
      JOIN category_path cp ON c.cat_id = cp.cat_id
      JOIN product_leaf_categories plc ON c.cat_id = plc.cat_id
      WHERE pc.pid = $2
      ORDER BY cp.path
    `, [id, id]);

    // Transform the results
    const categoryPathMap = categoryRows.reduce((acc, row) => {
      // Use cat_id in the key to differentiate categories with the same name
      acc[`${row.cat_id}:${row.category_name}`] = row.full_path;
      return acc;
    }, {});

    const product = {
      ...productRows[0],
      // Include cat_id in categories array to match the keys in categoryPathMap
      categories: categoryRows.map(row => `${row.cat_id}:${row.category_name}`),
      category_paths: categoryPathMap,
      price: parseFloat(productRows[0].price),
      regular_price: parseFloat(productRows[0].regular_price),
      cost_price: parseFloat(productRows[0].cost_price),
      stock_quantity: parseInt(productRows[0].stock_quantity),
      moq: parseInt(productRows[0].moq),
      uom: parseInt(productRows[0].uom),
      managing_stock: Boolean(productRows[0].managing_stock),
      replenishable: Boolean(productRows[0].replenishable),
      // Format new fields
      preorder_count: parseInt(productRows[0].preorder_count || 0),
      notions_inv_count: parseInt(productRows[0].notions_inv_count || 0),
      harmonized_tariff_code: productRows[0].harmonized_tariff_code || '',
      notions_reference: productRows[0].notions_reference || '',
      line: productRows[0].line || '',
      subline: productRows[0].subline || '',
      artist: productRows[0].artist || '',
      rating: parseFloat(productRows[0].rating || 0),
      reviews: parseInt(productRows[0].reviews || 0),
      weight: parseFloat(productRows[0].weight || 0),
      dimensions: {
        length: parseFloat(productRows[0].length || 0),
        width: parseFloat(productRows[0].width || 0),
        height: parseFloat(productRows[0].height || 0),
      },
      country_of_origin: productRows[0].country_of_origin || '',
      location: productRows[0].location || '',
      total_sold: parseInt(productRows[0].total_sold || 0),
      baskets: parseInt(productRows[0].baskets || 0),
      notifies: parseInt(productRows[0].notifies || 0),
      date_last_sold: productRows[0].date_last_sold || null,
      // Format existing analytics fields
      daily_sales_avg: parseFloat(productRows[0].daily_sales_avg) || 0,
      weekly_sales_avg: parseFloat(productRows[0].weekly_sales_avg) || 0,
      monthly_sales_avg: parseFloat(productRows[0].monthly_sales_avg) || 0,
      avg_quantity_per_order: parseFloat(productRows[0].avg_quantity_per_order) || 0,
      number_of_orders: parseInt(productRows[0].number_of_orders) || 0,
      first_sale_date: productRows[0].first_sale_date || null,
      last_sale_date: productRows[0].last_sale_date || null,
      days_of_inventory: parseFloat(productRows[0].days_of_inventory) || 0,
      weeks_of_inventory: parseFloat(productRows[0].weeks_of_inventory) || 0,
      reorder_point: parseFloat(productRows[0].reorder_point) || 0,
      safety_stock: parseFloat(productRows[0].safety_stock) || 0,
      avg_margin_percent: parseFloat(productRows[0].avg_margin_percent) || 0,
      total_revenue: parseFloat(productRows[0].total_revenue) || 0,
      inventory_value: parseFloat(productRows[0].inventory_value) || 0,
      cost_of_goods_sold: parseFloat(productRows[0].cost_of_goods_sold) || 0,
      gross_profit: parseFloat(productRows[0].gross_profit) || 0,
      gmroi: parseFloat(productRows[0].gmroi) || 0,
      avg_lead_time_days: parseFloat(productRows[0].avg_lead_time_days) || 0,
      current_lead_time: parseFloat(productRows[0].current_lead_time) || 0,
      target_lead_time: parseFloat(productRows[0].target_lead_time) || 0,
      lead_time_status: productRows[0].lead_time_status || null,
      reorder_qty: parseInt(productRows[0].reorder_qty) || 0,
      overstocked_amt: parseInt(productRows[0].overstocked_amt) || 0
    };

    res.json(product);
  } catch (error) {
    console.error('Error fetching product:', error);
    res.status(500).json({ error: 'Failed to fetch product' });
  }
});

// Get product time series data
router.get('/:id/time-series', async (req, res) => {
  const { id } = req.params;
  try {
    const pool = req.app.locals.pool;

    // Get monthly sales data
    const { rows: monthlySales } = await pool.query(`
      SELECT 
        TO_CHAR(date, 'YYYY-MM') as month,
        COUNT(DISTINCT order_number) as order_count,
        SUM(quantity) as units_sold,
        ROUND(SUM(price * quantity)::numeric, 3) as revenue
      FROM orders
      WHERE pid = $1
        AND canceled = false
      GROUP BY TO_CHAR(date, 'YYYY-MM')
      ORDER BY month DESC
      LIMIT 12
    `, [id]);

    // Format monthly sales data
    const formattedMonthlySales = monthlySales.map(month => ({
      month: month.month,
      order_count: parseInt(month.order_count),
      units_sold: parseInt(month.units_sold),
      revenue: parseFloat(month.revenue),
      profit: 0 // Set to 0 since we don't have cost data in orders table
    }));

    // Get recent orders
    const { rows: recentOrders } = await pool.query(`
      SELECT 
        TO_CHAR(date, 'YYYY-MM-DD') as date,
        order_number,
        quantity,
        price,
        discount,
        tax,
        shipping,
        customer_name as customer,
        status
      FROM orders 
      WHERE pid = $1
        AND canceled = false
      ORDER BY date DESC
      LIMIT 10
    `, [id]);

    // Get recent purchase orders with received quantities from the receivings table
    const { rows: recentPurchases } = await pool.query(`
      SELECT
        TO_CHAR(po.date, 'YYYY-MM-DD') as date,
        TO_CHAR(po.expected_date, 'YYYY-MM-DD') as expected_date,
        TO_CHAR(MAX(r.received_date), 'YYYY-MM-DD') as received_date,
        po.po_id,
        po.ordered,
        COALESCE(SUM(r.qty_each), 0)::integer as received,
        po.status,
        po.po_cost_price as cost_price,
        po.notes,
        CASE
          WHEN MAX(r.received_date) IS NOT NULL THEN
            EXTRACT(DAY FROM MAX(r.received_date) - po.date)::integer
          WHEN po.expected_date < CURRENT_DATE AND po.status NOT IN ('done', 'canceled') THEN
            (CURRENT_DATE - po.expected_date)
          ELSE NULL
        END as lead_time_days
      FROM purchase_orders po
      LEFT JOIN receivings r ON r.receiving_id = po.po_id AND r.pid = po.pid AND r.status != 'canceled'
      WHERE po.pid = $1
        AND po.status != 'canceled'
      GROUP BY po.id, po.po_id, po.date, po.expected_date, po.ordered, po.status, po.po_cost_price, po.notes
      ORDER BY po.date DESC
      LIMIT 10
    `, [id]);

    res.json({
      monthly_sales: formattedMonthlySales,
      recent_orders: recentOrders.map(order => ({
        ...order,
        price: parseFloat(order.price),
        discount: parseFloat(order.discount),
        tax: parseFloat(order.tax),
        shipping: parseFloat(order.shipping),
        quantity: parseInt(order.quantity)
      })),
      recent_purchases: recentPurchases.map(po => ({
        ...po,
        ordered: parseInt(po.ordered),
        received: parseInt(po.received),
        status: po.status, // Text-based status (e.g., 'done', 'ordered', 'receiving_started')
        cost_price: parseFloat(po.cost_price),
        lead_time_days: po.lead_time_days ? parseInt(po.lead_time_days) : null
      }))
    });
  } catch (error) {
    console.error('Error fetching product time series:', error);
    res.status(500).json({ error: 'Failed to fetch product time series' });
  }
});

// GET /products/:id/forecast
// Returns the 90-day daily forecast for a single product from product_forecasts
router.get('/:id/forecast', async (req, res) => {
  const { id } = req.params;
  try {
    const pool = req.app.locals.pool;

    const { rows } = await pool.query(`
      SELECT
        forecast_date AS date,
        forecast_units AS units,
        forecast_revenue AS revenue,
        lifecycle_phase AS phase,
        forecast_method AS method,
        confidence_lower,
        confidence_upper
      FROM product_forecasts
      WHERE pid = $1
      ORDER BY forecast_date
    `, [id]);

    if (rows.length === 0) {
      return res.json({ forecast: [], phase: null, method: null });
    }

    const phase = rows[0].phase;
    const method = rows[0].method;

    res.json({
      phase,
      method,
      forecast: rows.map(r => ({
        date: formatDateCol(r.date),
        units: parseFloat(r.units) || 0,
        revenue: parseFloat(r.revenue) || 0,
        confidenceLower: parseFloat(r.confidence_lower) || 0,
        confidenceUpper: parseFloat(r.confidence_upper) || 0,
      })),
    });
  } catch (error) {
    console.error('Error fetching product forecast:', error);
    res.status(500).json({ error: 'Failed to fetch product forecast' });
  }
});

export default router;
