/**
 * @license
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * TanStack Query hooks over the POS client in `./posApi`.
 *
 * The tenant-wide hooks fan out — one request per outlet, six in flight — and
 * merge here, because the POS API has no tenant parameter. That is deliberate
 * for now: the largest tenant on the platform has 22 outlets, so the worst case
 * is 44 requests per refresh, which is comfortable. A `GetTenantSalesSummary`
 * on the backend would collapse it to one, and is worth adding once there is a
 * measurement that justifies it.
 */

import { useQuery } from '@tanstack/react-query';
import {
  getPosLocationHealth,
  getPosSales,
  getPosSaleDetail,
  getPosCatalogue,
  getPosSalesSummary,
  mapLimit,
  mergeSummaries,
  parseTerminal,
  type PosBill,
  type PosCatalogue,
  type PosSalesFilter,
  type PosSalesPage,
  type PosSalesSummary,
  type PosTerminal,
} from './posApi';

/** Heartbeats land every 30s, so polling faster only burns requests. */
const HEALTH_POLL_MS = 30_000;
const FANOUT_LIMIT = 6;

export const posKeys = {
  all: ['pos'] as const,
  health: (ids: number[]) => [...posKeys.all, 'health', ids.join(',')] as const,
  summary: (ids: number[], from: string, to: string) =>
    [...posKeys.all, 'summary', ids.join(','), from, to] as const,
  sales: (f: PosSalesFilter) =>
    [
      ...posKeys.all,
      'sales',
      f.locationid,
      f.fromdate ?? '',
      f.todate ?? '',
      f.terminalid ?? '',
      f.cashiername ?? '',
      f.paymentmode ?? '',
      f.pageno ?? 0,
      f.pagesize ?? 50,
    ] as const,
  catalogue: (locationid: number) => [...posKeys.all, 'catalogue', locationid] as const,
  detail: (locationid: number, reference: string) =>
    [...posKeys.all, 'detail', locationid, reference] as const,
};

/** One outlet's tills, with the outlet id kept alongside so a tenant board can
 *  attribute a terminal to a store without a second lookup. */
export interface PosOutletHealth {
  locationid: number;
  total: number;
  online: number;
  terminals: PosTerminal[];
}

/**
 * Terminal health across one or more outlets.
 *
 * Parsing happens in the query function rather than the component so the Redis
 * strings are converted exactly once per fetch, not on every render.
 */
export function usePosFleetHealth(locationIds: number[]) {
  const ids = [...new Set(locationIds.filter((n) => n > 0))].sort((a, b) => a - b);
  return useQuery({
    queryKey: posKeys.health(ids),
    enabled: ids.length > 0,
    refetchInterval: HEALTH_POLL_MS,
    queryFn: async (): Promise<PosOutletHealth[]> => {
      const now = Date.now();
      return mapLimit(ids, FANOUT_LIMIT, async (locationid) => {
        try {
          const raw = await getPosLocationHealth(locationid);
          const terminals = (raw.terminals ?? []).map((t) => parseTerminal(t, now));
          // `online` is recounted here rather than trusted: the server counts
          // status === "online", which cannot distinguish a fresh heartbeat
          // from one that is about to expire.
          return {
            locationid,
            total: terminals.length,
            online: terminals.filter((t) => t.state === 'online').length,
            terminals,
          };
        } catch {
          // One outlet's Redis being unreachable must not blank the board for
          // every other shop.
          return { locationid, total: 0, online: 0, terminals: [] };
        }
      });
    },
  });
}

export interface PosScopedSummary {
  /** Per-outlet, for a leaderboard. Null where the outlet has no POS data. */
  perOutlet: Array<{ locationid: number; summary: PosSalesSummary | null }>;
  /** All outlets folded together, with `averagebill` recomputed. */
  merged: PosSalesSummary;
}

/**
 * Sales totals for a date range across one or more outlets.
 *
 * The merge recomputes `averagebill` from the summed totals — see
 * `mergeSummaries`.
 */
export function usePosSalesSummary(locationIds: number[], fromdate: string, todate: string) {
  const ids = [...new Set(locationIds.filter((n) => n > 0))].sort((a, b) => a - b);
  return useQuery({
    queryKey: posKeys.summary(ids, fromdate, todate),
    enabled: ids.length > 0 && Boolean(fromdate) && Boolean(todate),
    queryFn: async (): Promise<PosScopedSummary> => {
      const perOutlet = await mapLimit(ids, FANOUT_LIMIT, async (locationid) => {
        try {
          return { locationid, summary: await getPosSalesSummary({ locationid, fromdate, todate }) };
        } catch {
          return { locationid, summary: null };
        }
      });
      return { perOutlet, merged: mergeSummaries(perOutlet.map((p) => p.summary)) };
    },
  });
}

/** A page of bills for one outlet. Bills are always single-outlet: `locationid`
 *  is the authorisation boundary and the API takes exactly one. */
export function usePosSales(filter: PosSalesFilter) {
  return useQuery({
    queryKey: posKeys.sales(filter),
    enabled: filter.locationid > 0,
    queryFn: (): Promise<PosSalesPage> => getPosSales(filter),
    placeholderData: (prev) => prev,
  });
}

/** One bill with its lines. Resolves to null when the reference does not belong
 *  to this outlet — the server answers 404 for that case on purpose. */
export function usePosSaleDetail(locationid: number, reference: string) {
  return useQuery({
    queryKey: posKeys.detail(locationid, reference),
    enabled: locationid > 0 && reference.trim().length > 0,
    queryFn: (): Promise<PosBill | null> => getPosSaleDetail(locationid, reference),
  });
}

/**
 * The product snapshot a till at this store is working from.
 *
 * Deliberately **not** fanned out across the tenant and gated on `enabled`, so
 * it is fetched for one outlet only and only once its panel is opened. A
 * snapshot is the whole shelf, and pulling one per outlet on every page load
 * would be the heaviest thing on the page for a diagnostic most visits never
 * look at.
 */
export function usePosCatalogue(locationid: number, enabled: boolean) {
  return useQuery({
    queryKey: posKeys.catalogue(locationid),
    enabled: enabled && locationid > 0,
    staleTime: 60_000,
    queryFn: (): Promise<PosCatalogue> => getPosCatalogue(locationid),
  });
}
