/**
 * Task #295 — daily inventory reset worker.
 *
 * Per-restaurant, at the LOCAL midnight of any of its branches' timezones
 * (we use min(branches.timezone) per restaurant, falling back to 'UTC'),
 * any menu_items row with `daily_reset_count IS NOT NULL` whose
 * `last_reset_at` is NULL or older than the start of that local day is
 * reset:
 *
 *   - stock_count → daily_reset_count
 *   - is_in_stock → true *only* when auto_disabled_at_zero=true (so manual
 *                   "off" stays off across the reset)
 *   - auto_disabled_at_zero → false
 *   - last_low_stock_alert_at → NULL (cooldown lifts so the next crossing
 *                                     re-fires)
 *   - last_reset_at → NOW()
 *
 * The worker ticks every 60s. Because the WHERE clause re-checks
 * `last_reset_at < start_of_today_local`, missed ticks (server restart,
 * deploy, etc.) self-heal on the next minute. There is no "catch-up" of
 * missed days — we only ever reset to the LATEST target, which is exactly
 * what an operator expects after a downtime window.
 *
 * Worker registration uses the HMR-safe helper so Fast Refresh in dev
 * doesn't stack timers.
 */

import { db } from '@server/db/drizzle';
import { sql } from 'drizzle-orm';
import { registerHmrTimer, clearHmrTimer, isHmrTimerRegistered } from '@server/lib/hmrTimer';

import { childLogger } from '@server/logger';
const log = childLogger('svc.inventory');

const TIMER_KEY = 'inventory-daily-reset';
const MODULE_TOKEN = {};
const TICK_MS = 60 * 1000;
const FIRST_TICK_DELAY_MS = 30_000;

/**
 * Execute one pass of the daily reset across all restaurants. Exported for
 * tests + manual invocation.
 *
 * Strategy: pick each restaurant whose smallest branch timezone is
 * "currently at-or-past local midnight today", then reset their items in
 * a single UPDATE keyed off the restaurant + the local-day boundary.
 * Doing the boundary math in SQL means we never juggle TZ libraries on
 * the JS side and we stay correct across DST without extra code.
 */
export async function runInventoryDailyReset(): Promise<{ restaurants: number; itemsReset: number }> {
  // Pull every restaurant that owns at least one item with a daily reset
  // configured, alongside the timezone we'll use to define "today".
  // restaurants without branches default to UTC.
  const { rows: rsRows } = await db.execute(sql`
    SELECT mi.restaurant_id AS id,
           COALESCE(MIN(b.timezone), 'UTC') AS tz
      FROM menu_items mi
      LEFT JOIN branches b ON b.restaurant_id = mi.restaurant_id
     WHERE mi.daily_reset_count IS NOT NULL
     GROUP BY mi.restaurant_id
  `);

  let totalReset = 0;
  let touched = 0;
  for (const r of rsRows as Array<{ id: string; tz: string }>) {
    const tz = r.tz || 'UTC';
    // start_of_today_local in the requested timezone, expressed back as a
    // timestamptz for direct comparison with last_reset_at. The cast chain
    // (timestamptz → local timestamp → date_trunc → timestamptz at tz)
    // is the standard "today's midnight in tz X" idiom in PG.
    try {
      const { rowCount } = await db.execute(sql`
        UPDATE menu_items
           SET stock_count = daily_reset_count,
               is_in_stock = CASE WHEN auto_disabled_at_zero THEN true ELSE is_in_stock END,
               auto_disabled_at_zero = false,
               last_low_stock_alert_at = NULL,
               last_sold_out_alert_at = NULL,
               last_reset_at = NOW(),
               updated_at = NOW()
         WHERE restaurant_id = ${r.id}
           AND daily_reset_count IS NOT NULL
           AND (
             last_reset_at IS NULL
             OR last_reset_at < (date_trunc('day', NOW() AT TIME ZONE ${tz}) AT TIME ZONE ${tz})
           )
      `);
      const n = rowCount ?? 0;
      if (n > 0) {
        touched += 1;
        totalReset += n;
      }
    } catch (e) {
      // Most likely cause is an invalid timezone string in the branches
      // row. Log it and continue so one bad restaurant doesn't break the
      // whole tick.
      log.warn({ err: e, restaurantId: r.id, tz }, 'Inventory reset failed for restaurant');
    }
  }
  if (totalReset > 0) {
    log.info({ totalReset, restaurants: touched }, 'daily reset complete');
  }
  return { restaurants: touched, itemsReset: totalReset };
}

export function startInventoryResetWorker(): void {
  const wasRegistered = isHmrTimerRegistered(TIMER_KEY);
  registerHmrTimer({
    key: TIMER_KEY,
    moduleToken: MODULE_TOKEN,
    initialDelayMs: FIRST_TICK_DELAY_MS,
    intervalMs: TICK_MS,
    tick: async () => {
      try { await runInventoryDailyReset(); }
      catch (e) { log.warn({ err: e }, 'Inventory reset tick failed'); }
    },
  });
  if (!wasRegistered) {
    log.info('daily-reset worker started (interval=60s, first tick in 30s)');
  }
}

export function stopInventoryResetWorker(): void {
  clearHmrTimer(TIMER_KEY);
}
