import { supabase, rawQuery, toPgDate, toPgTimestamp } from '../db.js';

/**
 * Options Flow Query Module
 * Handles database queries for options flow data
 * 
 * Supports both raw SQL queries (Script 1 style) and query builder methods
 */

// Raw SQL query template for Script 1 style options flow analysis
// Full query from optionflowrockerscorer.sql with parameterized dates
export const optionsFlowQuery = `
WITH cfg AS (
  SELECT
    $1::date AS start_day,
    $2::date AS end_day,
    0.20::numeric AS tol_pct  -- tape-alignment tolerance (pct points)
),

/* 1) FLOW: normalize fields + clean numerics */
base AS (
  SELECT
    ofm.ctid                               AS rid,                          -- stable row identity within a monthly table
    ofm.*,
    UPPER(TRIM(ofm."Symbol"))              AS symbol_norm,

    /* cp_norm */
    CASE WHEN UPPER(TRIM(ofm."CallPut")) IN ('C','CALL','CALLS','CE') THEN 'CALL'
         WHEN UPPER(TRIM(ofm."CallPut")) IN ('P','PUT','PUTS','PE')   THEN 'PUT'
    END                                    AS cp_norm,

    /* side_norm */
    CASE
      WHEN UPPER(TRIM(ofm."Side")) IN ('A','AA','ASK','BUY','BOT','BTO','AT_ASK')  THEN 'BUY'
      WHEN UPPER(TRIM(ofm."Side")) IN ('B','BB','BID','SELL','SLD','STO','AT_BID') THEN 'SELL'
      ELSE NULL
    END                                    AS side_norm,

    /* numerics: cast via ::text → regexp_replace → ::numeric so it works whether input is text or numeric */
    NULLIF(REGEXP_REPLACE(ofm."Strike"::text,  '[\s$,]', '', 'g'),'')::numeric  AS strike_num,
    NULLIF(REGEXP_REPLACE(ofm."Spot"::text,    '[\s$,]', '', 'g'),'')::numeric  AS spot_num,
    NULLIF(REGEXP_REPLACE(ofm."Premium"::text, '[\s$,]', '', 'g'),'')::numeric  AS premium_num,
    NULLIF(REGEXP_REPLACE(ofm."Volume"::text,  '[\s$,]', '', 'g'),'')::numeric  AS vol_num,
    NULLIF(REGEXP_REPLACE(ofm."OI"::text,      '[\s$,]', '', 'g'),'')::numeric  AS oi_num,

    /* parse expiration date to DATE (supports YYYY-MM-DD or MM/DD/YYYY) */
    CASE
      WHEN ofm."ExpirationDate"::text ~ '^\d{4}-\d{2}-\d{2}$' THEN ofm."ExpirationDate"::date
      WHEN ofm."ExpirationDate"::text ~ '^\d{1,2}/\d{1,2}/\d{2,4}$' THEN to_date(ofm."ExpirationDate"::text, 'MM/DD/YYYY')
      ELSE NULL
    END                                    AS exp_date,

    /* build FLOW timestamp in local feed time (assume CST = America/Chicago) */
    /* CRITICAL: Normalize time by padding single-digit hours BEFORE parsing */
    CASE
      WHEN ofm."CreatedDate"::text ~ '^\d{4}-\d{2}-\d{2}$' AND ofm."CreatedTime"::text ~* '(AM|PM)'
        THEN CASE
          /* Has seconds: "09:30:45 AM" or "3:59:59 PM" - Convert to 24-hour format for reliable parsing */
          WHEN ofm."CreatedTime"::text ~ '^\d{1,2}:[0-9]{2}:[0-9]{2}\s*(AM|PM)'
            THEN to_timestamp(
              ofm."CreatedDate"::text || ' ' || 
              CASE 
                WHEN UPPER(btrim(ofm."CreatedTime"::text)) LIKE '%PM'
                  THEN CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '12:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                    ELSE LPAD(((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int + 12)::text, 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                  END
                ELSE  -- AM
                  CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '00:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                    ELSE LPAD(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1), 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                  END
              END,
              'YYYY-MM-DD HH24:MI:SS'
            )
          /* Has minutes: "09:30 AM" or "3:30 AM" - Convert to 24-hour format for reliable parsing */
          WHEN ofm."CreatedTime"::text ~ '^\d{1,2}:[0-9]{2}\s*(AM|PM)'
            THEN to_timestamp(
              ofm."CreatedDate"::text || ' ' || 
              CASE 
                WHEN UPPER(btrim(ofm."CreatedTime"::text)) LIKE '%PM'
                  THEN CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '12:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                    ELSE LPAD(((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int + 12)::text, 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                  END
                ELSE  -- AM
                  CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '00:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                    ELSE LPAD(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1), 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                  END
              END,
              'YYYY-MM-DD HH24:MI:SS'
            )
          /* Just hour: "09 AM" or "9 AM" - convert to 24-hour format */
          WHEN ofm."CreatedTime"::text ~ '^\d{1,2}\s*(AM|PM)$'
            THEN to_timestamp(
              ofm."CreatedDate"::text || ' ' || 
              CASE 
                WHEN UPPER(btrim(ofm."CreatedTime"::text)) LIKE '%PM'
                  THEN CASE 
                    WHEN (REGEXP_REPLACE(btrim(ofm."CreatedTime"::text), '\s*[AP]M$', '', 'i'))::int = 12
                      THEN '12:00:00'
                    ELSE LPAD(((REGEXP_REPLACE(btrim(ofm."CreatedTime"::text), '\s*[AP]M$', '', 'i'))::int + 12)::text, 2, '0') || ':00:00'
                  END
                ELSE
                  CASE 
                    WHEN (REGEXP_REPLACE(btrim(ofm."CreatedTime"::text), '\s*[AP]M$', '', 'i'))::int = 12
                      THEN '00:00:00'
                    ELSE LPAD((REGEXP_REPLACE(btrim(ofm."CreatedTime"::text), '\s*[AP]M$', '', 'i'))::text, 2, '0') || ':00:00'
                  END
              END,
              'YYYY-MM-DD HH24:MI:SS'
            )
          /* Fallback: try to parse with normalized hours */
          ELSE COALESCE(
            /* Try with seconds format - Convert to 24-hour format */
            NULLIF(to_timestamp(
              ofm."CreatedDate"::text || ' ' || 
              CASE 
                WHEN UPPER(btrim(ofm."CreatedTime"::text)) LIKE '%PM'
                  THEN CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '12:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                    ELSE LPAD(((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int + 12)::text, 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                  END
                ELSE
                  CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '00:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                    ELSE LPAD(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1), 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                  END
              END,
              'YYYY-MM-DD HH24:MI:SS'
            ), NULL),
            /* Try with minutes only format - Convert to 24-hour format */
            NULLIF(to_timestamp(
              ofm."CreatedDate"::text || ' ' || 
              CASE 
                WHEN UPPER(btrim(ofm."CreatedTime"::text)) LIKE '%PM'
                  THEN CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '12:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                    ELSE LPAD(((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int + 12)::text, 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                  END
                ELSE
                  CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '00:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                    ELSE LPAD(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1), 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                  END
              END,
              'YYYY-MM-DD HH24:MI:SS'
            ), NULL)
          )
        END
      WHEN ofm."CreatedDate"::text ~ '^\d{4}-\d{2}-\d{2}$'
        THEN to_timestamp(ofm."CreatedDate"::text || ' ' || btrim(ofm."CreatedTime"::text),
                        'YYYY-MM-DD HH24:MI:SS')
      WHEN ofm."CreatedTime"::text ~* '(AM|PM)'
        THEN CASE
          /* Has seconds: "09:30:45 AM" or "3:59:59 PM" - Convert to 24-hour format for reliable parsing */
          WHEN ofm."CreatedTime"::text ~ '^\d{1,2}:[0-9]{2}:[0-9]{2}\s*(AM|PM)'
            THEN to_timestamp(
              CASE 
                WHEN ofm."CreatedDate"::text ~ '^\d{4}-\d{2}-\d{2}$' 
                  THEN ofm."CreatedDate"::text
                WHEN ofm."CreatedDate"::text ~ '^\d{1,2}/\d{1,2}/\d{2,4}$'
                  THEN to_date(ofm."CreatedDate"::text, 'MM/DD/YYYY')::text
                ELSE ofm."CreatedDate"::text
              END || ' ' || 
              CASE 
                WHEN UPPER(btrim(ofm."CreatedTime"::text)) LIKE '%PM'
                  THEN CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '12:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                    ELSE LPAD(((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int + 12)::text, 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                  END
                ELSE
                  CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '00:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                    ELSE LPAD(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1), 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                  END
              END,
              'YYYY-MM-DD HH24:MI:SS'
            )
          /* Has minutes: "09:30 AM" or "3:30 AM" - Convert to 24-hour format for reliable parsing */
          WHEN ofm."CreatedTime"::text ~ '^\d{1,2}:[0-9]{2}\s*(AM|PM)'
            THEN to_timestamp(
              CASE 
                WHEN ofm."CreatedDate"::text ~ '^\d{4}-\d{2}-\d{2}$' 
                  THEN ofm."CreatedDate"::text
                WHEN ofm."CreatedDate"::text ~ '^\d{1,2}/\d{1,2}/\d{2,4}$'
                  THEN to_date(ofm."CreatedDate"::text, 'MM/DD/YYYY')::text
                ELSE ofm."CreatedDate"::text
              END || ' ' || 
              CASE 
                WHEN UPPER(btrim(ofm."CreatedTime"::text)) LIKE '%PM'
                  THEN CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '12:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                    ELSE LPAD(((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int + 12)::text, 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                  END
                ELSE
                  CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '00:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                    ELSE LPAD(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1), 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                  END
              END,
              'YYYY-MM-DD HH24:MI:SS'
            )
          /* Just hour: "09 AM" or "9 AM" - Convert to 24-hour format */
          WHEN ofm."CreatedTime"::text ~ '^[0-9]{1,2}\s*(AM|PM)$'
            THEN to_timestamp(
              CASE 
                WHEN ofm."CreatedDate"::text ~ '^\d{4}-\d{2}-\d{2}$' 
                  THEN ofm."CreatedDate"::text
                WHEN ofm."CreatedDate"::text ~ '^\d{1,2}/\d{1,2}/\d{2,4}$'
                  THEN to_date(ofm."CreatedDate"::text, 'MM/DD/YYYY')::text
                ELSE ofm."CreatedDate"::text
              END || ' ' || 
              CASE 
                WHEN UPPER(btrim(ofm."CreatedTime"::text)) LIKE '%PM'
                  THEN CASE 
                    WHEN (REGEXP_REPLACE(btrim(ofm."CreatedTime"::text), '\s*[AP]M$', '', 'i'))::int = 12
                      THEN '12:00:00'
                    ELSE LPAD(((REGEXP_REPLACE(btrim(ofm."CreatedTime"::text), '\s*[AP]M$', '', 'i'))::int + 12)::text, 2, '0') || ':00:00'
                  END
                ELSE
                  CASE 
                    WHEN (REGEXP_REPLACE(btrim(ofm."CreatedTime"::text), '\s*[AP]M$', '', 'i'))::int = 12
                      THEN '00:00:00'
                    ELSE LPAD((REGEXP_REPLACE(btrim(ofm."CreatedTime"::text), '\s*[AP]M$', '', 'i'))::text, 2, '0') || ':00:00'
                  END
              END,
              'YYYY-MM-DD HH24:MI:SS'
            )
          /* Fallback: try to parse with 24-hour conversion */
          ELSE COALESCE(
            /* Try with seconds format - Convert to 24-hour format */
            NULLIF(to_timestamp(
              CASE 
                WHEN ofm."CreatedDate"::text ~ '^\d{4}-\d{2}-\d{2}$' 
                  THEN ofm."CreatedDate"::text
                WHEN ofm."CreatedDate"::text ~ '^\d{1,2}/\d{1,2}/\d{2,4}$'
                  THEN to_date(ofm."CreatedDate"::text, 'MM/DD/YYYY')::text
                ELSE ofm."CreatedDate"::text
              END || ' ' || 
              CASE 
                WHEN UPPER(btrim(ofm."CreatedTime"::text)) LIKE '%PM'
                  THEN CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '12:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                    ELSE LPAD(((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int + 12)::text, 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                  END
                ELSE
                  CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '00:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                    ELSE LPAD(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1), 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':' || SPLIT_PART(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 3), ' ', 1)
                  END
              END,
              'YYYY-MM-DD HH24:MI:SS'
            ), NULL),
            /* Try with minutes only format - Convert to 24-hour format */
            NULLIF(to_timestamp(
              CASE 
                WHEN ofm."CreatedDate"::text ~ '^\d{4}-\d{2}-\d{2}$' 
                  THEN ofm."CreatedDate"::text
                WHEN ofm."CreatedDate"::text ~ '^\d{1,2}/\d{1,2}/\d{2,4}$'
                  THEN to_date(ofm."CreatedDate"::text, 'MM/DD/YYYY')::text
                ELSE ofm."CreatedDate"::text
              END || ' ' || 
              CASE 
                WHEN UPPER(btrim(ofm."CreatedTime"::text)) LIKE '%PM'
                  THEN CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '12:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                    ELSE LPAD(((SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int + 12)::text, 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                  END
                ELSE
                  CASE 
                    WHEN (SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1))::int = 12
                      THEN '00:' || SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                    ELSE LPAD(SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 1), 2, '0') || ':' || 
                         SPLIT_PART(btrim(ofm."CreatedTime"::text), ':', 2) || ':00'
                  END
              END,
              'YYYY-MM-DD HH24:MI:SS'
            ), NULL)
          )
        END
      ELSE to_timestamp(ofm."CreatedDate"::text || ' ' || btrim(ofm."CreatedTime"::text),
                      'MM/DD/YYYY HH24:MI:SS')
    END                                    AS flow_ts_local   -- timestamp without time zone (CST clock)

  FROM public."OptionsFlow_monthly" ofm
  WHERE ofm."Premium" IS NOT NULL
    AND btrim(ofm."Premium"::text) <> ''
    AND ofm."StockEtf" = 'STOCK'
    AND ofm."Symbol" NOT IN ('TSLA','NVDA')  -- your panel excludes these
),

/* 2) Window restriction (CST date window) and compute UTC */
flow AS (
  SELECT
    b.*,
    (b.flow_ts_local)::date                                         AS flow_date_cst,
    /* Convert local CST clock to a real point in time (timestamptz) */
    (b.flow_ts_local AT TIME ZONE 'America/Chicago')                AS flow_ts_utc
  FROM base b
  WHERE (b.flow_ts_local)::date BETWEEN (SELECT start_day FROM cfg) AND (SELECT end_day FROM cfg)
),

/* 3) Direction + moneyness % */
mny AS (
  SELECT
    f.*,
    CASE
      WHEN cp_norm='CALL' AND strike_num >  spot_num THEN 'OTM'
      WHEN cp_norm='CALL' AND strike_num <= spot_num THEN 'ITM'
      WHEN cp_norm='PUT'  AND strike_num <  spot_num THEN 'OTM'
      WHEN cp_norm='PUT'  AND strike_num >= spot_num THEN 'ITM'
    END AS moneyness,
    CASE
      WHEN (cp_norm='CALL' AND side_norm='BUY') OR (cp_norm='PUT' AND side_norm='SELL') THEN 'BULL'
      WHEN (cp_norm='PUT'  AND side_norm='BUY') OR (cp_norm='CALL' AND side_norm='SELL') THEN 'BEAR'
      ELSE NULL
    END AS direction,
    /* +mny_pct means ITM depth; -mny_pct means OTM distance */
    CASE
      WHEN spot_num IS NOT NULL AND spot_num <> 0 AND cp_norm='CALL'
        THEN ((spot_num - strike_num) * 100.0) / NULLIF(spot_num,0)
      WHEN spot_num IS NOT NULL AND spot_num <> 0 AND cp_norm='PUT'
        THEN ((strike_num - spot_num) * 100.0) / NULLIF(spot_num,0)
      ELSE NULL
    END AS mny_pct
  FROM flow f
),

/* 4) Running sums by (exp_date, symbol) — keep your original analytics */
agg AS (
  SELECT
    m.*,

    SUM(CASE WHEN cp_norm='CALL' AND side_norm='BUY'  AND moneyness='OTM' THEN premium_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS prem_cb_otm,
    SUM(CASE WHEN cp_norm='CALL' AND side_norm='BUY'  AND moneyness='ITM' THEN premium_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS prem_cb_itm,
    SUM(CASE WHEN cp_norm='CALL' AND side_norm='SELL' AND moneyness='OTM' THEN premium_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS prem_cs_otm,
    SUM(CASE WHEN cp_norm='CALL' AND side_norm='SELL' AND moneyness='ITM' THEN premium_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS prem_cs_itm,

    SUM(CASE WHEN cp_norm='PUT'  AND side_norm='BUY'  AND moneyness='OTM' THEN premium_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS prem_pb_otm,
    SUM(CASE WHEN cp_norm='PUT'  AND side_norm='BUY'  AND moneyness='ITM' THEN premium_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS prem_pb_itm,
    SUM(CASE WHEN cp_norm='PUT'  AND side_norm='SELL' AND moneyness='OTM' THEN premium_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS prem_ps_otm,
    SUM(CASE WHEN cp_norm='PUT'  AND side_norm='SELL' AND moneyness='ITM' THEN premium_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS prem_ps_itm,

    SUM(vol_num) OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS vol_all,
    SUM(oi_num)  OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS oi_all,

    SUM(CASE WHEN (cp_norm='CALL' AND side_norm='BUY') OR (cp_norm='PUT'  AND side_norm='SELL') THEN vol_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS bull_vol,
    SUM(CASE WHEN (cp_norm='PUT'  AND side_norm='BUY') OR (cp_norm='CALL' AND side_norm='SELL') THEN vol_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS bear_vol,

    SUM(CASE WHEN (cp_norm='CALL' AND side_norm='BUY') OR (cp_norm='PUT'  AND side_norm='SELL') THEN oi_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS bull_oi,
    SUM(CASE WHEN (cp_norm='PUT'  AND side_norm='BUY') OR (cp_norm='CALL' AND side_norm='SELL') THEN oi_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS bear_oi,

    /* OI runs for 💰 badge */
    SUM(CASE WHEN cp_norm='CALL' AND side_norm='BUY' AND moneyness='OTM' THEN oi_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS oi_cb_otm,
    SUM(CASE WHEN cp_norm='PUT'  AND side_norm='BUY' AND moneyness='OTM' THEN oi_num ELSE 0 END)
      OVER (PARTITION BY exp_date, symbol_norm ORDER BY flow_ts_utc, rid) AS oi_pb_otm,

    SUM(1) OVER (PARTITION BY exp_date, symbol_norm, direction ORDER BY flow_ts_utc, rid) AS dir_count
  FROM mny m
),

/* 5) Netting + badges (kept from your logic) */
final1 AS (
  SELECT
    a.*,
    (bull_vol - bear_vol) AS net_vol_raw,
    (bull_oi  - bear_oi ) AS net_oi_raw,
    (prem_cb_itm + prem_ps_itm) AS bull_prem_itm,
    (prem_cs_itm + prem_pb_itm) AS bear_prem_itm,
    (prem_cb_otm + prem_ps_otm) AS otm_bull_raw,
    (prem_pb_otm + prem_cs_otm) AS otm_bear_raw,
    (prem_cb_itm + prem_ps_itm + prem_cb_otm + prem_ps_otm) AS bull_total,
    (prem_cs_itm + prem_pb_itm + prem_cs_otm + prem_pb_otm) AS bear_total
  FROM agg a
),
deco AS (
  SELECT
    f.*,
    CASE
      WHEN (bull_prem_itm) > (bear_prem_itm) THEN '🟢'
      WHEN (bull_prem_itm) < (bear_prem_itm) THEN '🔴'
      ELSE ''
    END AS badge_round,
    (
      CASE WHEN (bull_prem_itm) > (bear_prem_itm) AND prem_cb_itm > prem_ps_itm THEN '💎' ELSE '' END
      || CASE WHEN (bull_prem_itm) < (bear_prem_itm) AND prem_pb_itm > prem_cs_itm THEN '💎' ELSE '' END
      || CASE WHEN (bull_prem_itm) > (bear_prem_itm) AND (otm_bull_raw - otm_bear_raw) > 10000 THEN '⭐' ELSE '' END
      || CASE WHEN (bull_prem_itm) < (bear_prem_itm) AND (otm_bear_raw - otm_bull_raw) > 10000 THEN '⭐' ELSE '' END
      || CASE WHEN (bull_prem_itm) > (bear_prem_itm) AND oi_cb_otm > 100000 THEN '💰' ELSE '' END
      || CASE WHEN (bull_prem_itm) < (bear_prem_itm) AND oi_pb_otm > 100000 THEN '💰' ELSE '' END
      || CASE WHEN COALESCE(vol_num,0) > COALESCE(oi_num,0) THEN '✔' ELSE '' END
    ) AS badge_more,
    CASE
      WHEN premium_num > 10000 AND
           (
             ( (bull_prem_itm) > (bear_prem_itm)
               AND ( (cp_norm='CALL' AND POSITION('AA' IN UPPER("Side"))>0)
                     OR  (cp_norm='PUT'  AND POSITION('BB' IN UPPER("Side"))>0) ) )
             OR
             ( (bull_prem_itm) < (bear_prem_itm)
               AND ( (cp_norm='CALL' AND POSITION('BB' IN UPPER("Side"))>0)
                     OR  (cp_norm='PUT'  AND POSITION('AA' IN UPPER("Side"))>0) ) )
           )
      THEN '⚡'
      ELSE ''
    END AS flash
  FROM final1 f
),
rocketize AS (
  SELECT
    d.*,
    CASE WHEN d.badge_round = '🟢' THEN 1 ELSE 0 END                           AS has_round_green,
    CASE WHEN POSITION('💎' IN d.badge_more) > 0 THEN 1 ELSE 0 END             AS has_diamond,
    CASE WHEN POSITION('⭐' IN d.badge_more) > 0 THEN 1 ELSE 0 END              AS has_star,
    CASE WHEN POSITION('💰' IN d.badge_more) > 0 THEN 1 ELSE 0 END             AS has_money,
    CASE WHEN d.flash = '⚡' THEN 1 ELSE 0 END                                  AS has_flash,
    CASE WHEN COALESCE(d.vol_num,0) > COALESCE(d.oi_num,0) THEN 1 ELSE 0 END   AS has_check,
    CASE WHEN d.premium_num > 1000000 THEN 1 ELSE 0 END                         AS has_fire,
    CASE WHEN d.premium_num >  500000 THEN 1 ELSE 0 END                         AS has_cash,
    CASE
      WHEN ( d.badge_round = '🟢'
             AND POSITION('💎' IN d.badge_more) > 0
             AND POSITION('⭐' IN d.badge_more) > 0
             AND POSITION('💰' IN d.badge_more) > 0
             AND d.flash = '⚡'
             AND d.premium_num > 1000000 )
        THEN '🚀'
      WHEN (COALESCE(d.vol_num,0) > COALESCE(d.oi_num,0))
           AND d.flash = '⚡'
           AND POSITION('💰' IN d.badge_more) > 0
           AND d.premium_num > 500000
        THEN '🚀🚀🚀'
      WHEN (COALESCE(d.vol_num,0) > COALESCE(d.oi_num,0))
           AND d.flash = '⚡'
           AND POSITION('💰' IN d.badge_more) > 0
        THEN '🚀🚀'
      WHEN (COALESCE(d.vol_num,0) > COALESCE(d.oi_num,0))
           AND ( d.flash = '⚡' OR d.premium_num > 500000 )
        THEN '🚀'
      ELSE ''
    END AS rocket
  FROM deco d
),

/* 6) PRICE CONTEXT (use LATERAL for fast top-1 seeks; convert bar times to CST for session bucketing) */
price_ctx AS (
  SELECT
    c.rid,
    c.symbol_norm,
    c.flow_ts_utc,
    c.flow_ts_local,
    c.flow_date_cst,

    /* Session bucket in CST based on flow time */
    CASE
      WHEN EXTRACT(HOUR FROM c.flow_ts_local)::int BETWEEN 4 AND 9
           AND NOT (EXTRACT(HOUR FROM c.flow_ts_local)::int = 9 AND EXTRACT(MINUTE FROM c.flow_ts_local)::int >= 30) THEN 'PRE'
      WHEN (EXTRACT(HOUR FROM c.flow_ts_local)::int = 9 AND EXTRACT(MINUTE FROM c.flow_ts_local)::int >= 30)
        OR (EXTRACT(HOUR FROM c.flow_ts_local)::int > 9 AND EXTRACT(HOUR FROM c.flow_ts_local)::int < 16) THEN 'RTH'
      WHEN EXTRACT(HOUR FROM c.flow_ts_local)::int BETWEEN 16 AND 20 THEN 'POST'
      ELSE 'OFF'
    END AS session_bucket,

    /* last 1m bar <= flow time */
    p1.close  AS u_close,
    p1.high   AS u_high,
    p1.low    AS u_low,
    p1.volume AS u_vol_1m,
    p1.ts     AS u_ts,

    /* first RTH bar >= 09:30:00 CST for that flow date */
    rth.open  AS rth_open,
    rth.ts    AS rth_ts,

    /* prior daily close (CST prior day) */
    pd.close  AS prior_close,

    /* 5m / 15m ago prices */
    p5.close  AS close_5m_ago,
    p15.close AS close_15m_ago

  FROM rocketize c

  /* nearest <= flow */
  LEFT JOIN LATERAL (
    SELECT p.close, p.high, p.low, p.volume, p.ts
    FROM public.prices_intraday_1m p
    WHERE UPPER(p.symbol)=c.symbol_norm
      AND p.ts <= c.flow_ts_utc
    ORDER BY p.ts DESC
    LIMIT 1
  ) p1 ON TRUE

  /* first RTH bar of same CST date (convert bar ts to CST on the fly) */
  LEFT JOIN LATERAL (
    SELECT p.open, p.ts
    FROM public.prices_intraday_1m p
    WHERE UPPER(p.symbol)=c.symbol_norm
      AND (timezone('America/Chicago', p.ts))::date = c.flow_date_cst
      AND (timezone('America/Chicago', p.ts))::time >= time '09:30:00'
    ORDER BY p.ts ASC
    LIMIT 1
  ) rth ON TRUE

  /* prior close (CST prior calendar day) */
  LEFT JOIN LATERAL (
    SELECT d.close
    FROM public.prices_daily d
    WHERE UPPER(d.symbol)=c.symbol_norm
      AND d."Date" = c.flow_date_cst - INTERVAL '1 day'
    ORDER BY d."Date" DESC
    LIMIT 1
  ) pd ON TRUE

  /* 5m ago */
  LEFT JOIN LATERAL (
    SELECT p.close
    FROM public.prices_intraday_1m p
    WHERE UPPER(p.symbol)=c.symbol_norm
      AND p.ts <= c.flow_ts_utc - INTERVAL '5 minutes'
    ORDER BY p.ts DESC
    LIMIT 1
  ) p5 ON TRUE

  /* 15m ago */
  LEFT JOIN LATERAL (
    SELECT p.close
    FROM public.prices_intraday_1m p
    WHERE UPPER(p.symbol)=c.symbol_norm
      AND p.ts <= c.flow_ts_utc - INTERVAL '15 minutes'
    ORDER BY p.ts DESC
    LIMIT 1
  ) p15 ON TRUE
),

/* 7) Price features with proper NUMERIC rounding (avoid double-precision round()) */
price_feats AS (
  SELECT
    px.*,
    /* vs prior close is available in all sessions; vs RTH open only valid during/after RTH */
    CASE
      WHEN px.prior_close IS NOT NULL AND px.u_close IS NOT NULL
        THEN ROUND( (((px.u_close - px.prior_close) * 100.0) / NULLIF(px.prior_close,0))::numeric, 2 )
      ELSE NULL
    END AS pct_vs_prior_close,

    CASE
      WHEN px.rth_open IS NOT NULL
           AND px.u_close IS NOT NULL
           AND px.session_bucket = 'RTH'
        THEN ROUND( (((px.u_close - px.rth_open) * 100.0) / NULLIF(px.rth_open,0))::numeric, 2 )
      ELSE NULL
    END AS pct_vs_rth_open,

    CASE WHEN px.close_5m_ago  IS NOT NULL AND px.u_close IS NOT NULL
      THEN ROUND( (((px.u_close - px.close_5m_ago)  * 100.0) / NULLIF(px.close_5m_ago,0))::numeric, 2 )
      ELSE NULL END AS pct_5m_momo,

    CASE WHEN px.close_15m_ago IS NOT NULL AND px.u_close IS NOT NULL
      THEN ROUND( (((px.u_close - px.close_15m_ago) * 100.0) / NULLIF(px.close_15m_ago,0))::numeric, 2 )
      ELSE NULL END AS pct_15m_momo
  FROM price_ctx px
),

/* 8) ALERTS: normalize Date + AM/PM time → CST local ts → UTC; find nearest within ±15m via LATERAL */
alert_nearest AS (
  SELECT
    c.rid,
    a1.near_alert_type,
    a1.near_alert_msg,
    a1.near_alert_price,
    a1.near_alert_volume,
    a1.event_ts_utc
  FROM rocketize c
  LEFT JOIN LATERAL (
    SELECT q."type"  AS near_alert_type,
           q."message" AS near_alert_msg,
           q."price" AS near_alert_price,
           q."volume" AS near_alert_volume,
           q.event_ts_utc
    FROM (
      SELECT
        a."type",
        a."message",
        a."price",
        a."volume",
        /* Build local event timestamp (CST clock) from text columns */
        /* Handle both HH:MI and HH:MI:SS formats, pad single-digit hours */
        CASE
          WHEN a."date" ~ '^\d{4}-\d{2}-\d{2}$'
            THEN COALESCE(
              /* Try with seconds first if present */
              NULLIF(to_timestamp(
                regexp_replace(
                  a."date" || ' ' || 
                  REGEXP_REPLACE(
                    btrim(a."timestamp"),
                    '^([1-9])(:\d{2}:\d{2}\s*(AM|PM))',
                    '0\\1\\2',
                    'i'
                  ),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'YYYY-MM-DD HH12:MI:SS AM'
              ), NULL),
              /* Fallback to minutes only */
              NULLIF(to_timestamp(
                regexp_replace(
                  a."date" || ' ' || 
                  REGEXP_REPLACE(
                    btrim(a."timestamp"),
                    '^([1-9])(:\d{2}\s*(AM|PM))',
                    '0\\1\\2',
                    'i'
                  ),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'YYYY-MM-DD HH12:MI AM'
              ), NULL),
              /* Last resort: as-is */
              to_timestamp(
                regexp_replace(
                  a."date" || ' ' || btrim(a."timestamp"),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'YYYY-MM-DD HH12:MI AM'
              )
            )
          ELSE COALESCE(
              /* Try with seconds first if present */
              NULLIF(to_timestamp(
                regexp_replace(
                  a."date" || ' ' || 
                  REGEXP_REPLACE(
                    btrim(a."timestamp"),
                    '^([1-9])(:\d{2}:\d{2}\s*(AM|PM))',
                    '0\\1\\2',
                    'i'
                  ),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'MM/DD/YYYY HH12:MI:SS AM'
              ), NULL),
              /* Fallback to minutes only */
              NULLIF(to_timestamp(
                regexp_replace(
                  a."date" || ' ' || 
                  REGEXP_REPLACE(
                    btrim(a."timestamp"),
                    '^([1-9])(:\d{2}\s*(AM|PM))',
                    '0\\1\\2',
                    'i'
                  ),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'MM/DD/YYYY HH12:MI AM'
              ), NULL),
              /* Last resort: as-is */
              to_timestamp(
                regexp_replace(
                  a."date" || ' ' || btrim(a."timestamp"),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'MM/DD/YYYY HH12:MI AM'
              )
            )
        END                                            AS event_ts_local,
        /* Convert to a real point in time (UTC) */
        (CASE
          WHEN a."date" ~ '^\d{4}-\d{2}-\d{2}$'
            THEN COALESCE(
              NULLIF(to_timestamp(
                regexp_replace(
                  a."date" || ' ' || 
                  REGEXP_REPLACE(
                    btrim(a."timestamp"),
                    '^([1-9])(:\d{2}:\d{2}\s*(AM|PM))',
                    '0\\1\\2',
                    'i'
                  ),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'YYYY-MM-DD HH12:MI:SS AM'
              ), NULL),
              NULLIF(to_timestamp(
                regexp_replace(
                  a."date" || ' ' || 
                  REGEXP_REPLACE(
                    btrim(a."timestamp"),
                    '^([1-9])(:\d{2}\s*(AM|PM))',
                    '0\\1\\2',
                    'i'
                  ),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'YYYY-MM-DD HH12:MI AM'
              ), NULL),
              to_timestamp(
                regexp_replace(
                  a."date" || ' ' || btrim(a."timestamp"),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'YYYY-MM-DD HH12:MI AM'
              )
            )
          ELSE COALESCE(
              NULLIF(to_timestamp(
                regexp_replace(
                  a."date" || ' ' || 
                  REGEXP_REPLACE(
                    btrim(a."timestamp"),
                    '^([1-9])(:\d{2}:\d{2}\s*(AM|PM))',
                    '0\\1\\2',
                    'i'
                  ),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'MM/DD/YYYY HH12:MI:SS AM'
              ), NULL),
              NULLIF(to_timestamp(
                regexp_replace(
                  a."date" || ' ' || 
                  REGEXP_REPLACE(
                    btrim(a."timestamp"),
                    '^([1-9])(:\d{2}\s*(AM|PM))',
                    '0\\1\\2',
                    'i'
                  ),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'MM/DD/YYYY HH12:MI AM'
              ), NULL),
              to_timestamp(
                regexp_replace(
                  a."date" || ' ' || btrim(a."timestamp"),
                  '\\s*(AM|PM)$', ' \\1', 'i'
                ),
                'MM/DD/YYYY HH12:MI AM'
              )
            )
         END AT TIME ZONE 'America/Chicago')           AS event_ts_utc
      FROM public."AlertStream_monthly" a
      WHERE UPPER(a."ticker") = c.symbol_norm
    ) q
    WHERE q.event_ts_utc BETWEEN c.flow_ts_utc - INTERVAL '15 minutes'
                         AND c.flow_ts_utc + INTERVAL '15 minutes'
    ORDER BY ABS(EXTRACT(EPOCH FROM (q.event_ts_utc - c.flow_ts_utc))) ASC
    LIMIT 1
  ) a1 ON TRUE
),

/* 9) Enrich + tape-alignment with tolerance + score + Rocket label with [ITM/OTM %] */
flow_enriched AS (
  SELECT
    r.*,
    pf.session_bucket,
    pf.pct_vs_prior_close,
    pf.pct_vs_rth_open,
    pf.pct_5m_momo,
    pf.pct_15m_momo,

    an.near_alert_type,
    an.near_alert_msg,

    /* Tape alignment with tolerance band from cfg */
    CASE
      WHEN pf.session_bucket='PRE'  AND pf.pct_vs_prior_close IS NOT NULL THEN
        CASE WHEN (r.direction='BULL' AND pf.pct_vs_prior_close >= (SELECT tol_pct FROM cfg))
               OR (r.direction='BEAR' AND pf.pct_vs_prior_close <= -(SELECT tol_pct FROM cfg)) THEN 1 ELSE 0 END
      WHEN pf.session_bucket='RTH'  AND pf.pct_vs_rth_open   IS NOT NULL THEN
        CASE WHEN (r.direction='BULL' AND pf.pct_vs_rth_open >= (SELECT tol_pct FROM cfg))
               OR (r.direction='BEAR' AND pf.pct_vs_rth_open <= -(SELECT tol_pct FROM cfg)) THEN 1 ELSE 0 END
      WHEN pf.session_bucket='POST' AND pf.pct_vs_prior_close IS NOT NULL THEN
        CASE WHEN (r.direction='BULL' AND pf.pct_vs_prior_close >= (SELECT tol_pct FROM cfg))
               OR (r.direction='BEAR' AND pf.pct_vs_prior_close <= -(SELECT tol_pct FROM cfg)) THEN 1 ELSE 0 END
      ELSE 0
    END AS tape_alignment,

    CASE WHEN an.near_alert_type IS NOT NULL THEN 1 ELSE 0 END AS catalyst_flag
  FROM rocketize r
  LEFT JOIN price_feats    pf ON pf.rid = r.rid
  LEFT JOIN alert_nearest  an ON an.rid = r.rid
),
scored AS (
  SELECT
    fe.*,
    (CASE
       WHEN fe.premium_num >= 2000000 THEN 3.0
       WHEN fe.premium_num >=  800000 THEN 2.0
       WHEN fe.premium_num >=  200000 THEN 1.0
       ELSE 0.0
     END)
    + 1.5 * CASE
              WHEN (fe.bull_total + fe.bear_total) IS NOT NULL AND (fe.bull_total + fe.bear_total) <> 0
                THEN (fe.bull_total - fe.bear_total) / NULLIF(ABS(fe.bull_total + fe.bear_total),0)
              ELSE 0 END
    + 1.2 * CASE WHEN COALESCE(fe.vol_num,0) > COALESCE(fe.oi_num,0) THEN 1 ELSE 0 END
    + 1.0 * CASE fe.session_bucket WHEN 'RTH' THEN 1 WHEN 'POST' THEN 0.5 WHEN 'PRE' THEN 0.3 ELSE 0 END
    + 1.0 * fe.catalyst_flag
    + 0.8 * CASE
              WHEN fe.cp_norm='CALL' AND fe.strike_num > fe.spot_num THEN 1
              WHEN fe.cp_norm='PUT'  AND fe.strike_num < fe.spot_num THEN 1
              ELSE 0 END
    + 0.5 * fe.tape_alignment
    AS rocket_score
  FROM flow_enriched fe
),
rocket2 AS (
  SELECT
    s.*,
    /* rocket label core */
    CASE
      WHEN s.rocket_score >= 5.0 THEN '🚀🚀🚀'
      WHEN s.rocket_score >= 3.5 THEN '🚀🚀'
      WHEN s.rocket_score >= 2.0 THEN '🚀'
      ELSE COALESCE(s.rocket, '')
    END AS rocket_base,
    /* append [ITM/OTM %] */
    CASE
      WHEN s.mny_pct IS NULL THEN
        (CASE
          WHEN s.rocket_score >= 5.0 THEN '🚀🚀🚀'
          WHEN s.rocket_score >= 3.5 THEN '🚀🚀'
          WHEN s.rocket_score >= 2.0 THEN '🚀'
          ELSE COALESCE(s.rocket, '')
         END)
      ELSE
        (CASE
          WHEN s.rocket_score >= 5.0 THEN '🚀🚀🚀'
          WHEN s.rocket_score >= 3.5 THEN '🚀🚀'
          WHEN s.rocket_score >= 2.0 THEN '🚀'
          ELSE COALESCE(s.rocket, '')
         END)
        || ' [' || CASE WHEN s.mny_pct >= 0 THEN 'ITM ' ELSE 'OTM ' END
        || TRIM(TO_CHAR(ABS(s.mny_pct)::numeric, 'FM999990.0')) || '%]'
    END AS Rocket_with_mny
  FROM scored s
)

/* ==================== FINAL SELECT ==================== */
SELECT
  (fe.flow_ts_local)::date AS "CreatedDate",
  TO_CHAR(fe.flow_ts_local, 'HH12:MI:SS AM') AS "CreatedTime",

  /* Symbol line with session + catalyst + your badges */
  /* Use symbol_norm (clean symbol) instead of fe."Symbol" to avoid duplication */
  (
    CASE
      WHEN fe.direction = 'BULL' THEN '(' || fe.dir_count || '🟩)  '
      WHEN fe.direction = 'BEAR' THEN '(' || fe.dir_count || '🟥)  '
      ELSE ''
    END
    || fe.symbol_norm
    || ' · ' || COALESCE(fe.session_bucket,'?')
    || CASE WHEN fe.near_alert_type IS NOT NULL THEN ' ⚡' ELSE '' END
    || CASE WHEN (fe.badge_round || fe.badge_more) <> '' THEN ' ' || fe.badge_round || fe.badge_more ELSE '' END
    || fe.flash
    || CASE
         WHEN fe.premium_num > 1000000 THEN ' 🔥'
         WHEN fe.premium_num >  500000 THEN ' 💵'
         WHEN fe.premium_num >  100000 THEN ' '
         ELSE ''
       END
  ) AS "Symbol",

  r2.Rocket_with_mny AS "Rocket",

  /* NetPremium and Premium abbreviations as before */
  CASE
    WHEN ABS(fe.bull_total - fe.bear_total) >= 1e6
      THEN (CASE WHEN fe.bull_total - fe.bear_total < 0 THEN '-' ELSE '' END)
           || TRIM(TO_CHAR(ABS(fe.bull_total - fe.bear_total)/1e6, 'FM999990.00')) || ' M'
    WHEN ABS(fe.bull_total - fe.bear_total) >= 1e3
      THEN (CASE WHEN fe.bull_total - fe.bear_total < 0 THEN '-' ELSE '' END)
           || TRIM(TO_CHAR(ROUND(ABS(fe.bull_total - fe.bear_total)/1e3)::numeric, 'FM999990')) || ' K'
    ELSE TRIM(TO_CHAR(ROUND(fe.bull_total - fe.bear_total)::numeric, 'FM999990'))
  END AS "NetPremium",

  CASE
    WHEN fe.premium_num IS NULL THEN NULL
    WHEN ABS(fe.premium_num) >= 1e6
      THEN (CASE WHEN fe.premium_num < 0 THEN '-' ELSE '' END)
           || TRIM(TO_CHAR(ABS(fe.premium_num)/1e6, 'FM999990.00')) || ' M'
    WHEN ABS(fe.premium_num) >= 1e3
      THEN (CASE WHEN fe.premium_num < 0 THEN '-' ELSE '' END)
           || TRIM(TO_CHAR(ROUND(ABS(fe.premium_num)/1e3)::numeric, 'FM999990')) || ' K'
    ELSE TRIM(TO_CHAR(ROUND(fe.premium_num)::numeric, 'FM999990'))
  END AS "Premium",

  fe."ExpirationDate", fe."Price", fe."CallPut", fe."Side", fe."Strike", fe."Spot", fe."Volume", fe."OI",

  /* trader-facing context — now meaningful */
  fe.pct_vs_prior_close AS "PctVsPriorClose",
  fe.pct_vs_rth_open    AS "PctVsRthOpen",
  fe.pct_5m_momo        AS "Pct5m",
  fe.pct_15m_momo       AS "Pct15m",

  /* Tape alignment arrows with tolerance */
  CASE
    WHEN fe.tape_alignment=1 AND fe.direction='BULL' THEN '↗︎'
    WHEN fe.tape_alignment=1 AND fe.direction='BEAR' THEN '↘︎'
    ELSE ''
  END AS "TapeAlign",

  fe.near_alert_type    AS "NearAlert",

  /* Raw numeric columns needed for JavaScript processing */
  fe.premium_num,
  fe.bull_total,
  fe.bear_total,
  fe.bull_prem_itm,
  fe.bear_prem_itm,
  fe.prem_cb_itm,
  fe.prem_ps_itm,
  fe.prem_pb_itm,
  fe.prem_cs_itm,
  fe.prem_cb_otm,
  fe.prem_ps_otm,
  fe.prem_pb_otm,
  fe.prem_cs_otm,
  fe.oi_cb_otm,
  fe.oi_pb_otm,
  fe.vol_num,
  fe.oi_num,
  fe.direction,
  fe.badge_round,
  fe.badge_more,
  fe.flash,
  fe.rocket,
  fe.cp_norm,
  fe.side_norm,
  fe.symbol_norm,
  fe.dir_count,
  fe.session_bucket,
  fe.flow_ts_utc,
  fe.mny_pct

FROM rocket2 r2
JOIN flow_enriched fe ON fe.rid = r2.rid
WHERE
  fe.premium_num > 80000
  -- Match working query: require badge_round AND diamond AND star
  AND (
    fe.badge_round IN ('🟢','🔴')
    AND POSITION('💎' IN fe.badge_more) > 0
    AND POSITION('⭐' IN fe.badge_more) > 0
  )
  -- Direction alignment: BULL with green badge and positive net, or BEAR with red badge and negative net
  AND (
    (fe.direction = 'BULL' AND fe.badge_round = '🟢' AND (fe.bull_total - fe.bear_total) > 0)
    OR (fe.direction = 'BEAR' AND fe.badge_round = '🔴' AND (fe.bull_total - fe.bear_total) < 0)
  )
ORDER BY fe.flow_ts_utc DESC, fe.rid DESC
LIMIT 1000;
`;

