import express from 'express';
import db from '../utils/db.js';
import { businessTodayStr, businessDateOf, businessNow, formatDateCol } from '../../shared/business-time/index.js';
const router = express.Router();

// Helper function to execute queries using the connection pool
async function executeQuery(sql, params = []) {
    const pool = db.getPool();
    if (!pool) {
        throw new Error('Database pool not initialized');
    }
    return pool.query(sql, params);
}

// Identity probe for the small dashboard / kiosk flow. Lives under /api/dashboard/*
// so it sits behind Caddy's office-IP `client_ip` allowlist — the office kiosk can
// reach it without a token, while non-office requests must carry a valid Bearer.
// Reaches this handler only if shared `authenticate` middleware populated req.user.
router.get('/whoami', (req, res) => {
    res.json({
        authenticated: !!req.user,
        is_kiosk: !!req.user?.is_kiosk,
    });
});

// GET /dashboard/stock/metrics
// Returns brand-level stock metrics
router.get('/stock/metrics', async (req, res) => {
    try {
        // Get stock metrics
        const { rows: [stockMetrics] } = await executeQuery(`
            SELECT 
                COALESCE(COUNT(*), 0)::integer as total_products,
                COALESCE(COUNT(CASE WHEN current_stock > 0 THEN 1 END), 0)::integer as products_in_stock,
                COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock END), 0)::integer as total_units,
                ROUND(COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock_cost END), 0)::numeric, 2) as total_cost,
                ROUND(COALESCE(SUM(CASE WHEN current_stock > 0 THEN current_stock_retail END), 0)::numeric, 2) as total_retail
            FROM product_metrics
            WHERE is_visible = true
        `);

        // Get brand stock values with Other category
        const { rows: brandValues } = await executeQuery(`
            WITH brand_totals AS (
                SELECT
                    COALESCE(brand, 'Unbranded') as brand,
                    COUNT(DISTINCT pid)::integer as variant_count,
                    COALESCE(SUM(current_stock), 0)::integer as stock_units,
                    ROUND(COALESCE(SUM(current_stock_cost), 0)::numeric, 2) as stock_cost,
                    ROUND(COALESCE(SUM(current_stock_retail), 0)::numeric, 2) as stock_retail
                FROM product_metrics
                WHERE current_stock > 0
                    AND is_visible = true
                GROUP BY COALESCE(brand, 'Unbranded')
                HAVING ROUND(COALESCE(SUM(current_stock_cost), 0)::numeric, 2) > 0
            ),
            other_brands AS (
                SELECT 
                    'Other' as brand,
                    SUM(variant_count)::integer as variant_count,
                    SUM(stock_units)::integer as stock_units,
                    ROUND(SUM(stock_cost)::numeric, 2) as stock_cost,
                    ROUND(SUM(stock_retail)::numeric, 2) as stock_retail
                FROM brand_totals
                WHERE stock_cost <= 5000
            ),
            main_brands AS (
                SELECT *
                FROM brand_totals
                WHERE stock_cost > 5000
            ),
            combined_results AS (
                SELECT * FROM main_brands
                UNION ALL
                SELECT * FROM other_brands
                WHERE stock_cost > 0
            )
            SELECT * FROM combined_results
            ORDER BY CASE WHEN brand = 'Other' THEN 1 ELSE 0 END, stock_cost DESC
        `);

        // Stock breakdown by lifecycle phase (lifecycle_phase populated by update_lifecycle_forecasts.sql)
        const { rows: phaseStock } = await executeQuery(`
            SELECT
                COALESCE(pm.lifecycle_phase, 'unknown') AS phase,
                COUNT(DISTINCT pm.pid)::integer AS products,
                COALESCE(SUM(pm.current_stock), 0)::integer AS units,
                ROUND(COALESCE(SUM(pm.current_stock_cost), 0)::numeric, 2) AS cost,
                ROUND(COALESCE(SUM(pm.current_stock_retail), 0)::numeric, 2) AS retail
            FROM product_metrics pm
            WHERE pm.is_visible = true AND pm.current_stock > 0
              AND COALESCE(pm.preorder_count, 0) = 0
            GROUP BY pm.lifecycle_phase
            ORDER BY cost DESC
        `);

        const phaseTotalCost = phaseStock.reduce((s, r) => s + (parseFloat(r.cost) || 0), 0);

        // Format the response with explicit type conversion
        const response = {
            totalProducts: parseInt(stockMetrics.total_products) || 0,
            productsInStock: parseInt(stockMetrics.products_in_stock) || 0,
            totalStockUnits: parseInt(stockMetrics.total_units) || 0,
            totalStockCost: parseFloat(stockMetrics.total_cost) || 0,
            totalStockRetail: parseFloat(stockMetrics.total_retail) || 0,
            brandStock: brandValues.map(v => ({
                brand: v.brand,
                variants: parseInt(v.variant_count) || 0,
                units: parseInt(v.stock_units) || 0,
                cost: parseFloat(v.stock_cost) || 0,
                retail: parseFloat(v.stock_retail) || 0
            })),
            phaseStock: phaseStock.filter(r => parseFloat(r.cost) > 0).map(r => ({
                phase: r.phase,
                products: parseInt(r.products) || 0,
                units: parseInt(r.units) || 0,
                cost: parseFloat(r.cost) || 0,
                retail: parseFloat(r.retail) || 0,
                percentage: phaseTotalCost > 0
                    ? parseFloat(((parseFloat(r.cost) / phaseTotalCost) * 100).toFixed(1))
                    : 0,
            })),
        };

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

// GET /dashboard/purchase/metrics
// Returns purchase order metrics by vendor
router.get('/purchase/metrics', async (req, res) => {
    try {
        // Active/overdue PO counts (staleness-filtered, from purchase_orders directly)
        const activePOs = await executeQuery(`
            WITH stale AS (
                SELECT po_id, pid
                FROM purchase_orders po
                WHERE po.status IN ('created', 'ordered', 'preordered', 'electronically_sent',
                                    'electronically_ready_send', 'receiving_started')
                  AND po.expected_date IS NOT NULL
                  AND po.expected_date < CURRENT_DATE - INTERVAL '90 days'
                  AND EXISTS (
                      SELECT 1 FROM purchase_orders newer
                      WHERE newer.pid = po.pid
                        AND newer.status NOT IN ('canceled', 'done')
                        AND COALESCE(newer.date_ordered, newer.date_created)
                            > COALESCE(po.date_ordered, po.date_created)
                  )
            )
            SELECT
                COUNT(DISTINCT po_id)::integer AS active_pos,
                COUNT(DISTINCT CASE WHEN expected_date < CURRENT_DATE THEN po_id END)::integer AS overdue_pos
            FROM purchase_orders po
            WHERE po.status NOT IN ('canceled', 'done')
              AND NOT EXISTS (SELECT 1 FROM stale s WHERE s.po_id = po.po_id AND s.pid = po.pid)
        `);
        const poMetrics = activePOs.rows[0];

        // On-order totals and vendor breakdown from product_metrics (FIFO-computed)
        // Consistent with Analytics and Pipeline components
        const { rows: vendorRows } = await executeQuery(`
            SELECT
                vendor,
                SUM(on_order_qty)::integer AS units,
                ROUND(SUM(on_order_cost)::numeric, 2) AS cost,
                ROUND(SUM(on_order_retail)::numeric, 2) AS retail,
                SUM(SUM(on_order_qty)::integer) OVER () AS total_units,
                ROUND(SUM(SUM(on_order_cost)) OVER ()::numeric, 2) AS total_cost,
                ROUND(SUM(SUM(on_order_retail)) OVER ()::numeric, 2) AS total_retail
            FROM product_metrics
            WHERE is_visible = true AND on_order_qty > 0
            GROUP BY vendor
            ORDER BY cost DESC
        `);

        const vendorOrders = vendorRows.map(v => ({
            vendor: v.vendor,
            orders: 0,
            units: parseInt(v.units) || 0,
            cost: parseFloat(v.cost) || 0,
            retail: parseFloat(v.retail) || 0
        }));

        const firstRow = vendorRows[0];
        const onOrderUnits = firstRow ? parseInt(firstRow.total_units) || 0 : 0;
        const onOrderCost = firstRow ? parseFloat(firstRow.total_cost) || 0 : 0;
        const onOrderRetail = firstRow ? parseFloat(firstRow.total_retail) || 0 : 0;

        // Format response to match PurchaseMetricsData interface
        const response = {
            activePurchaseOrders: parseInt(poMetrics.active_pos) || 0,
            overduePurchaseOrders: parseInt(poMetrics.overdue_pos) || 0,
            onOrderUnits,
            onOrderCost,
            onOrderRetail,
            vendorOrders
        };

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

// GET /dashboard/replenishment/metrics
// Returns replenishment needs by category
router.get('/replenishment/metrics', async (req, res) => {
    try {
        // Get summary metrics
        const { rows: [metrics] } = await executeQuery(`
            SELECT 
                COUNT(DISTINCT pm.pid)::integer as products_to_replenish,
                COALESCE(SUM(pm.replenishment_units), 0)::integer as total_units_needed,
                ROUND(COALESCE(SUM(pm.replenishment_cost), 0)::numeric, 2) as total_cost,
                ROUND(COALESCE(SUM(pm.replenishment_retail), 0)::numeric, 2) as total_retail
            FROM product_metrics pm
            WHERE pm.is_visible = true
                AND pm.is_replenishable = true
                AND (pm.status IN ('Critical', 'Reorder')
                    OR pm.current_stock < 0)
                AND pm.replenishment_units > 0
        `);

        // Get top variants to replenish
        const { rows: variants } = await executeQuery(`
            SELECT
                pm.pid,
                pm.title,
                pm.current_stock::integer as current_stock,
                pm.replenishment_units::integer as replenish_qty,
                ROUND(pm.replenishment_cost::numeric, 2) as replenish_cost,
                ROUND(pm.replenishment_retail::numeric, 2) as replenish_retail,
                pm.status,
                pm.planning_period_days::text as planning_period
            FROM product_metrics pm
            WHERE pm.is_visible = true
                AND pm.is_replenishable = true
                AND (pm.status IN ('Critical', 'Reorder')
                    OR pm.current_stock < 0)
                AND pm.replenishment_units > 0
            ORDER BY 
                CASE pm.status 
                    WHEN 'Critical' THEN 1 
                    WHEN 'Reorder' THEN 2 
                END,
                replenish_cost DESC
            LIMIT 5
        `);

        // Replenishment breakdown by lifecycle phase (lifecycle_phase on product_metrics)
        const { rows: phaseReplenish } = await executeQuery(`
            SELECT
                COALESCE(pm.lifecycle_phase, 'unknown') AS phase,
                COUNT(DISTINCT pm.pid)::integer AS products,
                COALESCE(SUM(pm.replenishment_units), 0)::integer AS units,
                ROUND(COALESCE(SUM(pm.replenishment_cost), 0)::numeric, 2) AS cost
            FROM product_metrics pm
            WHERE pm.is_visible = true
                AND pm.is_replenishable = true
                AND (pm.status IN ('Critical', 'Reorder') OR pm.current_stock < 0)
                AND pm.replenishment_units > 0
            GROUP BY pm.lifecycle_phase
            ORDER BY cost DESC
        `);

        const replenishTotalCost = phaseReplenish.reduce((s, r) => s + (parseFloat(r.cost) || 0), 0);

        // Format response
        const response = {
            productsToReplenish: parseInt(metrics.products_to_replenish) || 0,
            unitsToReplenish: parseInt(metrics.total_units_needed) || 0,
            replenishmentCost: parseFloat(metrics.total_cost) || 0,
            replenishmentRetail: parseFloat(metrics.total_retail) || 0,
            phaseBreakdown: phaseReplenish.filter(r => parseFloat(r.cost) > 0).map(r => ({
                phase: r.phase,
                products: parseInt(r.products) || 0,
                units: parseInt(r.units) || 0,
                cost: parseFloat(r.cost) || 0,
                percentage: replenishTotalCost > 0
                    ? parseFloat(((parseFloat(r.cost) / replenishTotalCost) * 100).toFixed(1))
                    : 0,
            })),
            topVariants: variants.map(v => ({
                id: v.pid,
                title: v.title,
                currentStock: parseInt(v.current_stock) || 0,
                replenishQty: parseInt(v.replenish_qty) || 0,
                replenishCost: parseFloat(v.replenish_cost) || 0,
                replenishRetail: parseFloat(v.replenish_retail) || 0,
                status: v.status,
                planningPeriod: v.planning_period
            }))
        };

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

// GET /dashboard/forecast/metrics
// Reads from product_forecasts table (lifecycle-aware forecasting pipeline).
// Falls back to velocity-based projection if forecast table is empty.
router.get('/forecast/metrics', async (req, res) => {
    // Business dates (Chicago calendar — see docs/TIME.md). Inputs may be
    // date-only strings or full instants.
    const startISO = req.query.startDate ? businessDateOf(req.query.startDate) : businessTodayStr();
    const endISO = req.query.endDate
        ? businessDateOf(req.query.endDate)
        : businessNow().plus({ days: 30 }).toISODate();
    const days = Math.max(1, Math.round((Date.parse(endISO) - Date.parse(startISO)) / (1000 * 60 * 60 * 24)));

    try {
        // Check if product_forecasts has data
        const { rows: [countRow] } = await executeQuery(
            `SELECT COUNT(*) AS cnt FROM product_forecasts WHERE forecast_date >= $1 LIMIT 1`,
            [startISO]
        );
        const hasForecastData = parseInt(countRow.cnt) > 0;

        if (hasForecastData) {
            // --- Read from lifecycle-aware forecast pipeline ---

            // Find the last date covered by product_forecasts
            const { rows: [horizonRow] } = await executeQuery(
                `SELECT MAX(forecast_date) AS max_date FROM product_forecasts`
            );
            const forecastHorizonISO = formatDateCol(horizonRow.max_date);
            const forecastHorizon = new Date(forecastHorizonISO + 'T00:00:00');
            const clampedEndISO = endISO <= forecastHorizonISO ? endISO : forecastHorizonISO;
            const needsExtrapolation = endISO > forecastHorizonISO;

            // Totals from actual forecast data (clamped to horizon)
            const { rows: [totals] } = await executeQuery(`
                SELECT
                    COALESCE(SUM(pf.forecast_units), 0) AS total_units,
                    COALESCE(SUM(pf.forecast_revenue), 0) AS total_revenue,
                    COUNT(DISTINCT pf.pid) FILTER (
                        WHERE pf.lifecycle_phase IN ('launch','decay','mature','preorder','slow_mover')
                    ) AS active_products,
                    COUNT(DISTINCT pf.pid) FILTER (
                        WHERE pf.forecast_method = 'lifecycle_curve'
                    ) AS curve_products
                FROM product_forecasts pf
                JOIN product_metrics pm ON pm.pid = pf.pid
                WHERE pm.is_visible = true
                  AND pf.forecast_date BETWEEN $1 AND $2
            `, [startISO, clampedEndISO]);

            const active = parseInt(totals.active_products) || 1;
            const curveProducts = parseInt(totals.curve_products) || 0;
            // NOTE: despite the name, this is "share of active products forecast via
            // lifecycle curves" (curve coverage), NOT a statistical confidence. It only
            // feeds a per-day tooltip field. See FORECAST_FIX_PLAN F9 (point 4).
            const confidenceLevel = parseFloat((curveProducts / active).toFixed(2));

            // Daily series from actual forecast
            const { rows: dailyRows } = await executeQuery(`
                SELECT pf.forecast_date AS date,
                    SUM(pf.forecast_units) AS units,
                    SUM(pf.forecast_revenue) AS revenue
                FROM product_forecasts pf
                JOIN product_metrics pm ON pm.pid = pf.pid
                WHERE pm.is_visible = true
                  AND pf.forecast_date BETWEEN $1 AND $2
                GROUP BY pf.forecast_date
                ORDER BY pf.forecast_date
            `, [startISO, clampedEndISO]);

            const dailyForecasts = dailyRows.map(d => ({
                date: formatDateCol(d.date),
                units: parseFloat(d.units) || 0,
                revenue: parseFloat(d.revenue) || 0,
                confidence: confidenceLevel,
            }));

            // Daily forecast broken down by lifecycle phase (for stacked chart)
            const { rows: dailyPhaseRows } = await executeQuery(`
                SELECT pf.forecast_date AS date,
                    COALESCE(SUM(pf.forecast_revenue) FILTER (WHERE pf.lifecycle_phase = 'preorder'), 0) AS preorder,
                    COALESCE(SUM(pf.forecast_revenue) FILTER (WHERE pf.lifecycle_phase = 'launch'), 0) AS launch,
                    COALESCE(SUM(pf.forecast_revenue) FILTER (WHERE pf.lifecycle_phase = 'decay'), 0) AS decay,
                    COALESCE(SUM(pf.forecast_revenue) FILTER (WHERE pf.lifecycle_phase = 'mature'), 0) AS mature,
                    COALESCE(SUM(pf.forecast_revenue) FILTER (WHERE pf.lifecycle_phase = 'slow_mover'), 0) AS slow_mover,
                    COALESCE(SUM(pf.forecast_revenue) FILTER (WHERE pf.lifecycle_phase = 'dormant'), 0) AS dormant
                FROM product_forecasts pf
                JOIN product_metrics pm ON pm.pid = pf.pid
                WHERE pm.is_visible = true
                  AND pf.forecast_date BETWEEN $1 AND $2
                GROUP BY pf.forecast_date
                ORDER BY pf.forecast_date
            `, [startISO, clampedEndISO]);

            // --- New product pipeline contribution ---
            // Average daily revenue from new product introductions (last 12 months).
            // Only used for EXTRAPOLATED days beyond the forecast horizon — within the
            // 90-day horizon, preorder/launch products are already forecast by lifecycle curves.
            const { rows: [pipeline] } = await executeQuery(`
                SELECT
                    COALESCE(AVG(monthly_revenue), 0) AS avg_monthly_revenue
                FROM (
                    SELECT DATE_TRUNC('month', pm.date_first_received) AS month,
                        COUNT(*) AS monthly_products,
                        SUM(pm.first_30_days_revenue) AS monthly_revenue
                    FROM product_metrics pm
                    WHERE pm.is_visible = true
                      AND pm.date_first_received >= NOW() - INTERVAL '12 months'
                      AND pm.date_first_received < DATE_TRUNC('month', NOW())
                    GROUP BY 1
                ) sub
            `);
            // Compute average product price for converting revenue to unit estimates
            const { rows: [priceRow] } = await executeQuery(`
                SELECT COALESCE(AVG(current_price) FILTER (WHERE current_price > 0 AND sales_30d > 0), 7) AS avg_price
                FROM product_metrics
                WHERE is_visible = true
            `);
            const avgPrice = parseFloat(priceRow.avg_price) || 7;

            // Daily new-product revenue = (avg products/month × avg 30d revenue/product) / 30
            const avgMonthlyRevenue = parseFloat(pipeline.avg_monthly_revenue) || 0;
            const newProductDailyRevenue = avgMonthlyRevenue / 30;
            const newProductDailyUnits = newProductDailyRevenue / avgPrice;

            let totalRevenue = dailyForecasts.reduce((sum, d) => sum + d.revenue, 0);
            let totalUnits = dailyForecasts.reduce((sum, d) => sum + d.units, 0);

            // --- Extrapolation beyond forecast horizon (rest-of-year) ---
            if (needsExtrapolation) {
                // Monthly seasonal indices from last 12 months of actual revenue
                const { rows: seasonalRows } = await executeQuery(`
                    SELECT EXTRACT(MONTH FROM o.date)::int AS month,
                        SUM(o.quantity * o.price) AS revenue
                    FROM orders o
                    WHERE o.canceled IS DISTINCT FROM TRUE
                      AND o.date >= NOW() - INTERVAL '12 months'
                    GROUP BY 1
                `);
                const monthlyRevenue = {};
                let totalMonthlyRev = 0;
                for (const r of seasonalRows) {
                    monthlyRevenue[r.month] = parseFloat(r.revenue) || 0;
                    totalMonthlyRev += monthlyRevenue[r.month];
                }
                const avgMonthRev = totalMonthlyRev / Math.max(Object.keys(monthlyRevenue).length, 1);
                const seasonalIndex = {};
                for (let m = 1; m <= 12; m++) {
                    seasonalIndex[m] = monthlyRevenue[m] ? monthlyRevenue[m] / avgMonthRev : 1.0;
                }

                // Baseline: avg daily revenue from last 7 days of forecast (mature tail)
                const tailDays = dailyForecasts.slice(-7);
                const baselineDaily = tailDays.length > 0
                    ? tailDays.reduce((s, d) => s + d.revenue, 0) / tailDays.length
                    : 0;

                // Generate estimated days beyond horizon
                const extraStart = new Date(forecastHorizon);
                extraStart.setDate(extraStart.getDate() + 1);
                const extraEnd = new Date(endISO + 'T00:00:00');

                for (let d = new Date(extraStart); d <= extraEnd; d.setDate(d.getDate() + 1)) {
                    const month = d.getMonth() + 1;
                    const seasonal = seasonalIndex[month] || 1.0;
                    // Beyond horizon: existing product tail + new product pipeline
                    const estRevenue = baselineDaily * seasonal + newProductDailyRevenue;
                    const estUnits = (baselineDaily * seasonal) / avgPrice + newProductDailyUnits;

                    dailyForecasts.push({
                        date: formatDateCol(d),
                        units: parseFloat(estUnits.toFixed(1)),
                        revenue: parseFloat(estRevenue.toFixed(2)),
                        confidence: 0, // lower confidence for extrapolated data
                        estimated: true,
                    });
                    totalRevenue += estRevenue;
                    totalUnits += estUnits;
                }
            }

            // Lifecycle phase breakdown (from actual forecast data only)
            const { rows: phaseRows } = await executeQuery(`
                SELECT pf.lifecycle_phase AS phase,
                    COUNT(DISTINCT pf.pid) AS products,
                    COALESCE(SUM(pf.forecast_units), 0) AS units,
                    COALESCE(SUM(pf.forecast_revenue), 0) AS revenue
                FROM product_forecasts pf
                JOIN product_metrics pm ON pm.pid = pf.pid
                WHERE pm.is_visible = true
                  AND pf.forecast_date BETWEEN $1 AND $2
                GROUP BY pf.lifecycle_phase
                ORDER BY revenue DESC
            `, [startISO, clampedEndISO]);

            const phaseTotal = phaseRows.reduce((s, r) => s + (parseFloat(r.revenue) || 0), 0);
            const phaseBreakdown = phaseRows
                .filter(r => parseFloat(r.revenue) > 0)
                .map(r => ({
                    phase: r.phase,
                    products: parseInt(r.products) || 0,
                    units: Math.round(parseFloat(r.units) || 0),
                    revenue: parseFloat(parseFloat(r.revenue).toFixed(2)),
                    percentage: phaseTotal > 0
                        ? parseFloat(((parseFloat(r.revenue) / phaseTotal) * 100).toFixed(1))
                        : 0,
                }));

            // Category breakdown (from actual forecast data only)
            const { rows: categoryRows } = await executeQuery(`
                WITH product_root_category AS (
                    SELECT DISTINCT ON (pf.pid)
                        pf.pid, ch.name AS category
                    FROM product_forecasts pf
                    JOIN product_metrics pm ON pm.pid = pf.pid
                    JOIN product_categories pc ON pc.pid = pf.pid
                    JOIN category_hierarchy ch ON ch.cat_id = pc.cat_id AND ch.level = 0
                    WHERE pm.is_visible = true
                      AND ch.name NOT IN ('Deals', 'Black Friday')
                      AND pf.forecast_date BETWEEN $1 AND $2
                    ORDER BY pf.pid, ch.name
                )
                SELECT prc.category,
                    SUM(pf.forecast_units) AS units,
                    SUM(pf.forecast_revenue) AS revenue
                FROM product_forecasts pf
                JOIN product_root_category prc ON prc.pid = pf.pid
                WHERE pf.forecast_date BETWEEN $1 AND $2
                GROUP BY prc.category
                ORDER BY revenue DESC
                LIMIT 8
            `, [startISO, clampedEndISO]);

            const dailyForecastsByPhase = dailyPhaseRows.map(d => ({
                date: formatDateCol(d.date),
                preorder: parseFloat(d.preorder) || 0,
                launch: parseFloat(d.launch) || 0,
                decay: parseFloat(d.decay) || 0,
                mature: parseFloat(d.mature) || 0,
                slow_mover: parseFloat(d.slow_mover) || 0,
                dormant: parseFloat(d.dormant) || 0,
            }));

            // Add extrapolated days to phase series (distribute proportionally using last phase ratios)
            if (needsExtrapolation && dailyForecastsByPhase.length > 0) {
                const lastPhaseDay = dailyForecastsByPhase[dailyForecastsByPhase.length - 1];
                const phases = ['preorder', 'launch', 'decay', 'mature', 'slow_mover', 'dormant'];
                const lastTotal = phases.reduce((s, p) => s + lastPhaseDay[p], 0);
                const phaseRatios = {};
                for (const p of phases) {
                    phaseRatios[p] = lastTotal > 0 ? lastPhaseDay[p] / lastTotal : 1 / phases.length;
                }
                // Match extrapolated days from dailyForecasts
                for (let i = dailyForecastsByPhase.length; i < dailyForecasts.length; i++) {
                    const dayRev = dailyForecasts[i].revenue;
                    const entry = { date: dailyForecasts[i].date };
                    for (const p of phases) {
                        entry[p] = parseFloat((dayRev * phaseRatios[p]).toFixed(2));
                    }
                    dailyForecastsByPhase.push(entry);
                }
            }

            return res.json({
                forecastSales: Math.round(totalUnits),
                forecastRevenue: parseFloat(totalRevenue.toFixed(2)),
                confidenceLevel,
                dailyForecasts,
                dailyForecastsByPhase,
                phaseBreakdown,
                categoryForecasts: categoryRows.map(c => ({
                    category: c.category,
                    units: Math.round(parseFloat(c.units)),
                    revenue: parseFloat(parseFloat(c.revenue).toFixed(2)),
                })),
            });
        }

        // --- Fallback: velocity-based projection (no forecast data yet) ---
        const { rows: [totals] } = await executeQuery(`
            SELECT
                COALESCE(SUM(sales_velocity_daily), 0) AS daily_units,
                COALESCE(SUM(sales_velocity_daily * current_price), 0) AS daily_revenue,
                COUNT(*) FILTER (WHERE sales_velocity_daily > 0) AS active_products
            FROM product_metrics
            WHERE is_visible = true AND sales_velocity_daily > 0
        `);

        const dailyUnits = parseFloat(totals.daily_units) || 0;
        const dailyRevenue = parseFloat(totals.daily_revenue) || 0;

        const dailyForecasts = [];
        const fallbackStart = new Date(startISO + 'T00:00:00'); // local midnight — round-trips via formatDateCol
        for (let i = 0; i < days; i++) {
            const d = new Date(fallbackStart);
            d.setDate(fallbackStart.getDate() + i);
            dailyForecasts.push({
                date: formatDateCol(d),
                units: parseFloat(dailyUnits.toFixed(1)),
                revenue: parseFloat(dailyRevenue.toFixed(2)),
                confidence: 0,
            });
        }

        const { rows: categoryRows } = await executeQuery(`
            WITH product_root_category AS (
                SELECT DISTINCT ON (pm.pid) pm.pid,
                    pm.sales_velocity_daily, pm.current_price,
                    ch.name AS category
                FROM product_metrics pm
                JOIN product_categories pc ON pc.pid = pm.pid
                JOIN category_hierarchy ch ON ch.cat_id = pc.cat_id AND ch.level = 0
                WHERE pm.is_visible = true AND pm.sales_velocity_daily > 0
                  AND ch.name NOT IN ('Deals', 'Black Friday')
                ORDER BY pm.pid, ch.name
            )
            SELECT category,
                ROUND(SUM(sales_velocity_daily)::numeric, 1) AS daily_units,
                ROUND(SUM(sales_velocity_daily * current_price)::numeric, 2) AS daily_revenue
            FROM product_root_category
            GROUP BY category ORDER BY daily_revenue DESC LIMIT 8
        `);

        res.json({
            forecastSales: Math.round(dailyUnits * days),
            forecastRevenue: parseFloat((dailyRevenue * days).toFixed(2)),
            confidenceLevel: 0,
            dailyForecasts,
            categoryForecasts: categoryRows.map(c => ({
                category: c.category,
                units: Math.round(parseFloat(c.daily_units) * days),
                revenue: parseFloat((parseFloat(c.daily_revenue) * days).toFixed(2)),
            })),
        });
    } catch (err) {
        console.error('Error fetching forecast metrics:', err);
        res.status(500).json({ error: 'Failed to fetch forecast metrics' });
    }
});

// GET /dashboard/forecast/accuracy
// Returns forecast accuracy metrics computed by the forecast engine.
// Reads from forecast_accuracy table (populated after each forecast run).
router.get('/forecast/accuracy', async (req, res) => {
    try {
        // Check if forecast_accuracy table exists and has data
        const { rows: [tableCheck] } = await executeQuery(`
            SELECT EXISTS (
                SELECT 1 FROM information_schema.tables
                WHERE table_name = 'forecast_accuracy'
            ) AS exists
        `);

        if (!tableCheck.exists) {
            return res.json({ hasData: false, message: 'Accuracy data not yet available' });
        }

        // Get the latest run that has accuracy data
        const { rows: runRows } = await executeQuery(`
            SELECT DISTINCT fa.run_id, fr.finished_at
            FROM forecast_accuracy fa
            JOIN forecast_runs fr ON fr.id = fa.run_id
            ORDER BY fr.finished_at DESC
            LIMIT 1
        `);

        if (runRows.length === 0) {
            return res.json({ hasData: false, message: 'No accuracy data computed yet' });
        }

        const latestRunId = runRows[0].run_id;
        const computedAt = runRows[0].finished_at;

        // Count days of history available
        const { rows: [historyInfo] } = await executeQuery(`
            SELECT
                COUNT(DISTINCT forecast_date) AS days_of_history,
                MIN(forecast_date) AS earliest_date,
                MAX(forecast_date) AS latest_date
            FROM product_forecasts_history
        `);

        // Fetch all accuracy metrics for the latest run
        const { rows: metrics } = await executeQuery(`
            SELECT metric_type, dimension_value, sample_size,
                total_actual_units, total_forecast_units,
                mae, wmape, bias, rmse, naive_wmape, fva
            FROM forecast_accuracy
            WHERE run_id = $1
            ORDER BY metric_type, dimension_value
        `, [latestRunId]);

        // Shared shaping for an "overall"-style aggregate row (daily or weekly grain).
        const shapeOverall = (m) => m ? {
            sampleSize: parseInt(m.sample_size),
            totalActual: parseFloat(m.total_actual_units) || 0,
            totalForecast: parseFloat(m.total_forecast_units) || 0,
            mae: m.mae != null ? parseFloat(parseFloat(m.mae).toFixed(4)) : null,
            wmape: m.wmape != null ? parseFloat((parseFloat(m.wmape) * 100).toFixed(1)) : null,
            bias: m.bias != null ? parseFloat(parseFloat(m.bias).toFixed(4)) : null,
            rmse: m.rmse != null ? parseFloat(parseFloat(m.rmse).toFixed(4)) : null,
            naiveWmape: m.naive_wmape != null ? parseFloat((parseFloat(m.naive_wmape) * 100).toFixed(1)) : null,
            fva: m.fva != null ? parseFloat(parseFloat(m.fva).toFixed(3)) : null,
        } : null;

        // Organize into response structure
        const overall = metrics.find(m => m.metric_type === 'overall' && m.dimension_value === 'all')
        const overallInclDormant = metrics.find(m => m.metric_type === 'overall' && m.dimension_value === 'all_incl_dormant')
        const overallWeekly = metrics.find(m => m.metric_type === 'overall_weekly');
        const byPhase = metrics
            .filter(m => m.metric_type === 'by_phase')
            .map(m => ({
                phase: m.dimension_value,
                sampleSize: parseInt(m.sample_size),
                totalActual: parseFloat(m.total_actual_units) || 0,
                totalForecast: parseFloat(m.total_forecast_units) || 0,
                mae: m.mae != null ? parseFloat(parseFloat(m.mae).toFixed(4)) : null,
                wmape: m.wmape != null ? parseFloat((parseFloat(m.wmape) * 100).toFixed(1)) : null,
                bias: m.bias != null ? parseFloat(parseFloat(m.bias).toFixed(4)) : null,
                rmse: m.rmse != null ? parseFloat(parseFloat(m.rmse).toFixed(4)) : null,
                naiveWmape: m.naive_wmape != null ? parseFloat((parseFloat(m.naive_wmape) * 100).toFixed(1)) : null,
                fva: m.fva != null ? parseFloat(parseFloat(m.fva).toFixed(3)) : null,
            }))
            .sort((a, b) => (b.totalActual || 0) - (a.totalActual || 0));

        const byLeadTime = metrics
            .filter(m => m.metric_type === 'by_lead_time')
            .map(m => ({
                bucket: m.dimension_value,
                sampleSize: parseInt(m.sample_size),
                mae: m.mae != null ? parseFloat(parseFloat(m.mae).toFixed(4)) : null,
                wmape: m.wmape != null ? parseFloat((parseFloat(m.wmape) * 100).toFixed(1)) : null,
                bias: m.bias != null ? parseFloat(parseFloat(m.bias).toFixed(4)) : null,
                rmse: m.rmse != null ? parseFloat(parseFloat(m.rmse).toFixed(4)) : null,
            }))
            .sort((a, b) => {
                const order = { '1-7d': 0, '8-14d': 1, '15-30d': 2, '31-60d': 3, '61-90d': 4 };
                return (order[a.bucket] ?? 99) - (order[b.bucket] ?? 99);
            });

        const byMethod = metrics
            .filter(m => m.metric_type === 'by_method')
            .map(m => ({
                method: m.dimension_value,
                sampleSize: parseInt(m.sample_size),
                mae: m.mae != null ? parseFloat(parseFloat(m.mae).toFixed(4)) : null,
                wmape: m.wmape != null ? parseFloat((parseFloat(m.wmape) * 100).toFixed(1)) : null,
                bias: m.bias != null ? parseFloat(parseFloat(m.bias).toFixed(4)) : null,
            }));

        const dailyTrend = metrics
            .filter(m => m.metric_type === 'daily')
            .map(m => ({
                date: m.dimension_value,
                mae: m.mae != null ? parseFloat(parseFloat(m.mae).toFixed(4)) : null,
                wmape: m.wmape != null ? parseFloat((parseFloat(m.wmape) * 100).toFixed(1)) : null,
                bias: m.bias != null ? parseFloat(parseFloat(m.bias).toFixed(4)) : null,
            }))
            .sort((a, b) => a.date.localeCompare(b.date));

        // Historical accuracy trend (across runs)
        const { rows: trendRows } = await executeQuery(`
            SELECT fa.run_id, fr.finished_at::date AS run_date,
                fa.mae, fa.wmape, fa.bias, fa.rmse, fa.sample_size
            FROM forecast_accuracy fa
            JOIN forecast_runs fr ON fr.id = fa.run_id
            WHERE fa.metric_type = 'overall'
              AND fa.dimension_value = 'all'
            ORDER BY fr.finished_at
        `);

        const accuracyTrend = trendRows.map(r => ({
            date: formatDateCol(r.run_date),
            mae: r.mae != null ? parseFloat(parseFloat(r.mae).toFixed(4)) : null,
            wmape: r.wmape != null ? parseFloat((parseFloat(r.wmape) * 100).toFixed(1)) : null,
            bias: r.bias != null ? parseFloat(parseFloat(r.bias).toFixed(4)) : null,
            sampleSize: parseInt(r.sample_size),
        }));

        // Weekly-grain trend across runs (starts empty for old runs that predate
        // the overall_weekly metric — that's expected, no backfill). F9.
        const { rows: weeklyTrendRows } = await executeQuery(`
            SELECT fr.finished_at::date AS run_date,
                fa.wmape, fa.naive_wmape, fa.fva, fa.sample_size
            FROM forecast_accuracy fa
            JOIN forecast_runs fr ON fr.id = fa.run_id
            WHERE fa.metric_type = 'overall_weekly'
              AND fa.dimension_value = 'all'
            ORDER BY fr.finished_at
        `);

        const accuracyTrendWeekly = weeklyTrendRows.map(r => ({
            date: formatDateCol(r.run_date),
            wmape: r.wmape != null ? parseFloat((parseFloat(r.wmape) * 100).toFixed(1)) : null,
            naiveWmape: r.naive_wmape != null ? parseFloat((parseFloat(r.naive_wmape) * 100).toFixed(1)) : null,
            fva: r.fva != null ? parseFloat(parseFloat(r.fva).toFixed(3)) : null,
            sampleSize: parseInt(r.sample_size),
        }));

        res.json({
            hasData: true,
            computedAt,
            daysOfHistory: parseInt(historyInfo.days_of_history) || 0,
            historyRange: {
                from: formatDateCol(historyInfo.earliest_date),
                to: formatDateCol(historyInfo.latest_date),
            },
            overall: shapeOverall(overall),
            overallInclDormant: shapeOverall(overallInclDormant),
            overallWeekly: shapeOverall(overallWeekly),
            byPhase,
            byLeadTime,
            byMethod,
            dailyTrend,
            accuracyTrend,
            accuracyTrendWeekly,
        });
    } catch (err) {
        console.error('Error fetching forecast accuracy:', err);
        res.status(500).json({ error: 'Failed to fetch forecast accuracy' });
    }
});

// GET /dashboard/overstock/metrics
// Returns overstock metrics by category
router.get('/overstock/metrics', async (req, res) => {
    try {
        // Check if we have any products with Overstock status
        const { rows: [countCheck] } = await executeQuery(`
            SELECT COUNT(*) as overstock_count FROM product_metrics WHERE status = 'Overstock' AND is_visible = true
        `);
        
        // If no overstock products, return empty metrics
        if (parseInt(countCheck.overstock_count) === 0) {
            return res.json({
                overstockedProducts: 0,
                totalExcessUnits: 0,
                totalExcessCost: 0,
                totalExcessRetail: 0,
                categoryData: []
            });
        }
        
        // Get summary metrics in a simpler, more direct query
        const { rows: [summaryMetrics] } = await executeQuery(`
            SELECT 
                COUNT(DISTINCT pid)::integer as total_overstocked,
                SUM(overstocked_units)::integer as total_excess_units,
                ROUND(SUM(overstocked_cost)::numeric, 2) as total_excess_cost,
                ROUND(SUM(overstocked_retail)::numeric, 2) as total_excess_retail
            FROM product_metrics
            WHERE status = 'Overstock'
                AND is_visible = true
        `);
        
        // Get category breakdowns separately
        const { rows: categoryData } = await executeQuery(`
            SELECT
                c.name as category_name,
                COUNT(DISTINCT pm.pid)::integer as overstocked_products,
                SUM(pm.overstocked_units)::integer as total_excess_units,
                ROUND(SUM(pm.overstocked_cost)::numeric, 2) as total_excess_cost,
                ROUND(SUM(pm.overstocked_retail)::numeric, 2) as total_excess_retail
            FROM categories c
            JOIN product_categories pc ON c.cat_id = pc.cat_id
            JOIN product_metrics pm ON pc.pid = pm.pid
            WHERE pm.status = 'Overstock'
                AND pm.is_visible = true
            GROUP BY c.name
            ORDER BY total_excess_cost DESC
            LIMIT 8
        `);

        // Overstock breakdown by lifecycle phase
        const { rows: phaseOverstock } = await executeQuery(`
            SELECT
                COALESCE(pm.lifecycle_phase, 'unknown') AS phase,
                COUNT(DISTINCT pm.pid)::integer AS products,
                COALESCE(SUM(pm.overstocked_units), 0)::integer AS units,
                ROUND(COALESCE(SUM(pm.overstocked_cost), 0)::numeric, 2) AS cost,
                ROUND(COALESCE(SUM(pm.overstocked_retail), 0)::numeric, 2) AS retail
            FROM product_metrics pm
            WHERE pm.status = 'Overstock' AND pm.is_visible = true
              AND COALESCE(pm.preorder_count, 0) = 0
            GROUP BY pm.lifecycle_phase
            ORDER BY cost DESC
        `);
        const overstockPhaseTotalCost = phaseOverstock.reduce((s, r) => s + (parseFloat(r.cost) || 0), 0);

        // Format response with explicit type conversion
        const response = {
            overstockedProducts: parseInt(summaryMetrics.total_overstocked) || 0,
            totalExcessUnits: parseInt(summaryMetrics.total_excess_units) || 0,
            totalExcessCost: parseFloat(summaryMetrics.total_excess_cost) || 0,
            totalExcessRetail: parseFloat(summaryMetrics.total_excess_retail) || 0,
            categoryData: categoryData.map(cat => ({
                category: cat.category_name,
                products: parseInt(cat.overstocked_products) || 0,
                units: parseInt(cat.total_excess_units) || 0,
                cost: parseFloat(cat.total_excess_cost) || 0,
                retail: parseFloat(cat.total_excess_retail) || 0
            })),
            phaseBreakdown: phaseOverstock.filter(r => parseFloat(r.cost) > 0).map(r => ({
                phase: r.phase,
                products: parseInt(r.products) || 0,
                units: parseInt(r.units) || 0,
                cost: parseFloat(r.cost) || 0,
                retail: parseFloat(r.retail) || 0,
                percentage: overstockPhaseTotalCost > 0
                    ? parseFloat(((parseFloat(r.cost) / overstockPhaseTotalCost) * 100).toFixed(1))
                    : 0,
            })),
        };

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

// GET /dashboard/overstock/products
// Returns list of most overstocked products
router.get('/overstock/products', async (req, res) => {
    const limit = parseInt(req.query.limit) || 50;
    try {
        const { rows } = await executeQuery(`
            SELECT 
                pm.pid,
                pm.sku AS SKU,
                pm.title,
                pm.brand,
                pm.vendor,
                pm.current_stock as stock_quantity,
                pm.current_cost_price as cost_price,
                pm.current_price as price,
                pm.sales_velocity_daily as daily_sales_avg,
                pm.stock_cover_in_days as days_of_inventory,
                pm.overstocked_units as overstocked_amt,
                pm.overstocked_cost as excess_cost,
                pm.overstocked_retail as excess_retail,
                STRING_AGG(c.name, ', ') as categories
            FROM product_metrics pm
            LEFT JOIN product_categories pc ON pm.pid = pc.pid
            LEFT JOIN categories c ON pc.cat_id = c.cat_id
            WHERE pm.status = 'Overstock'
                AND pm.is_visible = true
            GROUP BY pm.pid, pm.sku, pm.title, pm.brand, pm.vendor, pm.current_stock, pm.current_cost_price, pm.current_price, 
                    pm.sales_velocity_daily, pm.stock_cover_in_days, pm.overstocked_units, pm.overstocked_cost, pm.overstocked_retail
            ORDER BY excess_cost DESC
            LIMIT $1
        `, [limit]);
        res.json(rows);
    } catch (err) {
        console.error('Error fetching overstocked products:', err);
        res.status(500).json({ error: 'Failed to fetch overstocked products' });
    }
});

// GET /dashboard/best-sellers
// Returns best-selling products, brands, and categories (from product_metrics)
router.get('/best-sellers', async (req, res) => {
    try {
        // Best selling products
        const { rows: products } = await executeQuery(`
            SELECT
                pm.pid,
                pm.sku,
                pm.title,
                pm.sales_30d::integer as units_sold,
                ROUND(pm.revenue_30d::numeric, 2) as revenue,
                ROUND(pm.profit_30d::numeric, 2) as profit
            FROM product_metrics pm
            WHERE pm.is_visible = true
                AND pm.sales_30d > 0
            ORDER BY pm.sales_30d DESC
            LIMIT 10
        `);

        // Best selling brands
        const { rows: brands } = await executeQuery(`
            SELECT
                pm.brand,
                SUM(pm.sales_30d)::integer as units_sold,
                ROUND(SUM(pm.revenue_30d)::numeric, 2) as revenue,
                ROUND(SUM(pm.profit_30d)::numeric, 2) as profit,
                ROUND(
                    CASE WHEN SUM(pm.revenue_30d) > 0 AND COUNT(CASE WHEN pm.sales_growth_30d_vs_prev IS NOT NULL THEN 1 END) > 0
                        THEN SUM(pm.revenue_30d * COALESCE(pm.sales_growth_30d_vs_prev, 0)) / NULLIF(SUM(CASE WHEN pm.sales_growth_30d_vs_prev IS NOT NULL THEN pm.revenue_30d ELSE 0 END), 0)
                        ELSE NULL
                    END::numeric, 1
                ) as growth_rate
            FROM product_metrics pm
            WHERE pm.is_visible = true
                AND pm.sales_30d > 0
            GROUP BY pm.brand
            ORDER BY units_sold DESC
            LIMIT 10
        `);

        // Best selling categories with full path from materialized view
        const { rows: categories } = await executeQuery(`
            SELECT
                c.cat_id,
                c.name,
                ch.path as "categoryPath",
                SUM(pm.sales_30d)::integer as units_sold,
                ROUND(SUM(pm.revenue_30d)::numeric, 2) as revenue,
                ROUND(SUM(pm.profit_30d)::numeric, 2) as profit
            FROM product_metrics pm
            JOIN product_categories pc ON pm.pid = pc.pid
            JOIN categories c ON pc.cat_id = c.cat_id
            JOIN category_hierarchy ch ON c.cat_id = ch.cat_id
            WHERE pm.is_visible = true
                AND pm.sales_30d > 0
            GROUP BY c.cat_id, c.name, ch.path
            ORDER BY units_sold DESC
            LIMIT 10
        `);

        res.json({ products, brands, categories });
    } catch (err) {
        console.error('Error fetching best sellers:', err);
        res.status(500).json({ error: 'Failed to fetch best sellers' });
    }
});

// GET /dashboard/year-revenue-estimate
// Returns YTD actual revenue + rest-of-year forecast revenue for a full-year estimate
router.get('/year-revenue-estimate', async (req, res) => {
    const todayISO = businessTodayStr(); // business 'today', not UTC
    const yearStart = `${todayISO.slice(0, 4)}-01-01`;
    const yearEndISO = `${todayISO.slice(0, 4)}-12-31`;

    try {
        // YTD actual revenue from orders
        const { rows: [ytd] } = await executeQuery(`
            SELECT COALESCE(SUM(price * quantity), 0) AS revenue
            FROM orders
            WHERE date >= $1 AND date <= $2 AND canceled = false
        `, [yearStart, todayISO]);

        // Forecast horizon
        const { rows: [horizonRow] } = await executeQuery(
            `SELECT MAX(forecast_date) AS max_date FROM product_forecasts`
        );
        const forecastHorizonISO = horizonRow?.max_date ? formatDateCol(horizonRow.max_date) : todayISO;

        const clampedEnd = yearEndISO <= forecastHorizonISO ? yearEndISO : forecastHorizonISO;

        // Forecast revenue from tomorrow to clamped end
        const { rows: [forecast] } = await executeQuery(`
            SELECT COALESCE(SUM(pf.forecast_revenue), 0) AS revenue
            FROM product_forecasts pf
            JOIN product_metrics pm ON pm.pid = pf.pid
            WHERE pm.is_visible = true
              AND pf.forecast_date > $1 AND pf.forecast_date <= $2
        `, [todayISO, clampedEnd]);

        let eoyForecastRevenue = parseFloat(forecast.revenue) || 0;

        // If forecast doesn't cover full year, extrapolate remaining days
        if (yearEndISO > forecastHorizonISO) {
            const { rows: [tailRow] } = await executeQuery(`
                SELECT AVG(daily_rev) AS avg_daily FROM (
                    SELECT forecast_date, SUM(pf.forecast_revenue) AS daily_rev
                    FROM product_forecasts pf
                    JOIN product_metrics pm ON pm.pid = pf.pid
                    WHERE pm.is_visible = true
                      AND pf.forecast_date > ($1::date - INTERVAL '7 days')
                      AND pf.forecast_date <= $1
                    GROUP BY forecast_date
                ) sub
            `, [forecastHorizonISO]);

            const baselineDaily = parseFloat(tailRow?.avg_daily) || 0;
            const horizonDate = new Date(forecastHorizonISO + 'T00:00:00');
            const yearEnd = new Date(yearEndISO + 'T00:00:00');
            const extraDays = Math.round((yearEnd - horizonDate) / (1000 * 60 * 60 * 24));
            eoyForecastRevenue += baselineDaily * extraDays;
        }

        const ytdRevenue = parseFloat(ytd.revenue) || 0;

        res.json({
            ytdRevenue,
            eoyForecastRevenue,
            yearTotal: ytdRevenue + eoyForecastRevenue,
        });
    } catch (err) {
        console.error('Error fetching year revenue estimate:', err);
        res.status(500).json({ error: 'Failed to fetch year revenue estimate' });
    }
});

// GET /dashboard/sales/metrics
// Returns sales metrics for specified period
router.get('/sales/metrics', async (req, res) => {
    // Default: trailing 30 business days ending now (Chicago day start).
    const startDate = req.query.startDate || businessNow().minus({ days: 30 }).startOf('day').toISO();
    const endDate = req.query.endDate || new Date().toISOString();
    
    try {
        // Get daily orders and totals for the specified period
        const { rows: dailyRows } = await executeQuery(`
            SELECT 
                DATE(date) as sale_date,
                COUNT(DISTINCT order_number) as total_orders,
                SUM(quantity) as total_units,
                SUM(price * quantity) as total_revenue,
                SUM(costeach * quantity) as total_cogs
            FROM orders
            WHERE date BETWEEN $1 AND $2
                AND canceled = false
            GROUP BY DATE(date)
            ORDER BY sale_date
        `, [startDate, endDate]);

        // Get overall metrics for the period
        const { rows: [metrics] } = await executeQuery(`
            SELECT
                COUNT(DISTINCT order_number) as total_orders,
                SUM(quantity) as total_units,
                SUM(price * quantity) as total_revenue,
                SUM(costeach * quantity) as total_cogs
            FROM orders
            WHERE date BETWEEN $1 AND $2
                AND canceled = false
        `, [startDate, endDate]);

        // Sales breakdown by lifecycle phase
        const { rows: phaseSales } = await executeQuery(`
            SELECT
                COALESCE(pm.lifecycle_phase, 'unknown') AS phase,
                COUNT(DISTINCT o.order_number)::integer AS orders,
                COALESCE(SUM(o.quantity), 0)::integer AS units,
                ROUND(COALESCE(SUM(o.price * o.quantity), 0)::numeric, 2) AS revenue,
                ROUND(COALESCE(SUM(o.costeach * o.quantity), 0)::numeric, 2) AS cogs
            FROM orders o
            LEFT JOIN product_metrics pm ON o.pid = pm.pid
            WHERE o.date BETWEEN $1 AND $2 AND o.canceled = false
            GROUP BY pm.lifecycle_phase
            ORDER BY revenue DESC
        `, [startDate, endDate]);
        const salePhaseTotalRev = phaseSales.reduce((s, r) => s + (parseFloat(r.revenue) || 0), 0);

        // Daily sales broken down by lifecycle phase (for stacked chart)
        const { rows: dailyPhaseRows } = await executeQuery(`
            SELECT
                DATE(o.date) AS sale_date,
                COALESCE(SUM(o.price * o.quantity) FILTER (WHERE COALESCE(pm.lifecycle_phase, 'unknown') = 'preorder'), 0) AS preorder,
                COALESCE(SUM(o.price * o.quantity) FILTER (WHERE COALESCE(pm.lifecycle_phase, 'unknown') = 'launch'), 0) AS launch,
                COALESCE(SUM(o.price * o.quantity) FILTER (WHERE COALESCE(pm.lifecycle_phase, 'unknown') = 'decay'), 0) AS decay,
                COALESCE(SUM(o.price * o.quantity) FILTER (WHERE COALESCE(pm.lifecycle_phase, 'unknown') = 'mature'), 0) AS mature,
                COALESCE(SUM(o.price * o.quantity) FILTER (WHERE COALESCE(pm.lifecycle_phase, 'unknown') = 'slow_mover'), 0) AS slow_mover,
                COALESCE(SUM(o.price * o.quantity) FILTER (WHERE COALESCE(pm.lifecycle_phase, 'unknown') = 'dormant'), 0) AS dormant,
                COALESCE(SUM(o.price * o.quantity) FILTER (WHERE pm.lifecycle_phase IS NULL), 0) AS unknown
            FROM orders o
            LEFT JOIN product_metrics pm ON o.pid = pm.pid
            WHERE o.date BETWEEN $1 AND $2 AND o.canceled = false
            GROUP BY DATE(o.date)
            ORDER BY sale_date
        `, [startDate, endDate]);

        const response = {
            totalOrders: parseInt(metrics?.total_orders) || 0,
            totalUnitsSold: parseInt(metrics?.total_units) || 0,
            totalCogs: parseFloat(metrics?.total_cogs) || 0,
            totalRevenue: parseFloat(metrics?.total_revenue) || 0,
            dailySales: dailyRows.map(day => ({
                date: formatDateCol(day.sale_date),
                units: parseInt(day.total_units) || 0,
                revenue: parseFloat(day.total_revenue) || 0,
                cogs: parseFloat(day.total_cogs) || 0
            })),
            dailySalesByPhase: dailyPhaseRows.map(d => ({
                date: formatDateCol(d.sale_date),
                preorder: parseFloat(d.preorder) || 0,
                launch: parseFloat(d.launch) || 0,
                decay: parseFloat(d.decay) || 0,
                mature: parseFloat(d.mature) || 0,
                slow_mover: parseFloat(d.slow_mover) || 0,
                dormant: parseFloat(d.dormant) || 0,
                unknown: parseFloat(d.unknown) || 0,
            })),
            phaseBreakdown: phaseSales.filter(r => parseFloat(r.revenue) > 0).map(r => ({
                phase: r.phase,
                orders: parseInt(r.orders) || 0,
                units: parseInt(r.units) || 0,
                revenue: parseFloat(r.revenue) || 0,
                cogs: parseFloat(r.cogs) || 0,
                percentage: salePhaseTotalRev > 0
                    ? parseFloat(((parseFloat(r.revenue) / salePhaseTotalRev) * 100).toFixed(1))
                    : 0,
            })),
        };

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

// GET /dashboard/vendor/performance
// Returns detailed vendor performance metrics
router.get('/vendor/performance', async (req, res) => {
    try {
        const { rows } = await executeQuery(`
            WITH stale AS (
                SELECT po_id, pid
                FROM purchase_orders po
                WHERE po.status IN ('created', 'ordered', 'preordered', 'electronically_sent',
                                    'electronically_ready_send', 'receiving_started')
                  AND po.expected_date IS NOT NULL
                  AND po.expected_date < CURRENT_DATE - INTERVAL '90 days'
                  AND EXISTS (
                      SELECT 1 FROM purchase_orders newer
                      WHERE newer.pid = po.pid
                        AND newer.status NOT IN ('canceled', 'done')
                        AND COALESCE(newer.date_ordered, newer.date_created)
                            > COALESCE(po.date_ordered, po.date_created)
                  )
            ),
            vendor_orders AS (
                SELECT
                    po.vendor,
                    COUNT(DISTINCT po.po_id)::integer as total_orders,
                    COALESCE(ROUND(AVG(CASE WHEN po.received_date IS NOT NULL
                        THEN EXTRACT(EPOCH FROM (po.received_date - po.date))/86400
                        ELSE NULL END)::numeric, 2), 0) as avg_lead_time,
                    COALESCE(ROUND(SUM(CASE
                        WHEN po.status = 'done' AND po.received_date <= po.expected_date
                        THEN 1
                        ELSE 0
                        END)::numeric * 100.0 / NULLIF(COUNT(*)::numeric, 0), 2), 0) as on_time_delivery_rate,
                    COALESCE(ROUND(AVG(CASE
                        WHEN po.status = 'done'
                        THEN po.received::numeric / NULLIF(po.ordered::numeric, 0) * 100
                        ELSE NULL
                        END)::numeric, 2), 0) as avg_fill_rate,
                    COUNT(CASE WHEN po.status IN ('created', 'electronically_ready_send', 'ordered', 'preordered', 'electronically_sent', 'receiving_started')
                               AND NOT EXISTS (SELECT 1 FROM stale s WHERE s.po_id = po.po_id AND s.pid = po.pid)
                          THEN 1 END)::integer as active_orders,
                    COUNT(CASE WHEN po.status IN ('created', 'electronically_ready_send', 'ordered', 'preordered', 'electronically_sent', 'receiving_started')
                               AND po.expected_date < CURRENT_DATE
                               AND NOT EXISTS (SELECT 1 FROM stale s WHERE s.po_id = po.po_id AND s.pid = po.pid)
                          THEN 1 END)::integer as overdue_orders
                FROM purchase_orders po
                WHERE po.date >= CURRENT_DATE - INTERVAL '180 days'
                GROUP BY po.vendor
            )
            SELECT
                vo.vendor,
                vo.total_orders,
                vo.avg_lead_time,
                vo.on_time_delivery_rate,
                vo.avg_fill_rate,
                vo.active_orders,
                vo.overdue_orders
            FROM vendor_orders vo
            ORDER BY vo.on_time_delivery_rate DESC
            LIMIT 10
        `);

        const formattedData = rows.map(row => ({
            vendor: row.vendor,
            total_orders: Number(row.total_orders) || 0,
            avg_lead_time: Number(row.avg_lead_time) || 0,
            on_time_delivery_rate: Number(row.on_time_delivery_rate) || 0,
            avg_fill_rate: Number(row.avg_fill_rate) || 0,
            active_orders: Number(row.active_orders) || 0,
            overdue_orders: Number(row.overdue_orders) || 0
        }));

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

// GET /dashboard/replenish/products
// Returns list of products to replenish
router.get('/replenish/products', async (req, res) => {
    const limit = parseInt(req.query.limit) || 50;
    try {
        const { rows } = await executeQuery(`
            SELECT 
                pm.pid,
                pm.sku,
                pm.title,
                pm.current_stock AS stock_quantity,
                pm.sales_velocity_daily AS daily_sales_avg,
                pm.replenishment_units AS reorder_qty,
                pm.date_last_received AS last_purchase_date
            FROM product_metrics pm
            WHERE pm.is_visible = true
                AND pm.is_replenishable = true
                AND (pm.status IN ('Critical', 'Reorder')
                    OR pm.current_stock < 0)
                AND pm.replenishment_units > 0
            ORDER BY
                CASE pm.status
                    WHEN 'Critical' THEN 1
                    WHEN 'Reorder' THEN 2
                END,
                pm.replenishment_cost DESC
            LIMIT $1
        `, [limit]);
        res.json(rows);
    } catch (err) {
        console.error('Error fetching products to replenish:', err);
        res.status(500).json({ error: 'Failed to fetch products to replenish' });
    }
});

export default router;