-- SESSION-TZ ASSUMPTION: this script runs with TimeZone=America/Chicago
-- (pinned in the pool config and as the inventory_db database default).
-- CURRENT_DATE / NOW()::date / ts::date here mean the BUSINESS date
-- (Chicago calendar day = 1am-Eastern day rule). See docs/TIME.md.
-- Description: Calculates and updates aggregated metrics per category with hierarchy rollups.
-- Dependencies: product_metrics, products, categories, product_categories, category_hierarchy, calculate_status table.
-- Frequency: Daily (after product_metrics update).

DO $$
DECLARE
    _module_name VARCHAR := 'category_metrics';
    _start_time TIMESTAMPTZ := clock_timestamp();
    _min_revenue NUMERIC := 50.00; -- Minimum revenue threshold for margin calculation
BEGIN
    RAISE NOTICE 'Running % calculation...', _module_name;
    
    -- Refresh the category hierarchy materialized view first
    REFRESH MATERIALIZED VIEW CONCURRENTLY category_hierarchy;

    -- First calculate direct metrics (products directly in each category)
    WITH DirectCategoryMetrics AS (
        SELECT
            pc.cat_id,
            COUNT(DISTINCT pm.pid) AS product_count,
            COUNT(DISTINCT CASE WHEN pm.is_visible THEN pm.pid END) AS active_product_count,
            COUNT(DISTINCT CASE WHEN pm.is_replenishable THEN pm.pid END) AS replenishable_product_count,
            SUM(pm.current_stock) AS current_stock_units,
            SUM(pm.current_stock_cost) AS current_stock_cost,
            SUM(pm.current_stock_retail) AS current_stock_retail,
            -- Sales metrics — revenue uses plain COALESCE (matching brand/vendor);
            -- a positive-only revenue filter while cogs/profit sum everything put
            -- the margin numerator and denominator on different row populations.
            SUM(CASE WHEN pm.sales_7d > 0 THEN pm.sales_7d ELSE 0 END) AS sales_7d,
            SUM(COALESCE(pm.revenue_7d, 0)) AS revenue_7d,
            SUM(CASE WHEN pm.sales_30d > 0 THEN pm.sales_30d ELSE 0 END) AS sales_30d,
            SUM(COALESCE(pm.revenue_30d, 0)) AS revenue_30d,
            SUM(COALESCE(pm.cogs_30d, 0)) AS cogs_30d,
            SUM(COALESCE(pm.profit_30d, 0)) AS profit_30d,
            SUM(CASE WHEN pm.sales_365d > 0 THEN pm.sales_365d ELSE 0 END) AS sales_365d,
            SUM(COALESCE(pm.revenue_365d, 0)) AS revenue_365d,
            SUM(CASE WHEN pm.lifetime_sales > 0 THEN pm.lifetime_sales ELSE 0 END) AS lifetime_sales,
            SUM(COALESCE(pm.lifetime_revenue, 0)) AS lifetime_revenue
        FROM public.product_categories pc
        JOIN public.product_metrics pm ON pc.pid = pm.pid
        GROUP BY pc.cat_id
    ),
    -- Map each category to ALL distinct products in it or any descendant.
    -- Uses the path array from category_hierarchy: for product P in category C,
    -- P contributes to C and every ancestor in C's path.
    -- DISTINCT ensures each (ancestor, pid) pair appears only once, preventing
    -- double-counting when a product belongs to multiple categories under the same parent.
    CategoryProducts AS (
        SELECT DISTINCT
            ancestor_cat_id,
            pc.pid
        FROM public.product_categories pc
        JOIN category_hierarchy ch ON pc.cat_id = ch.cat_id
        CROSS JOIN LATERAL unnest(ch.path) AS ancestor_cat_id
    ),
    -- Calculate rolled-up metrics using deduplicated product sets
    RolledUpMetrics AS (
        SELECT
            cp.ancestor_cat_id AS cat_id,
            COUNT(DISTINCT cp.pid) AS product_count,
            COUNT(DISTINCT CASE WHEN pm.is_visible THEN cp.pid END) AS active_product_count,
            COUNT(DISTINCT CASE WHEN pm.is_replenishable THEN cp.pid END) AS replenishable_product_count,
            SUM(pm.current_stock) AS current_stock_units,
            SUM(pm.current_stock_cost) AS current_stock_cost,
            SUM(pm.current_stock_retail) AS current_stock_retail,
            SUM(CASE WHEN pm.sales_7d > 0 THEN pm.sales_7d ELSE 0 END) AS sales_7d,
            SUM(COALESCE(pm.revenue_7d, 0)) AS revenue_7d,
            SUM(CASE WHEN pm.sales_30d > 0 THEN pm.sales_30d ELSE 0 END) AS sales_30d,
            SUM(COALESCE(pm.revenue_30d, 0)) AS revenue_30d,
            SUM(COALESCE(pm.cogs_30d, 0)) AS cogs_30d,
            SUM(COALESCE(pm.profit_30d, 0)) AS profit_30d,
            SUM(CASE WHEN pm.sales_365d > 0 THEN pm.sales_365d ELSE 0 END) AS sales_365d,
            SUM(COALESCE(pm.revenue_365d, 0)) AS revenue_365d,
            SUM(CASE WHEN pm.lifetime_sales > 0 THEN pm.lifetime_sales ELSE 0 END) AS lifetime_sales,
            SUM(COALESCE(pm.lifetime_revenue, 0)) AS lifetime_revenue
        FROM CategoryProducts cp
        JOIN public.product_metrics pm ON cp.pid = pm.pid
        GROUP BY cp.ancestor_cat_id
    ),
    -- Previous period rolled up using same deduplicated product sets
    RolledUpPreviousPeriod AS (
        SELECT
            cp.ancestor_cat_id AS cat_id,
            SUM(CASE WHEN dps.snapshot_date >= CURRENT_DATE - INTERVAL '59 days'
                     AND dps.snapshot_date < CURRENT_DATE - INTERVAL '29 days'
                THEN dps.units_sold ELSE 0 END) AS sales_prev_30d,
            SUM(CASE WHEN dps.snapshot_date >= CURRENT_DATE - INTERVAL '59 days'
                     AND dps.snapshot_date < CURRENT_DATE - INTERVAL '29 days'
                THEN dps.net_revenue ELSE 0 END) AS revenue_prev_30d
        FROM CategoryProducts cp
        JOIN public.daily_product_snapshots dps ON cp.pid = dps.pid
        GROUP BY cp.ancestor_cat_id
    ),
    AllCategories AS (
        -- Ensure all categories are included
        SELECT 
            c.cat_id,
            c.name,
            c.type,
            c.parent_id
        FROM public.categories c
        WHERE c.status = 'active'
    )
    INSERT INTO public.category_metrics (
        category_id, category_name, category_type, parent_id, last_calculated,
        -- Rolled-up metrics
        product_count, active_product_count, replenishable_product_count,
        current_stock_units, current_stock_cost, current_stock_retail,
        sales_7d, revenue_7d, sales_30d, revenue_30d, profit_30d, cogs_30d,
        sales_365d, revenue_365d, lifetime_sales, lifetime_revenue,
        -- Direct metrics
        direct_product_count, direct_active_product_count, direct_replenishable_product_count,
        direct_current_stock_units, direct_stock_cost, direct_stock_retail,
        direct_sales_7d, direct_revenue_7d, direct_sales_30d, direct_revenue_30d,
        direct_profit_30d, direct_cogs_30d, direct_sales_365d, direct_revenue_365d,
        direct_lifetime_sales, direct_lifetime_revenue,
        -- KPIs
        avg_margin_30d,
        sales_growth_30d_vs_prev, revenue_growth_30d_vs_prev
    )
    SELECT
        ac.cat_id,
        ac.name,
        ac.type,
        ac.parent_id,
        _start_time,
        -- Rolled-up metrics (includes descendants)
        COALESCE(rum.product_count, 0),
        COALESCE(rum.active_product_count, 0),
        COALESCE(rum.replenishable_product_count, 0),
        COALESCE(rum.current_stock_units, 0),
        COALESCE(rum.current_stock_cost, 0.00),
        COALESCE(rum.current_stock_retail, 0.00),
        COALESCE(rum.sales_7d, 0), COALESCE(rum.revenue_7d, 0.00),
        COALESCE(rum.sales_30d, 0), COALESCE(rum.revenue_30d, 0.00),
        COALESCE(rum.profit_30d, 0.00), COALESCE(rum.cogs_30d, 0.00),
        COALESCE(rum.sales_365d, 0), COALESCE(rum.revenue_365d, 0.00),
        COALESCE(rum.lifetime_sales, 0), COALESCE(rum.lifetime_revenue, 0.00),
        -- Direct metrics (only this category)
        COALESCE(dcm.product_count, 0),
        COALESCE(dcm.active_product_count, 0),
        COALESCE(dcm.replenishable_product_count, 0),
        COALESCE(dcm.current_stock_units, 0),
        COALESCE(dcm.current_stock_cost, 0.00),
        COALESCE(dcm.current_stock_retail, 0.00),
        COALESCE(dcm.sales_7d, 0), COALESCE(dcm.revenue_7d, 0.00),
        COALESCE(dcm.sales_30d, 0), COALESCE(dcm.revenue_30d, 0.00),
        COALESCE(dcm.profit_30d, 0.00), COALESCE(dcm.cogs_30d, 0.00),
        COALESCE(dcm.sales_365d, 0), COALESCE(dcm.revenue_365d, 0.00),
        COALESCE(dcm.lifetime_sales, 0), COALESCE(dcm.lifetime_revenue, 0.00),
        -- KPIs - Calculate margin only for categories with significant revenue
        CASE
            WHEN COALESCE(rum.revenue_30d, 0) >= _min_revenue THEN
                ((COALESCE(rum.revenue_30d, 0) - COALESCE(rum.cogs_30d, 0)) / COALESCE(rum.revenue_30d, 1)) * 100.0
            ELSE NULL
        END,
        -- Growth metrics for rolled-up values
        std_numeric(safe_divide((rum.sales_30d - rupp.sales_prev_30d) * 100.0, rupp.sales_prev_30d), 2),
        std_numeric(safe_divide((rum.revenue_30d - rupp.revenue_prev_30d) * 100.0, rupp.revenue_prev_30d), 2)
    FROM AllCategories ac
    LEFT JOIN DirectCategoryMetrics dcm ON ac.cat_id = dcm.cat_id
    LEFT JOIN RolledUpMetrics rum ON ac.cat_id = rum.cat_id
    LEFT JOIN RolledUpPreviousPeriod rupp ON ac.cat_id = rupp.cat_id

    ON CONFLICT (category_id) DO UPDATE SET
        last_calculated = EXCLUDED.last_calculated,
        category_name = EXCLUDED.category_name,
        category_type = EXCLUDED.category_type,
        parent_id = EXCLUDED.parent_id,
        -- Rolled-up metrics
        product_count = EXCLUDED.product_count,
        active_product_count = EXCLUDED.active_product_count,
        replenishable_product_count = EXCLUDED.replenishable_product_count,
        current_stock_units = EXCLUDED.current_stock_units,
        current_stock_cost = EXCLUDED.current_stock_cost,
        current_stock_retail = EXCLUDED.current_stock_retail,
        sales_7d = EXCLUDED.sales_7d, revenue_7d = EXCLUDED.revenue_7d,
        sales_30d = EXCLUDED.sales_30d, revenue_30d = EXCLUDED.revenue_30d,
        profit_30d = EXCLUDED.profit_30d, cogs_30d = EXCLUDED.cogs_30d,
        sales_365d = EXCLUDED.sales_365d, revenue_365d = EXCLUDED.revenue_365d,
        lifetime_sales = EXCLUDED.lifetime_sales, lifetime_revenue = EXCLUDED.lifetime_revenue,
        -- Direct metrics
        direct_product_count = EXCLUDED.direct_product_count,
        direct_active_product_count = EXCLUDED.direct_active_product_count,
        direct_replenishable_product_count = EXCLUDED.direct_replenishable_product_count,
        direct_current_stock_units = EXCLUDED.direct_current_stock_units,
        direct_stock_cost = EXCLUDED.direct_stock_cost,
        direct_stock_retail = EXCLUDED.direct_stock_retail,
        direct_sales_7d = EXCLUDED.direct_sales_7d, direct_revenue_7d = EXCLUDED.direct_revenue_7d,
        direct_sales_30d = EXCLUDED.direct_sales_30d, direct_revenue_30d = EXCLUDED.direct_revenue_30d,
        direct_profit_30d = EXCLUDED.direct_profit_30d, direct_cogs_30d = EXCLUDED.direct_cogs_30d,
        direct_sales_365d = EXCLUDED.direct_sales_365d, direct_revenue_365d = EXCLUDED.direct_revenue_365d,
        direct_lifetime_sales = EXCLUDED.direct_lifetime_sales, direct_lifetime_revenue = EXCLUDED.direct_lifetime_revenue,
        avg_margin_30d = EXCLUDED.avg_margin_30d,
        sales_growth_30d_vs_prev = EXCLUDED.sales_growth_30d_vs_prev,
        revenue_growth_30d_vs_prev = EXCLUDED.revenue_growth_30d_vs_prev
    WHERE -- Only update if at least one value has changed
        category_metrics.product_count IS DISTINCT FROM EXCLUDED.product_count OR
        category_metrics.active_product_count IS DISTINCT FROM EXCLUDED.active_product_count OR
        category_metrics.current_stock_units IS DISTINCT FROM EXCLUDED.current_stock_units OR
        category_metrics.sales_30d IS DISTINCT FROM EXCLUDED.sales_30d OR
        category_metrics.revenue_30d IS DISTINCT FROM EXCLUDED.revenue_30d OR
        category_metrics.lifetime_sales IS DISTINCT FROM EXCLUDED.lifetime_sales OR
        category_metrics.direct_product_count IS DISTINCT FROM EXCLUDED.direct_product_count OR
        category_metrics.direct_sales_30d IS DISTINCT FROM EXCLUDED.direct_sales_30d OR
        -- Cost revisions can change profit/cogs with unchanged sales/revenue
        category_metrics.profit_30d IS DISTINCT FROM EXCLUDED.profit_30d OR
        category_metrics.cogs_30d IS DISTINCT FROM EXCLUDED.cogs_30d;

    -- Update calculate_status
    INSERT INTO public.calculate_status (module_name, last_calculation_timestamp)
    VALUES (_module_name, _start_time)
    ON CONFLICT (module_name) DO UPDATE SET last_calculation_timestamp = _start_time;

    RAISE NOTICE 'Finished % calculation. Duration: %', _module_name, clock_timestamp() - _start_time;
END $$;

-- Return metrics about the update operation for tracking
WITH update_stats AS (
    SELECT 
        COUNT(*) as total_categories,
        COUNT(*) FILTER (WHERE last_calculated >= NOW() - INTERVAL '5 minutes') as rows_processed,
        COUNT(*) FILTER (WHERE category_type = 10) as sections,
        COUNT(*) FILTER (WHERE category_type = 11) as categories,
        COUNT(*) FILTER (WHERE category_type = 12) as subcategories,
        SUM(product_count) as total_products_rolled,
        SUM(direct_product_count) as total_products_direct,
        SUM(sales_30d) as total_sales_30d,
        SUM(revenue_30d) as total_revenue_30d
    FROM public.category_metrics
)
SELECT 
    rows_processed,
    total_categories,
    sections,
    categories,
    subcategories,
    total_products_rolled::int,
    total_products_direct::int,
    total_sales_30d::int,
    ROUND(total_revenue_30d, 2) as total_revenue_30d
FROM update_stats;