/**
 * Build parameters for Script 1 options flow query
 * @param {Date|string} startDate - Start date
 * @param {Date|string} endDate - End date
 * @returns {Object} Parameter object for raw SQL query
 */
export function buildOptionsFlowParams(startDate, endDate) {
  // Normalize dates to PostgreSQL-safe ISO 8601 format at the Node.js boundary
  // This ensures consistent date handling and prevents parsing errors
  const start_day = toPgDate(startDate);
  const end_day = toPgDate(endDate);

  return {
    start_day: start_day,
    end_day: end_day,
    tol_pct: 0.20,
    per_print_min_notional: 1000000,
    // ... all other params from Script 1
  };
}

export class OptionsFlowQuery {
  /**
   * Get recent options flow data using raw SQL (Script 1 style)
   * @param {Object} filters - Query filters
   * @param {boolean} useRawSQL - Whether to use raw SQL query or query builder
   * @returns {Promise<Array>} Options flow entries
   */
  static async getRecentFlow(filters = {}, useRawSQL = false) {
    // Use raw SQL if enabled and SQL query is defined
    if (useRawSQL && optionsFlowQuerySQL && !optionsFlowQuerySQL.includes('PASTE')) {
      try {
        const params = buildOptionsFlowParams({
          startDate: filters.startDate || new Date(Date.now() - 24 * 60 * 60 * 1000),
          endDate: filters.endDate || new Date(),
          tolPct: filters.tolPct || 0.20,
          perPrintMinNotional: filters.minPremium || 1000000,
          limit: filters.limit || 100
        });

        const result = await rawQuery(optionsFlowQuerySQL, params);
        
        // Parse result (assuming it returns JSONB array)
        if (Array.isArray(result)) {
          return result;
        } else if (result && Array.isArray(result.data)) {
          return result.data;
        } else {
          return [];
        }
      } catch (error) {
        console.warn('Raw SQL query failed, falling back to query builder:', error.message);
        // Fall through to query builder
      }
    }

    // Fallback to query builder method
    const {
      limit = 100,
      symbol = null,
      type = null, // CALL or PUT
      minPremium = null,
      startDate = null,
      endDate = null,
      orderBy = 'created_at',
      orderDirection = 'desc'
    } = filters;
    
    let query = supabase
      .from('options_flow')
      .select('*')
      .order(orderBy, { ascending: orderDirection === 'asc' })
      .limit(limit);
    
    if (symbol) {
      query = query.eq('symbol', symbol.toUpperCase());
    }
    
    if (type) {
      query = query.eq('type', type.toUpperCase());
    }
    
    if (minPremium) {
      query = query.gte('total_premium', minPremium);
    }
    
    if (startDate) {
      query = query.gte('created_at', startDate);
    }
    
    if (endDate) {
      query = query.lte('created_at', endDate);
    }
    
    const { data, error } = await query;
    
    if (error) {
      throw new Error(`Database query failed: ${error.message}`);
    }
    
    return data || [];
  }
  
