/**
 * WS0c: Procurement Intelligence API
 *
 * Endpoints:
 *   GET /api/procurement/overview          — Dashboard summary
 *   GET /api/procurement/signals           — Active reorder signals
 *   GET /api/procurement/signals/:id       — Signal for a specific transceiver
 *   GET /api/procurement/abc               — ABC classification list
 *   GET /api/procurement/market-intel      — Market intelligence events
 *   GET /api/procurement/stock-trends/:id  — Stock history for a transceiver
 *   GET /api/procurement/lifecycle         — Lifecycle events (EOL, standards)
 *   GET /api/procurement/ai-clusters       — AI datacenter announcements with transceiver demand
 *   GET /api/procurement/internal-demand   — Flexoptix internal demand velocity (fast/slow/dead)
 */
import { Router, Request, Response } from "express";
import { pool } from "../db/client";
import { sendCSV } from "../utils/csv";

export const procurementRouter = Router();

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/overview
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/overview", async (_req: Request, res: Response) => {
  try {
    const [signals, abc, intel, lifecycle] = await Promise.all([
      pool.query(`
        WITH latest AS (
          SELECT DISTINCT ON (transceiver_id) signal
          FROM reorder_signals
          WHERE expires_at > NOW()
          ORDER BY transceiver_id, computed_at DESC
        )
        SELECT signal, COUNT(*) AS count FROM latest GROUP BY signal
      `),
      pool.query(`
        SELECT abc_class, COUNT(*) AS count FROM abc_classification GROUP BY abc_class ORDER BY abc_class
      `),
      pool.query(`
        SELECT intel_type, buy_signal_implication, COUNT(*) AS count
        FROM market_intelligence
        WHERE created_at > NOW() - INTERVAL '90 days'
        GROUP BY intel_type, buy_signal_implication
        ORDER BY count DESC
        LIMIT 10
      `),
      pool.query(`
        SELECT event_type, impact_level, COUNT(*) AS count
        FROM product_lifecycle_events
        WHERE created_at > NOW() - INTERVAL '180 days'
        GROUP BY event_type, impact_level
        ORDER BY count DESC
      `),
    ]);

    res.json({
      signals_summary: signals.rows,
      abc_summary: abc.rows,
      market_intel_summary: intel.rows,
      lifecycle_summary: lifecycle.rows,
    });
  } catch (err) {
    console.error("Procurement overview error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/signals?signal=buy_now&abc_class=A&limit=50&offset=0
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/signals", async (req: Request, res: Response) => {
  try {
    const {
      signal, abc_class, form_factor, speed_gbps,
      limit = "50", offset = "0"
    } = req.query;

    // Use DISTINCT ON with the existing idx_reorder_transceiver index instead of
    // a correlated subquery that would run once per active row (108k+ scans).
    const params: any[] = [];
    let idx = 1;

    const signalFilter  = signal      ? ` AND rs.signal = $${idx++}`                          : "";
    if (signal)      params.push(signal);
    const abcFilter     = abc_class   ? ` AND ac.abc_class = $${idx++}`                        : "";
    if (abc_class)   params.push(abc_class);
    const ffFilter      = form_factor ? ` AND t.form_factor = $${idx++}`                       : "";
    if (form_factor) params.push(form_factor);
    const speedFilter   = speed_gbps  ? ` AND t.speed_gbps = $${idx++}`                        : "";
    if (speed_gbps)  params.push(parseFloat(speed_gbps as string));

    params.push(parseInt(limit as string), parseInt(offset as string));
    const limitIdx = idx; idx++;
    const offsetIdx = idx;

    const sql = `
      WITH latest AS (
        SELECT DISTINCT ON (transceiver_id)
          id, transceiver_id, signal, signal_strength, reasons,
          stock_trend, price_trend, lead_time_weeks, hype_phase,
          computed_at, expires_at, is_demo_data
        FROM reorder_signals
        WHERE expires_at > NOW()
        ORDER BY transceiver_id, computed_at DESC
      )
      SELECT rs.*,
        t.part_number, t.standard_name, t.form_factor, t.speed_gbps,
        t.reach_label, t.image_url, t.image_r2_key,
        ac.abc_class, ac.demand_score, ac.supply_risk,
        v.name AS vendor_name
      FROM latest rs
      JOIN transceivers t ON rs.transceiver_id = t.id
      LEFT JOIN abc_classification ac ON ac.transceiver_id = t.id
      LEFT JOIN vendors v ON t.vendor_id = v.id
      WHERE 1=1${signalFilter}${abcFilter}${ffFilter}${speedFilter}
      ORDER BY rs.signal_strength DESC
      LIMIT $${limitIdx} OFFSET $${offsetIdx}
    `;

    const result = await pool.query(sql, params);
    res.json({ data: result.rows, total: result.rowCount });
  } catch (err) {
    console.error("Signals error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/signals/:transceiver_id
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/signals/:id", async (req: Request, res: Response) => {
  try {
    const { id } = req.params;

    const [signal, stockHistory, priceHistory, lifecycle] = await Promise.all([
      pool.query(`
        SELECT rs.*, ac.abc_class, ac.demand_score, ac.supply_risk
        FROM reorder_signals rs
        LEFT JOIN abc_classification ac ON ac.transceiver_id = rs.transceiver_id
        WHERE rs.transceiver_id::text = $1
        ORDER BY rs.computed_at DESC LIMIT 1
      `, [id]),

      pool.query(`
        SELECT ss.stock_level, ss.stock_quantity, ss.incoming_quantity,
          ss.incoming_eta, ss.lead_time_days, ss.moq, ss.price_breaks,
          ss.scraped_at, ss.crawler_confidence,
          v.name AS vendor_name
        FROM stock_snapshots ss
        JOIN vendors v ON ss.vendor_id = v.id
        WHERE ss.transceiver_id::text = $1
        ORDER BY ss.scraped_at DESC LIMIT 50
      `, [id]),

      pool.query(`
        SELECT po.price, po.currency, po.time,
          v.name AS vendor_name
        FROM price_observations po
        JOIN vendors v ON po.source_vendor_id = v.id
        WHERE po.transceiver_id::text = $1
        ORDER BY po.time DESC LIMIT 30
      `, [id]),

      pool.query(`
        SELECT * FROM product_lifecycle_events
        WHERE transceiver_id::text = $1
        ORDER BY effective_date ASC NULLS LAST, created_at DESC
      `, [id]),
    ]);

    res.json({
      signal: signal.rows[0] || null,
      stock_history: stockHistory.rows,
      price_history: priceHistory.rows,
      lifecycle_events: lifecycle.rows,
    });
  } catch (err) {
    console.error("Signal detail error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/abc?class=A&form_factor=QSFP28
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/abc", async (req: Request, res: Response) => {
  try {
    const { class: cls, form_factor, speed_gbps, limit = "100", offset = "0" } = req.query;

    let sql = `
      SELECT ac.*,
        t.part_number, t.standard_name, t.form_factor, t.speed_gbps,
        t.reach_label, t.image_url,
        v.name AS vendor_name,
        rs.signal, rs.signal_strength
      FROM abc_classification ac
      JOIN transceivers t ON ac.transceiver_id = t.id
      LEFT JOIN vendors v ON t.vendor_id = v.id
      LEFT JOIN LATERAL (
        SELECT signal, signal_strength FROM reorder_signals
        WHERE transceiver_id = ac.transceiver_id AND expires_at > NOW()
        ORDER BY computed_at DESC LIMIT 1
      ) rs ON true
      WHERE 1=1
    `;
    const params: any[] = [];
    let idx = 1;

    if (cls) { sql += ` AND ac.abc_class = $${idx}`; params.push(cls); idx++; }
    if (form_factor) { sql += ` AND t.form_factor = $${idx}`; params.push(form_factor); idx++; }
    if (speed_gbps) { sql += ` AND t.speed_gbps = $${idx}`; params.push(parseFloat(speed_gbps as string)); idx++; }

    sql += ` ORDER BY ac.abc_class, ac.demand_score DESC LIMIT $${idx} OFFSET $${idx + 1}`;
    params.push(parseInt(limit as string), parseInt(offset as string));

    const result = await pool.query(sql, params);
    if ((req.query.format as string) === "csv") {
      return sendCSV(res, result.rows, `tip-abc-classification-${new Date().toISOString().slice(0,10)}.csv`);
    }
    res.json({ data: result.rows, total: result.rowCount });
  } catch (err) {
    console.error("ABC error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/market-intel?type=&days=90&signal=buy_now
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/market-intel", async (req: Request, res: Response) => {
  try {
    const {
      type, days = "90", signal, technology,
      limit = "50", offset = "0"
    } = req.query;

    let sql = `
      SELECT * FROM market_intelligence
      WHERE created_at > NOW() - INTERVAL '1 day' * $1
    `;
    const params: any[] = [parseInt(days as string)];
    let idx = 2;

    if (type) { sql += ` AND intel_type = $${idx}`; params.push(type); idx++; }
    if (signal) { sql += ` AND buy_signal_implication = $${idx}`; params.push(signal); idx++; }
    if (technology) { sql += ` AND $${idx} = ANY(technologies)`; params.push(technology); idx++; }

    sql += ` ORDER BY relevance_score DESC, created_at DESC LIMIT $${idx} OFFSET $${idx + 1}`;
    params.push(parseInt(limit as string), parseInt(offset as string));

    const result = await pool.query(sql, params);
    res.json({ data: result.rows, total: result.rowCount });
  } catch (err) {
    console.error("Market intel error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/stock-trends/:transceiver_id
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/stock-trends/:id", async (req: Request, res: Response) => {
  try {
    const result = await pool.query(`
      SELECT DISTINCT ON (ss.vendor_id, date_trunc('day', ss.scraped_at))
        ss.stock_level, ss.stock_quantity, ss.incoming_quantity,
        ss.incoming_eta, ss.lead_time_days, ss.scraped_at,
        v.name AS vendor_name
      FROM stock_snapshots ss
      JOIN vendors v ON ss.vendor_id = v.id
      WHERE ss.transceiver_id::text = $1
      ORDER BY ss.vendor_id, date_trunc('day', ss.scraped_at) DESC, ss.scraped_at DESC
      LIMIT 200
    `, [req.params.id]);

    res.json({ data: result.rows });
  } catch (err) {
    console.error("Stock trends error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/lifecycle?type=eol_announced&impact=high&days=180
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/lifecycle", async (req: Request, res: Response) => {
  try {
    const {
      type, impact, technology, signal,
      days = "180", limit = "50"
    } = req.query;

    let sql = `
      SELECT ple.*,
        t.part_number, t.standard_name, t.form_factor, t.speed_gbps
      FROM product_lifecycle_events ple
      LEFT JOIN transceivers t ON ple.transceiver_id = t.id
      WHERE ple.created_at > NOW() - INTERVAL '1 day' * $1
    `;
    const params: any[] = [parseInt(days as string)];
    let idx = 2;

    if (type) { sql += ` AND ple.event_type = $${idx}`; params.push(type); idx++; }
    if (impact) { sql += ` AND ple.impact_level = $${idx}`; params.push(impact); idx++; }
    if (technology) { sql += ` AND ple.technology ILIKE $${idx}`; params.push(`%${technology}%`); idx++; }
    if (signal) { sql += ` AND ple.buy_signal = $${idx}`; params.push(signal); idx++; }

    sql += ` ORDER BY ple.impact_level DESC, ple.effective_date ASC NULLS LAST, ple.created_at DESC LIMIT $${idx}`;
    params.push(parseInt(limit as string));

    const result = await pool.query(sql, params);
    res.json({ data: result.rows });
  } catch (err) {
    console.error("Lifecycle error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/ai-clusters?days=90&limit=50&min_transceivers=0
// Returns AI datacenter announcements with transceiver demand estimates
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/ai-clusters", async (req: Request, res: Response) => {
  try {
    const {
      days = "90",
      limit = "50",
      min_transceivers = "0",
    } = req.query;

    const daysN = Math.min(Math.max(parseInt(days as string) || 90, 1), 730);
    const limitN = Math.min(parseInt(limit as string) || 50, 200);
    const minTx = parseInt(min_transceivers as string) || 0;

    const result = await pool.query(
      `SELECT
         id, company, title, summary,
         announced_date, scale_mw, scale_servers,
         network_speed, estimated_transceivers,
         deployment_date, location, source_url, source_name,
         created_at
       FROM ai_cluster_announcements
       WHERE
         (announced_date IS NULL OR announced_date >= NOW() - INTERVAL '1 day' * $1)
         AND ($2 = 0 OR estimated_transceivers >= $2)
       ORDER BY announced_date DESC NULLS LAST, created_at DESC
       LIMIT $3`,
      [daysN, minTx, limitN]
    );

    // Aggregate stats
    const statsResult = await pool.query(
      `SELECT
         COUNT(*) AS total,
         COUNT(*) FILTER (WHERE estimated_transceivers > 0) AS with_estimates,
         SUM(estimated_transceivers) FILTER (WHERE estimated_transceivers > 0) AS total_estimated_transceivers,
         SUM(scale_mw) FILTER (WHERE scale_mw IS NOT NULL) AS total_mw,
         COUNT(DISTINCT company) FILTER (WHERE company != 'Unknown') AS distinct_companies
       FROM ai_cluster_announcements
       WHERE announced_date >= NOW() - INTERVAL '1 day' * $1`,
      [daysN]
    );

    res.json({
      data: result.rows,
      stats: statsResult.rows[0],
      period_days: daysN,
    });
  } catch (err) {
    console.error("AI clusters error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/internal-demand?velocity_class=fast_mover&limit=100
// Returns Flexoptix internal demand data — real SKU velocity from internal data
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/internal-demand", async (req: Request, res: Response) => {
  try {
    const {
      velocity_class,
      limit = "100",
      offset = "0",
      sort = "demand_12m",
    } = req.query;

    const allowedSorts: Record<string, string> = {
      demand_12m: "fid.demand_12m DESC",
      demand_3m: "fid.demand_3m DESC",
      trend: "fid.demand_trend_pct DESC NULLS LAST",
      sku: "fid.sku ASC",
    };
    const orderBy = allowedSorts[sort as string] ?? allowedSorts["demand_12m"];

    const params: unknown[] = [];
    const conditions: string[] = ["fid.is_internal = true"];
    let idx = 1;

    if (velocity_class) {
      conditions.push(`fid.velocity_class = $${idx}`);
      params.push(velocity_class);
      idx++;
    }

    params.push(Math.min(parseInt(limit as string) || 100, 500));
    params.push(Math.max(parseInt(offset as string) || 0, 0));

    const result = await pool.query(
      `SELECT
         fid.id, fid.sku, fid.description,
         fid.demand_12m, fid.demand_3m, fid.demand_trend_pct,
         fid.velocity_class, fid.imported_at,
         t.part_number, t.standard_name, t.form_factor, t.speed_gbps,
         t.reach_label, t.image_url,
         v.name AS vendor_name
       FROM flexoptix_internal_demand fid
       LEFT JOIN transceivers t ON t.id = fid.transceiver_id
       LEFT JOIN vendors v ON v.id = t.vendor_id
       WHERE ${conditions.join(" AND ")}
       ORDER BY ${orderBy}
       LIMIT $${idx} OFFSET $${idx + 1}`,
      params
    );

    // Velocity summary
    const summaryResult = await pool.query(
      `SELECT
         velocity_class,
         COUNT(*) AS cnt,
         SUM(demand_12m)::numeric(12,0) AS total_demand_12m,
         AVG(demand_trend_pct)::numeric(8,1) AS avg_trend_pct
       FROM flexoptix_internal_demand
       WHERE is_internal = true
       GROUP BY velocity_class
       ORDER BY total_demand_12m DESC NULLS LAST`
    );

    res.json({
      data: result.rows,
      summary: summaryResult.rows,
      total: result.rowCount,
    });
  } catch (err) {
    console.error("Internal demand error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/hyperscaler-capex
// Hyperscaler quarterly CapEx from SEC filings — demand context for transceivers
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/hyperscaler-capex", async (_req: Request, res: Response) => {
  try {
    const result = await pool.query(`
      SELECT
        company, period_label, period_end,
        capex_usd_millions, dc_capex_est_millions,
        yoy_growth_pct, filing_type, source_url
      FROM hyperscaler_capex
      ORDER BY period_end DESC, capex_usd_millions DESC
    `);

    const summaryResult = await pool.query(`
      SELECT
        company,
        MAX(period_end) AS latest_period_end,
        MAX(period_label) AS latest_period,
        (ARRAY_AGG(capex_usd_millions ORDER BY period_end DESC))[1] AS latest_capex,
        (ARRAY_AGG(dc_capex_est_millions ORDER BY period_end DESC))[1] AS latest_dc_capex,
        (ARRAY_AGG(yoy_growth_pct ORDER BY period_end DESC))[1] AS latest_yoy_growth,
        AVG(yoy_growth_pct) FILTER (WHERE period_end >= NOW() - INTERVAL '365 days') AS avg_yoy_12m
      FROM hyperscaler_capex
      GROUP BY company
      ORDER BY latest_capex DESC NULLS LAST
    `);

    res.json({
      data: result.rows,
      summary: summaryResult.rows,
    });
  } catch (err) {
    console.error("Hyperscaler capex error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/marketplace-velocity
// Secondary market (eBay) sell-through as demand signal
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/marketplace-velocity", async (_req: Request, res: Response) => {
  try {
    const result = await pool.query(`
      SELECT DISTINCT ON (form_factor, speed_label)
        marketplace, keyword, form_factor, speed_label,
        sold_count_30d, active_listings, avg_sold_price,
        min_price, max_price, currency, scraped_at
      FROM marketplace_velocity
      ORDER BY form_factor, speed_label, scraped_at DESC
    `);

    const hotResult = await pool.query(`
      SELECT DISTINCT ON (form_factor, speed_label)
        marketplace, form_factor, speed_label,
        sold_count_30d, active_listings, avg_sold_price
      FROM marketplace_velocity
      WHERE sold_count_30d > 0
      ORDER BY form_factor, speed_label, scraped_at DESC
    `);

    res.json({
      data: result.rows,
      hot: hotResult.rows.sort(
        (a: { sold_count_30d: string }, b: { sold_count_30d: string }) =>
          parseInt(b.sold_count_30d) - parseInt(a.sold_count_30d)
      ),
    });
  } catch (err) {
    console.error("Marketplace velocity error:", err);
    res.status(500).json({ error: "Internal server error" });
  }
});

// ─── E: GET /api/procurement/reorder-top ─────────────────────────────────────
// Top buy_now reorder signals with full reasons — 211k precomputed signals
procurementRouter.get("/reorder-top", async (req: Request, res: Response) => {
  const limit = Math.min(parseInt(req.query.limit as string) || 50, 200);
  const formFactor = (req.query.form_factor as string) || "";
  const minStrength = parseFloat(req.query.min_strength as string) || 0;

  try {
    const result = await pool.query(`
      SELECT DISTINCT ON (t.id)
        t.id, t.part_number, t.speed_gbps, t.form_factor, t.reach_label,
        v.name  AS vendor_name,
        rs.signal, rs.signal_strength,
        rs.price_trend, rs.stock_trend, rs.hype_phase,
        rs.reasons,
        rs.computed_at
      FROM reorder_signals rs
      JOIN transceivers t ON t.id = rs.transceiver_id
      JOIN vendors v ON v.id = t.vendor_id
      WHERE rs.signal = 'buy_now'
        AND rs.is_demo_data = false
        AND rs.signal_strength >= $1
        AND ($2 = '' OR t.form_factor ILIKE $2)
      ORDER BY t.id, rs.signal_strength DESC, rs.computed_at DESC
    `, [minStrength, formFactor]);

    // After DISTINCT ON, re-sort by signal_strength
    const rows = result.rows.sort(
      (a: { signal_strength: string }, b: { signal_strength: string }) =>
        parseFloat(b.signal_strength) - parseFloat(a.signal_strength)
    );

    const summary = await pool.query(`
      SELECT
        COUNT(*) FILTER (WHERE signal = 'buy_now' AND is_demo_data = false)::int  AS buy_now,
        COUNT(*) FILTER (WHERE signal = 'wait'    AND is_demo_data = false)::int  AS wait,
        COUNT(*) FILTER (WHERE signal = 'hold'    AND is_demo_data = false)::int  AS hold,
        COUNT(*) FILTER (WHERE signal = 'monitor' AND is_demo_data = false)::int  AS monitor,
        ROUND(AVG(signal_strength) FILTER (WHERE signal = 'buy_now' AND is_demo_data = false)::numeric,3) AS avg_buy_strength
      FROM reorder_signals
    `);

    res.json({ success: true, data: rows.slice(0, limit), summary: summary.rows[0] });
  } catch (err) {
    res.status(500).json({ error: String(err) });
  }
});

// ─── B: GET /api/procurement/switch-compat ───────────────────────────────────
// Switch ↔ transceiver compatibility matrix
procurementRouter.get("/switch-compat", async (req: Request, res: Response) => {
  const search = (req.query.search as string) || "";
  const limitNum = Math.min(parseInt(req.query.limit as string) || 30, 100);

  try {
    if (search.length >= 2) {
      // Search for switches matching query, return their compatible transceivers
      const switches = await pool.query(`
        SELECT DISTINCT ON (sw.id)
          sw.id, v.name AS sw_vendor, sw.model AS sw_model, sw.series AS sw_series,
          COUNT(c.transceiver_id) OVER (PARTITION BY sw.id)::int AS compat_count
        FROM switches sw
        JOIN compatibility c ON c.switch_id = sw.id
        LEFT JOIN vendors v ON v.id = sw.vendor_id
        WHERE sw.model ILIKE $1 OR COALESCE(v.name,'') ILIKE $1 OR sw.series ILIKE $1
        ORDER BY sw.id, compat_count DESC
        LIMIT $2
      `, [`%${search}%`, limitNum]);

      // For each matched switch, get top compatible transceivers with prices
      const switchIds = switches.rows.map((s: { id: string }) => s.id);
      if (switchIds.length === 0) {
        return res.json({ success: true, switches: [], transceivers: [] });
      }

      const transceivers = await pool.query(`
        SELECT
          c.switch_id,
          t.id AS tx_id, t.part_number, t.speed_gbps, t.form_factor, t.reach_label,
          v.name AS vendor_name,
          c.verification_method, c.status,
          (SELECT ROUND(MIN(po.price)::numeric,2) FROM price_observations po
           WHERE po.transceiver_id = t.id AND po.price > 0) AS min_price,
          (SELECT po.currency FROM price_observations po
           WHERE po.transceiver_id = t.id AND po.price > 0
           ORDER BY po.time DESC LIMIT 1) AS currency
        FROM compatibility c
        JOIN transceivers t ON t.id = c.transceiver_id
        JOIN vendors v ON v.id = t.vendor_id
        WHERE c.switch_id = ANY($1)
          AND c.status = 'compatible'
        ORDER BY t.speed_gbps DESC, t.form_factor
        LIMIT 200
      `, [switchIds]);

      return res.json({
        success: true,
        switches: switches.rows,
        transceivers: transceivers.rows,
      });
    }

    // No search — return top switches by compat count
    const top = await pool.query(`
      SELECT v.name AS vendor, sw.model, sw.series,
        COUNT(c.transceiver_id)::int AS compat_count
      FROM switches sw
      JOIN compatibility c ON c.switch_id = sw.id
      LEFT JOIN vendors v ON v.id = sw.vendor_id
      WHERE c.status = 'compatible'
      GROUP BY sw.id, v.name, sw.model, sw.series
      ORDER BY compat_count DESC
      LIMIT $1
    `, [limitNum]);

    const stats = await pool.query(`
      SELECT
        COUNT(DISTINCT sw.id)::int AS total_switches,
        COUNT(DISTINCT c.transceiver_id)::int AS total_transceivers,
        COUNT(*)::int AS total_compat_rows
      FROM switches sw JOIN compatibility c ON c.switch_id = sw.id
      WHERE c.status = 'compatible'
    `);

    return res.json({ success: true, topSwitches: top.rows, stats: stats.rows[0] });
  } catch (err) {
    res.status(500).json({ error: String(err) });
  }
});

// ─── A: GET /api/procurement/arbitrage ───────────────────────────────────────
// OEM vs Flexoptix price gaps via transceiver_equivalences
procurementRouter.get("/arbitrage", async (_req: Request, res: Response) => {
  // FX rates for normalization — approximate
  const FX: Record<string, number> = { USD: 1.0, EUR: 1.08, GBP: 1.27 };

  try {
    const result = await pool.query(`
      SELECT
        te.confidence,
        fx.part_number  AS fx_part,
        vfx.name        AS fx_vendor,
        fx.speed_gbps, fx.form_factor, fx.reach_label,
        comp.part_number AS comp_part,
        vcomp.name       AS comp_vendor,
        (SELECT price  FROM price_observations WHERE transceiver_id = te.flexoptix_id  AND price > 2 ORDER BY time DESC LIMIT 1) AS fx_price,
        (SELECT currency FROM price_observations WHERE transceiver_id = te.flexoptix_id AND price > 2 ORDER BY time DESC LIMIT 1) AS fx_curr,
        (SELECT price  FROM price_observations WHERE transceiver_id = te.competitor_id AND price > 2 ORDER BY time DESC LIMIT 1) AS comp_price,
        (SELECT currency FROM price_observations WHERE transceiver_id = te.competitor_id AND price > 2 ORDER BY time DESC LIMIT 1) AS comp_curr
      FROM transceiver_equivalences te
      JOIN transceivers fx   ON fx.id   = te.flexoptix_id
      JOIN transceivers comp ON comp.id = te.competitor_id
      JOIN vendors vfx   ON vfx.id   = fx.vendor_id
      JOIN vendors vcomp ON vcomp.id = comp.vendor_id
      WHERE te.status IN ('approved','auto_approved')
        AND EXISTS(SELECT 1 FROM price_observations WHERE transceiver_id = te.flexoptix_id  AND price > 2)
        AND EXISTS(SELECT 1 FROM price_observations WHERE transceiver_id = te.competitor_id AND price > 2)
      ORDER BY te.confidence DESC
      LIMIT 2000
    `);

    const pairs = result.rows
      .map((r: {
        fx_price: string; fx_curr: string;
        comp_price: string; comp_curr: string;
        confidence: string;
        fx_part: string; fx_vendor: string;
        comp_part: string; comp_vendor: string;
        speed_gbps: string; form_factor: string; reach_label: string;
      }) => {
        const fxUSD  = parseFloat(r.fx_price)   * (FX[r.fx_curr]   || 1.0);
        const compUSD = parseFloat(r.comp_price) * (FX[r.comp_curr] || 1.0);
        if (!fxUSD || !compUSD) return null;
        const savings    = compUSD - fxUSD;
        const savingsPct = Math.round((savings / compUSD) * 100);
        return { ...r, fxUSD: Math.round(fxUSD), compUSD: Math.round(compUSD), savings: Math.round(savings), savingsPct };
      })
      .filter((r): r is NonNullable<typeof r> => r !== null && r.savings > 0)
      .sort((a, b) => b.savingsPct - a.savingsPct)
      .slice(0, 100);

    // Stats
    const totalPairs  = result.rows.length;
    const fxCheaper   = pairs.length;
    const avgSavings  = pairs.length ? Math.round(pairs.reduce((s, r) => s + r.savingsPct, 0) / pairs.length) : 0;

    res.json({ success: true, pairs, stats: { totalPairs, fxCheaper, avgSavingsPct: avgSavings } });
  } catch (err) {
    res.status(500).json({ error: String(err) });
  }
});

// ─── D: GET /api/procurement/dead-stock-revival ──────────────────────────────
// Dead-stock SKUs whose equivalents are in rising hype phases
procurementRouter.get("/dead-stock-revival", async (_req: Request, res: Response) => {
  try {
    const [deadStock, hypeMap] = await Promise.all([
      pool.query(`
        SELECT
          fid.transceiver_id,
          fid.sku                   AS part_number,
          fid.velocity_class,
          fid.demand_12m,
          fid.demand_trend_pct,
          t.speed_gbps, t.form_factor, t.reach_label,
          v.name AS vendor_name
        FROM flexoptix_internal_demand fid
        JOIN transceivers t ON t.id = fid.transceiver_id
        JOIN vendors v ON v.id = t.vendor_id
        WHERE fid.velocity_class = 'dead_stock'
          AND fid.is_internal = true
        LIMIT 7500
      `),
      pool.query(`
        SELECT DISTINCT ON (technology)
          technology, hype_phase, hype_score, computed_at
        FROM hype_cycle_analysis
        ORDER BY technology, computed_at DESC
      `),
    ]);

    // Build speed → hype phase map
    type HypeRow = { technology: string; hype_phase: string; hype_score: string };
    const ASCENDING = new Set(["innovation_trigger","peak_inflated_expectations","slope_enlightenment","plateau_productivity"]);
    const speedToHype = new Map<number, HypeRow>();
    for (const h of hypeMap.rows as HypeRow[]) {
      const speedMatch = h.technology.match(/^(\d+(?:\.\d+)?)G/);
      if (speedMatch) speedToHype.set(parseFloat(speedMatch[1]), h);
    }

    type DeadRow = {
      transceiver_id: string; part_number: string;
      speed_gbps: string; form_factor: string; reach_label: string;
      vendor_name: string; demand_12m: string; demand_trend_pct: string;
      velocity_class: string;
    };

    const revivals = (deadStock.rows as DeadRow[])
      .map((r) => {
        const speed = parseFloat(r.speed_gbps);
        const hype  = speedToHype.get(speed);
        if (!hype) return null;
        const ascending = ASCENDING.has(hype.hype_phase);
        const score = parseFloat(hype.hype_score);
        return { ...r, hype_phase: hype.hype_phase, hype_score: score, ascending };
      })
      .filter((r): r is NonNullable<typeof r> => r !== null && r.ascending && r.hype_score > 30)
      .sort((a, b) => b.hype_score - a.hype_score)
      .slice(0, 100);

    const totalDead = deadStock.rows.length;
    res.json({ success: true, revivals, totalDeadStock: totalDead, revivalCount: revivals.length });
  } catch (err) {
    res.status(500).json({ error: String(err) });
  }
});

// ─── C: GET /api/procurement/supply-squeeze ──────────────────────────────────

// ─── GET /api/procurement/availability ───────────────────────────────────────
// Data-grounded supply-availability per speed tier. Derived from real supplier
// diversity (distinct source vendors offering it) + stock coverage. Surfaces the
// 400G/800G/1.6T supply tightening that exists in the data but was not shown.
procurementRouter.get("/availability", async (_req: Request, res: Response) => {
  const client = await pool.connect();
  try {
    // Disable parallel workers for this aggregate — Docker /dev/shm (64MB) is too
    // small for parallel hash and the query is fast enough single-threaded.
    await client.query('BEGIN');
    await client.query('SET LOCAL max_parallel_workers_per_gather = 0');
    const result = await client.query(`
      WITH sup AS (
        SELECT t.speed_gbps,
          COUNT(DISTINCT po.source_vendor_id) FILTER (WHERE po.time > NOW() - INTERVAL '45 days') AS suppliers,
          COUNT(DISTINCT po.source_vendor_id) FILTER (WHERE po.time <= NOW() - INTERVAL '45 days') AS suppliers_prior
        FROM price_observations po
        JOIN transceivers t ON t.id = po.transceiver_id
        WHERE t.speed_gbps IN (100,200,400,800,1600) AND po.time > NOW() - INTERVAL '90 days'
        GROUP BY t.speed_gbps
      ),
      sk AS (
        SELECT speed_gbps, COUNT(*) AS sku_count
        FROM transceivers WHERE speed_gbps IN (100,200,400,800,1600)
        GROUP BY speed_gbps HAVING COUNT(*) >= 3
      ),
      st AS (
        SELECT t.speed_gbps,
          COUNT(*) FILTER (WHERE s.in_stock IS TRUE) AS skus_in_stock,
          COUNT(*) AS skus_with_stock
        FROM transceivers t
        JOIN LATERAL (
          SELECT in_stock FROM stock_observations so
          WHERE so.transceiver_id = t.id ORDER BY so.time DESC LIMIT 1
        ) s ON true
        WHERE t.speed_gbps IN (100,200,400,800,1600)
        GROUP BY t.speed_gbps
      ),
      pm AS (
        -- per-SKU paired price momentum (30d vs prior 30d), bias-free: only SKUs in
        -- both windows; aggregate the median per-SKU pct delta per speed tier.
        SELECT speed_gbps,
          ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY (med_now - med_prior) / NULLIF(med_prior,0) * 100)::numeric, 1) AS price_delta_pct
        FROM (
          SELECT t.speed_gbps, t.id,
            PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY po.price) FILTER (WHERE po.time >= NOW() - INTERVAL '30 days') AS med_now,
            PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY po.price) FILTER (WHERE po.time >= NOW() - INTERVAL '60 days' AND po.time < NOW() - INTERVAL '30 days') AS med_prior,
            COUNT(*) FILTER (WHERE po.time >= NOW() - INTERVAL '30 days') AS n_now,
            COUNT(*) FILTER (WHERE po.time >= NOW() - INTERVAL '60 days' AND po.time < NOW() - INTERVAL '30 days') AS n_prior
          FROM price_observations po JOIN transceivers t ON t.id = po.transceiver_id
          WHERE t.speed_gbps IN (100,200,400,800,1600) AND po.price > 0 AND COALESCE(po.is_anomalous,false) = false
          GROUP BY t.speed_gbps, t.id
        ) per
        WHERE med_now IS NOT NULL AND med_prior IS NOT NULL AND med_prior > 0 AND n_now >= 2 AND n_prior >= 2
        GROUP BY speed_gbps
      )
      SELECT
        sk.speed_gbps,
        sk.sku_count::int,
        COALESCE(sup.suppliers,0)::int       AS suppliers,
        COALESCE(sup.suppliers_prior,0)::int AS suppliers_prior,
        COALESCE(st.skus_in_stock,0)::int    AS skus_in_stock,
        COALESCE(st.skus_with_stock,0)::int  AS skus_with_stock,
        CASE WHEN COALESCE(st.skus_with_stock,0) > 0
          THEN ROUND(100.0 * st.skus_in_stock / st.skus_with_stock)::int ELSE NULL END AS in_stock_pct,
        pm.price_delta_pct
      FROM sk
      LEFT JOIN sup ON sup.speed_gbps = sk.speed_gbps
      LEFT JOIN st  ON st.speed_gbps  = sk.speed_gbps
      LEFT JOIN pm  ON pm.speed_gbps  = sk.speed_gbps
      ORDER BY sk.speed_gbps DESC
    `);

    type Row = { speed_gbps: string; sku_count: number; suppliers: number; suppliers_prior: number; skus_in_stock: number; skus_with_stock: number; in_stock_pct: number | null; price_delta_pct: string | null };
    const tiers = (result.rows as Row[]).map((r) => {
      const sup = r.suppliers ?? 0;
      const inStockPct = r.in_stock_pct ?? null;
      // availability class from supplier diversity + stock coverage
      let availability: "scarce" | "constrained" | "moderate" | "abundant";
      if (sup <= 3 || (inStockPct !== null && inStockPct < 20)) availability = "scarce";
      else if (sup <= 6 || (inStockPct !== null && inStockPct < 45)) availability = "constrained";
      else if (sup <= 9) availability = "moderate";
      else availability = "abundant";
      // tightening trend: supplier diversity dropped vs prior window
      const trend = r.suppliers_prior > 0
        ? (sup < r.suppliers_prior ? "tightening" : sup > r.suppliers_prior ? "loosening" : "stable")
        : "stable";
      const priceDelta = r.price_delta_pct != null ? parseFloat(r.price_delta_pct) : null;
      const priceTrend = priceDelta === null ? "unknown" : priceDelta > 3 ? "rising" : priceDelta < -3 ? "falling" : "stable";
      const drivers: string[] = [`${sup} active suppliers`];
      if (trend === "tightening") drivers.push(`supplier base shrank ${r.suppliers_prior}→${sup} vs prior 45d`);
      else if (trend === "loosening") drivers.push(`supplier base grew ${r.suppliers_prior}→${sup} vs prior 45d`);
      if (inStockPct !== null) drivers.push(`${inStockPct}% of tracked SKUs in stock`);
      if (priceDelta !== null) drivers.push(`price ${priceDelta >= 0 ? "+" : ""}${priceDelta}% (30d, same-SKU median)`);
      drivers.push(`${r.sku_count} SKUs catalogued`);
      // Plain-language WHY, built only from real per-speed signals
      const reasons: string[] = [];
      if (availability === "scarce") reasons.push(sup <= 3 ? `only ${sup} supplier(s) offer it` : `near-zero stock coverage`);
      else if (availability === "constrained") reasons.push(`limited supplier base (${sup})`);
      if (trend === "tightening") reasons.push(`supplier base is shrinking`);
      if (priceTrend === "rising") reasons.push(`prices rising ${priceDelta}% on the same parts`);
      if (priceTrend === "falling") reasons.push(`prices easing ${priceDelta}%`);
      if (inStockPct !== null && inStockPct < 50) reasons.push(`${inStockPct}% in-stock coverage`);
      const why = reasons.length ? reasons.join("; ") : `broad supply: ${sup} suppliers, ${inStockPct ?? "n/a"}% in stock, stable pricing`;
      return {
        speed_gbps: parseFloat(r.speed_gbps),
        sku_count: r.sku_count,
        suppliers: sup,
        in_stock_pct: inStockPct,
        price_delta_pct: priceDelta,
        price_trend: priceTrend,
        availability,
        trend,
        why,
        drivers,
      };
    });

    res.json({ success: true, tiers });
  } catch (err) {
    try { await client.query('ROLLBACK'); } catch { /* ignore */ }
    console.error("availability error:", err);
    res.status(500).json({ success: false, error: String(err) });
  } finally {
    client.release();
  }
});


// Multi-signal supply constraint detector
procurementRouter.get("/supply-squeeze", async (_req: Request, res: Response) => {
  try {
    const [priceSignals, aiDemand, hypeData, stockData] = await Promise.all([
      // Price momentum: 30d vs 60d avg by speed/form_factor
      pool.query(`
        -- Per-SKU paired comparison: only transceivers present in BOTH periods.
        -- This eliminates catalog-composition bias (new expensive SKUs entering a
        -- speed/form-factor bucket would otherwise fake a huge price jump).
        WITH per_sku AS (
          SELECT
            t.id, t.speed_gbps, t.form_factor,
            PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY po.price)
              FILTER (WHERE po.time >= NOW() - INTERVAL '30 days') AS med_now,
            PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY po.price)
              FILTER (WHERE po.time >= NOW() - INTERVAL '60 days' AND po.time < NOW() - INTERVAL '30 days') AS med_prior,
            COUNT(*) FILTER (WHERE po.time >= NOW() - INTERVAL '30 days') AS obs_now,
            COUNT(*) FILTER (WHERE po.time >= NOW() - INTERVAL '60 days' AND po.time < NOW() - INTERVAL '30 days') AS obs_prior
          FROM price_observations po
          JOIN transceivers t ON t.id = po.transceiver_id
          WHERE po.price > 5 AND po.currency = 'USD'
            AND COALESCE(po.is_anomalous, false) = false
            AND t.form_factor IN ('SFP','SFP+','SFP28','SFP56','QSFP+','QSFP28','QSFP56','QSFP-DD','QSFP-DD800','OSFP','OSFP-XD','XFP','CFP','CFP2','CFP4','CDFP','DSFP')
            AND po.price < 15000
            AND t.part_number NOT ILIKE '%AOC%'
            AND t.part_number NOT ILIKE '%-DAC-%'
            AND (t.standard_name IS NULL OR (t.standard_name NOT ILIKE '%Switch%' AND t.standard_name NOT ILIKE '%InfiniBand%'))
          GROUP BY t.id, t.speed_gbps, t.form_factor
        )
        SELECT
          speed_gbps, form_factor,
          -- avg_30d / avg_prior_30d kept as column names for downstream compatibility,
          -- but they now carry MEDIAN-of-matched-SKU prices (only SKUs in both periods)
          PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY med_now)   AS avg_30d,
          PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY med_prior) AS avg_prior_30d,
          -- The real signal: median of per-SKU percentage deltas
          ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY (med_now - med_prior) / NULLIF(med_prior,0) * 100)::numeric, 1) AS sku_median_delta_pct,
          COUNT(*) AS obs_30d
        FROM per_sku
        WHERE med_now IS NOT NULL AND med_prior IS NOT NULL AND med_prior > 0
          AND obs_now >= 2 AND obs_prior >= 2
        GROUP BY speed_gbps, form_factor
        HAVING COUNT(*) >= 3
      `),
      // AI cluster demand by speed tier
      pool.query(`
        SELECT
          CASE
            WHEN COALESCE(network_speed, title, summary, '') ILIKE '%800G%' THEN 800
            WHEN COALESCE(network_speed, title, summary, '') ILIKE '%400G%' THEN 400
            WHEN COALESCE(network_speed, title, summary, '') ILIKE '%100G%' THEN 100
            ELSE 0
          END AS speed_tier,
          COALESCE(SUM(estimated_transceivers),0)::int AS total_tx,
          COUNT(*)::int AS cluster_count
        FROM ai_cluster_announcements
        WHERE announced_date >= NOW() - INTERVAL '90 days'
        GROUP BY 1
        HAVING COALESCE(SUM(estimated_transceivers),0) > 0
      `),
      // Hype phase per technology
      pool.query(`
        SELECT DISTINCT ON (technology)
          technology, hype_phase, hype_score
        FROM hype_cycle_analysis ORDER BY technology, computed_at DESC
      `),
      // Stock level distribution (in_stock vs out_of_stock)
      pool.query(`
        SELECT
          t.speed_gbps, t.form_factor,
          COUNT(*) FILTER (WHERE so.stock_level = 'out_of_stock')::int AS out_of_stock,
          COUNT(*) FILTER (WHERE so.stock_level = 'in_stock')::int      AS in_stock,
          COUNT(*)::int AS total_obs
        FROM stock_observations so
        JOIN transceivers t ON t.id = so.transceiver_id
        WHERE so.observed_at >= NOW() - INTERVAL '14 days'
        GROUP BY t.speed_gbps, t.form_factor
        HAVING COUNT(*) >= 3
      `).catch(() => ({ rows: [] })),
    ]);

    type PriceRow = { speed_gbps: string; form_factor: string; avg_30d: string; avg_prior_30d: string; obs_30d: string; sku_median_delta_pct: string | null };
    type HypeRow = { technology: string; hype_phase: string; hype_score: string };
    type AiRow = { speed_tier: string; total_tx: string; cluster_count: string };
    type StockRow = { speed_gbps: string; form_factor: string; out_of_stock: string; in_stock: string; total_obs: string };

    const speedToHype = new Map<number, HypeRow>();
    for (const h of hypeData.rows as HypeRow[]) {
      const m = h.technology.match(/^(\d+(?:\.\d+)?)G/);
      if (m) speedToHype.set(parseFloat(m[1]), h);
    }

    const aiBySpeed = new Map<number, AiRow>();
    for (const a of aiDemand.rows as AiRow[]) {
      aiBySpeed.set(parseFloat(a.speed_tier), a);
    }

    const stockByKey = new Map<string, StockRow>();
    for (const s of stockData.rows as StockRow[]) {
      stockByKey.set(`${s.speed_gbps}:${s.form_factor}`, s);
    }

    const RISKY_PHASES = new Set(["peak_inflated_expectations","slope_enlightenment","plateau_productivity"]);

    const signals = (priceSignals.rows as PriceRow[])
      .map((r) => {
        const speed = parseFloat(r.speed_gbps);
        // Prefer the per-SKU median delta (composition-bias-free); fall back to aggregate
        const priceUp = r.sku_median_delta_pct != null
          ? parseFloat(r.sku_median_delta_pct)
          : (r.avg_30d && r.avg_prior_30d
              ? ((parseFloat(r.avg_30d) - parseFloat(r.avg_prior_30d)) / parseFloat(r.avg_prior_30d)) * 100
              : 0);
        const hype = speedToHype.get(speed);
        const ai  = aiBySpeed.get(speed);
        const stock = stockByKey.get(`${r.speed_gbps}:${r.form_factor}`);

        let activeSignals = 0;
        const reasons: string[] = [];

        if (priceUp > 5)  { activeSignals++; reasons.push(`Price +${Math.round(priceUp)}% (30d)`); }
        if (hype && RISKY_PHASES.has(hype.hype_phase)) { activeSignals++; reasons.push(`Hype: ${hype.hype_phase.replace(/_/g,' ')}`); }
        if (ai && parseInt(ai.total_tx) > 50000) { activeSignals++; reasons.push(`AI demand: ${parseInt(ai.total_tx).toLocaleString()} tx in 90d`); }
        if (stock && parseInt(stock.out_of_stock) > parseInt(stock.in_stock)) { activeSignals++; reasons.push(`Stock pressure: ${stock.out_of_stock}/${stock.total_obs} vendors OOS`); }

        const severity = activeSignals >= 3 ? "critical" : activeSignals === 2 ? "warning" : activeSignals === 1 ? "watch" : "ok";
        return {
          speed_gbps: r.speed_gbps, form_factor: r.form_factor,
          avg_30d: r.avg_30d, avg_prior_30d: r.avg_prior_30d,
          price_momentum_pct: Math.round(priceUp),
          hype_phase: hype?.hype_phase || null,
          hype_score: hype ? parseFloat(hype.hype_score) : null,
          ai_demand_tx: ai ? parseInt(ai.total_tx) : 0,
          activeSignals, severity, reasons,
        };
      })
      .filter((r) => r.activeSignals >= 1)
      .sort((a, b) => b.activeSignals - a.activeSignals || b.price_momentum_pct - a.price_momentum_pct);

    res.json({ success: true, signals, criticalCount: signals.filter(s => s.severity === "critical").length });
  } catch (err) {
    res.status(500).json({ error: String(err) });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/lead-times — Rolling lead-time trends per vendor/speed
// Query params: form_factor, speed_gbps, days (default 90), limit (default 20)
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/lead-times", async (req: Request, res: Response) => {
  const days  = Math.min(parseInt(req.query.days  as string) || 90, 365);
  const limit = Math.min(parseInt(req.query.limit as string) || 20,  50);
  const ff    = req.query.form_factor as string | undefined;
  const spd   = req.query.speed_gbps as string | undefined;

  try {
    const [weekly, summary, concentration] = await Promise.all([
      // Weekly avg lead time per vendor × speed tier
      pool.query(`
        SELECT
          v.name                                                AS vendor_name,
          v.id::text                                           AS vendor_id,
          t.speed_gbps::text,
          t.form_factor,
          DATE_TRUNC('week', ss.scraped_at)::date             AS week,
          ROUND(AVG(ss.lead_time_days)::numeric, 1)           AS avg_lead_days,
          ROUND(MIN(ss.lead_time_days)::numeric, 1)           AS min_lead_days,
          ROUND(MAX(ss.lead_time_days)::numeric, 1)           AS max_lead_days,
          COUNT(*)::int                                        AS observations
        FROM stock_snapshots ss
        JOIN transceivers t ON t.id = ss.transceiver_id
        JOIN vendors v      ON v.id = ss.source_vendor_id
        WHERE ss.scraped_at >= NOW() - INTERVAL '${days} days'
          AND ss.lead_time_days IS NOT NULL
          AND ss.lead_time_days > 0
          ${ff  ? `AND t.form_factor = '${ff.replace(/'/g,"''")}'`  : ""}
          ${spd ? `AND t.speed_gbps  = ${parseFloat(spd)}`         : ""}
        GROUP BY v.name, v.id, t.speed_gbps, t.form_factor, DATE_TRUNC('week', ss.scraped_at)
        ORDER BY week DESC, avg_lead_days DESC
        LIMIT 500
      `),

      // Overall summary: current vs prior-period avg per vendor
      pool.query(`
        WITH cur AS (
          SELECT
            v.name AS vendor_name, v.id::text AS vendor_id,
            ROUND(AVG(ss.lead_time_days)::numeric, 1) AS avg_days,
            COUNT(*)::int AS obs
          FROM stock_snapshots ss
          JOIN vendors v ON v.id = ss.source_vendor_id
          JOIN transceivers t ON t.id = ss.transceiver_id
          WHERE ss.scraped_at >= NOW() - INTERVAL '30 days'
            AND ss.lead_time_days > 0
            ${ff  ? `AND t.form_factor = '${ff.replace(/'/g,"''")}'`  : ""}
            ${spd ? `AND t.speed_gbps  = ${parseFloat(spd)}`         : ""}
          GROUP BY v.name, v.id
        ),
        prior AS (
          SELECT v.id::text AS vendor_id,
            ROUND(AVG(ss.lead_time_days)::numeric, 1) AS avg_days
          FROM stock_snapshots ss
          JOIN vendors v ON v.id = ss.source_vendor_id
          JOIN transceivers t ON t.id = ss.transceiver_id
          WHERE ss.scraped_at >= NOW() - INTERVAL '60 days'
            AND ss.scraped_at <  NOW() - INTERVAL '30 days'
            AND ss.lead_time_days > 0
            ${ff  ? `AND t.form_factor = '${ff.replace(/'/g,"''")}'`  : ""}
            ${spd ? `AND t.speed_gbps  = ${parseFloat(spd)}`         : ""}
          GROUP BY v.id
        )
        SELECT
          c.vendor_name, c.vendor_id,
          c.avg_days    AS current_30d_avg,
          p.avg_days    AS prior_30d_avg,
          ROUND((c.avg_days - COALESCE(p.avg_days, c.avg_days))::numeric, 1) AS delta_days,
          c.obs
        FROM cur c
        LEFT JOIN prior p ON p.vendor_id = c.vendor_id
        ORDER BY c.avg_days DESC
        LIMIT ${limit}
      `),

      // Speed-tier breakdown — which form factors have longest lead times right now
      pool.query(`
        SELECT
          t.speed_gbps::text, t.form_factor,
          ROUND(AVG(ss.lead_time_days)::numeric, 1) AS avg_lead_days,
          COUNT(DISTINCT ss.source_vendor_id)::int  AS vendors_reporting,
          COUNT(*)::int                             AS total_obs
        FROM stock_snapshots ss
        JOIN transceivers t ON t.id = ss.transceiver_id
        WHERE ss.scraped_at >= NOW() - INTERVAL '30 days'
          AND ss.lead_time_days > 0
        GROUP BY t.speed_gbps, t.form_factor
        HAVING COUNT(*) >= 3
        ORDER BY avg_lead_days DESC
        LIMIT 20
      `),
    ]);

    res.json({
      success: true,
      filters: { days, form_factor: ff || null, speed_gbps: spd || null },
      weekly_trend: weekly.rows,
      vendor_summary: summary.rows,
      speed_tier_breakdown: concentration.rows,
    });
  } catch (err) {
    res.status(500).json({ error: String(err) });
  }
});

// ─────────────────────────────────────────────────────────────────────────────
// GET /api/procurement/supply-concentration — Single-vendor dependency risk
// Flags SKUs where >70% of price observations come from one vendor (30d)
// ─────────────────────────────────────────────────────────────────────────────
procurementRouter.get("/supply-concentration", async (_req: Request, res: Response) => {
  try {
    const result = await pool.query(`
      WITH obs_30d AS (
        SELECT
          po.transceiver_id,
          po.source_vendor_id,
          COUNT(*) AS vendor_obs
        FROM price_observations po
        WHERE po.time >= NOW() - INTERVAL '30 days'
          AND po.price > 0
          AND COALESCE(po.is_anomalous, false) = false
        GROUP BY po.transceiver_id, po.source_vendor_id
      ),
      totals AS (
        SELECT transceiver_id, SUM(vendor_obs) AS total_obs
        FROM obs_30d GROUP BY transceiver_id
      ),
      ranked AS (
        SELECT
          o.transceiver_id,
          o.source_vendor_id,
          o.vendor_obs,
          t.total_obs,
          ROUND((o.vendor_obs::numeric / NULLIF(t.total_obs,0)) * 100, 1) AS share_pct,
          ROW_NUMBER() OVER (PARTITION BY o.transceiver_id ORDER BY o.vendor_obs DESC) AS rnk
        FROM obs_30d o JOIN totals t ON t.transceiver_id = o.transceiver_id
      )
      SELECT
        tx.id::text, tx.part_number, tx.form_factor,
        tx.speed_gbps::text,
        tx.standard_name,
        v.name AS dominant_vendor,
        r.share_pct,
        r.total_obs::int,
        r.vendor_obs::int AS dominant_obs,
        CASE
          WHEN r.share_pct >= 90 THEN 'critical'
          WHEN r.share_pct >= 75 THEN 'high'
          ELSE 'medium'
        END AS risk_level
      FROM ranked r
      JOIN transceivers tx ON tx.id = r.transceiver_id
      JOIN vendors v        ON v.id  = r.source_vendor_id
      WHERE r.rnk = 1
        AND r.share_pct >= 70
        AND r.total_obs  >= 5
      ORDER BY r.share_pct DESC
      LIMIT 50
    `);

    const rows = result.rows;
    res.json({
      success: true,
      concentrated: rows,
      stats: {
        total_at_risk: rows.length,
        critical: rows.filter(r => r.risk_level === "critical").length,
        high:     rows.filter(r => r.risk_level === "high").length,
        medium:   rows.filter(r => r.risk_level === "medium").length,
      },
    });
  } catch (err) {
    res.status(500).json({ error: String(err) });
  }
});

// GET /api/procurement/price-movers — SKUs with biggest price delta vs prior period
procurementRouter.get("/price-movers", async (req: Request, res: Response) => {
  const days = Math.min(parseInt(req.query.days as string) || 7, 90);
  const limit = Math.min(parseInt(req.query.limit as string) || 20, 50);

  try {
    const result = await pool.query(`
      WITH cur AS (
        -- Group by part_number+source_vendor+currency to avoid duplicates from multiple
        -- vendor-OEM transceiver_ids with the same part number.
        -- Use PERCENTILE_CONT (median) to suppress multi-tier list-price noise
        -- (e.g. Mouser 1x/10x/100x tiers appearing as price swings).
        SELECT t.part_number, po.source_vendor_id, po.currency,
               PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY po.price) AS med_price,
               COUNT(*)    AS obs
        FROM price_observations po
        JOIN transceivers t ON t.id = po.transceiver_id
        WHERE po.time >= NOW() - INTERVAL '${days} days'
          AND po.price > 0 AND COALESCE(po.is_anomalous, false) = false
        GROUP BY t.part_number, po.source_vendor_id, po.currency
      ),
      prior AS (
        SELECT t.part_number, po.source_vendor_id,
               PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY po.price) AS med_price,
               COUNT(*) AS obs
        FROM price_observations po
        JOIN transceivers t ON t.id = po.transceiver_id
        WHERE po.time >= NOW() - INTERVAL '${days * 2} days'
          AND po.time <   NOW() - INTERVAL '${days} days'
          AND po.price > 0 AND COALESCE(po.is_anomalous, false) = false
        GROUP BY t.part_number, po.source_vendor_id
      ),
      ref_tx AS (
        -- pick one canonical transceiver_id per part_number for metadata
        SELECT DISTINCT ON (part_number) id, part_number, form_factor, speed_gbps, standard_name
        FROM transceivers ORDER BY part_number, id
      )
      SELECT
        ref.id, ref.part_number, ref.form_factor,
        ref.speed_gbps::text            AS speed_gbps,
        ref.standard_name,
        sv.name                         AS vendor_name,
        ROUND(c.med_price::numeric, 2)  AS current_avg,
        ROUND(p.med_price::numeric, 2)  AS prior_avg,
        ROUND(((c.med_price - p.med_price) / NULLIF(p.med_price, 0) * 100)::numeric, 1) AS delta_pct,
        c.currency,
        (c.obs + p.obs)::int            AS observations
      FROM cur c
      JOIN prior p  ON p.part_number       = c.part_number
                   AND p.source_vendor_id  = c.source_vendor_id
      JOIN ref_tx ref ON ref.part_number   = c.part_number
      JOIN vendors sv     ON sv.id         = c.source_vendor_id
      -- cv_filter: exclude SKUs where the source has high price variance across the full
      -- 2*days window (e.g. Mouser quantity-tier noise). CV > 0.35 = unreliable source.
      JOIN (
        SELECT t2.part_number, po2.source_vendor_id,
               STDDEV(po2.price) / NULLIF(AVG(po2.price), 0) AS cv
        FROM price_observations po2
        JOIN transceivers t2 ON t2.id = po2.transceiver_id
        WHERE po2.time >= NOW() - INTERVAL '${days * 2} days'
          AND po2.price > 0 AND COALESCE(po2.is_anomalous, false) = false
        GROUP BY t2.part_number, po2.source_vendor_id
        HAVING COUNT(*) >= 2
      ) cv_filter
        ON cv_filter.part_number      = c.part_number
       AND cv_filter.source_vendor_id = c.source_vendor_id
      WHERE ABS((c.med_price - p.med_price) / NULLIF(p.med_price, 0) * 100) >= 2
        AND (c.obs + p.obs) >= 4
        AND COALESCE(cv_filter.cv, 0) <= 0.35
      ORDER BY ABS((c.med_price - p.med_price) / NULLIF(p.med_price, 0) * 100) DESC
      LIMIT ${limit * 2}
    `);

    const rows = result.rows;
    const gainers = rows.filter((r) => parseFloat(r.delta_pct) > 0).slice(0, limit);
    const losers  = rows.filter((r) => parseFloat(r.delta_pct) < 0).slice(0, limit);

    const avgOf = (arr: typeof gainers, key: string) =>
      arr.length ? Math.round(arr.reduce((s, r) => s + parseFloat(r[key]), 0) / arr.length * 10) / 10 : 0;

    res.json({
      success: true,
      days,
      gainers,
      losers,
      stats: {
        totalMovers: rows.length,
        gainersCount: gainers.length,
        losersCount:  losers.length,
        avgGainPct:   avgOf(gainers, "delta_pct"),
        avgLossPct:   avgOf(losers,  "delta_pct"),
      },
    });
  } catch (err) {
    res.status(500).json({ error: String(err) });
  }
});