  /**
   * Get flow statistics by symbol
   * @param {string} symbol - Stock symbol
   * @param {Object} timeRange - Time range filter
   * @returns {Promise<Object>} Flow statistics
   */
  static async getFlowStats(symbol, timeRange = {}) {
    const { startDate = null, endDate = null } = timeRange;
    
    let query = supabase
      .from('options_flow')
      .select('*')
      .eq('symbol', symbol.toUpperCase());
    
    if (startDate) {
      query = query.gte('created_at', startDate);
    }
    
    if (endDate) {
      query = query.lte('created_at', endDate);
    }
    
    const { data, error } = await query;
    
    if (error) {
      throw new Error(`Database query failed: ${error.message}`);
    }
    
    if (!data || data.length === 0) {
      return {
        symbol,
        totalTrades: 0,
        totalPremium: 0,
        callVolume: 0,
        putVolume: 0,
        avgSentiment: 0
      };
    }
    
    const calls = data.filter(f => f.type === 'CALL');
    const puts = data.filter(f => f.type === 'PUT');
    
    return {
      symbol,
      totalTrades: data.length,
      totalPremium: data.reduce((sum, f) => sum + (f.total_premium || 0), 0),
      callVolume: calls.reduce((sum, f) => sum + (f.volume || 0), 0),
      putVolume: puts.reduce((sum, f) => sum + (f.volume || 0), 0),
      avgSentiment: data.reduce((sum, f) => sum + (f.sentiment || 0.5), 0) / data.length,
      callPutRatio: puts.length > 0 ? calls.length / puts.length : calls.length
    };
  }
  
  /**
   * Get flow by ID
   * @param {string} id - Flow entry ID
   * @returns {Promise<Object>} Flow entry
   */
  static async getFlowById(id) {
    const { data, error } = await supabase
      .from('options_flow')
      .select('*')
      .eq('id', id)
      .single();
    
    if (error) {
      throw new Error(`Database query failed: ${error.message}`);
    }
    
    return data;
  }
  
  /**
   * Search flows by multiple criteria
   * @param {Object} searchParams - Search parameters
   * @returns {Promise<Array>} Matching flow entries
   */
  static async searchFlows(searchParams) {
    const {
      symbols = [],
      minScore = null,
      badges = [],
      timeWindow = null
    } = searchParams;
    
    let query = supabase
      .from('options_flow')
      .select('*');
    
    if (symbols.length > 0) {
      query = query.in('symbol', symbols.map(s => s.toUpperCase()));
    }
    
    if (minScore) {
      query = query.gte('rocket_score', minScore);
    }
    
    if (timeWindow) {
      const startDate = new Date();
      startDate.setHours(startDate.getHours() - timeWindow);
      query = query.gte('created_at', startDate.toISOString());
    }
    
    const { data, error } = await query
      .order('created_at', { ascending: false })
      .limit(500);
    
    if (error) {
      throw new Error(`Database query failed: ${error.message}`);
    }
    
    return data || [];
  }
}

