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

/**
 * Workolik Hasura REST client — the `/api/rest/*` surface documented at
 * https://developer.nearledaily.com (Users, Orders, Tenants, Products,
 * Apps & Locations, Partners). The "Mobile" section is intentionally omitted.
 *
 * All requests go through the Vite dev proxy at `/hasura/*`, which rewrites to
 * `https://api.workolik.com/api/rest/*` and injects the `x-hasura-admin-secret`
 * header server-side (see vite.config.ts). The secret never reaches the browser.
 *
 * Components should not call these directly — use the TanStack Query hooks in
 * `./queries`, which add caching, dedup, and loading/error state.
 */

import { cleanTenantLocations } from './fiestaApi';

const HASURA_BASE = '/hasura';

/** Tenant whose live data the dashboard displays. */
export const DEFAULT_TENANT_ID = 1087;

/** Order-module config the tenant's order summary is computed against. */
export const DEFAULT_CONFIG_ID = 1;

type QueryParams = Record<string, string | number | undefined | null>;

async function hasuraGet<T = unknown>(endpoint: string, params: QueryParams = {}): Promise<T> {
  const qs = new URLSearchParams();
  Object.entries(params).forEach(([k, v]) => {
    if (v !== undefined && v !== null && v !== '') qs.append(k, String(v));
  });
  const query = qs.toString();
  const res = await fetch(`${HASURA_BASE}/${endpoint}${query ? `?${query}` : ''}`, {
    headers: { Accept: 'application/json' },
  });
  if (!res.ok) {
    throw new Error(`Hasura ${endpoint} failed: ${res.status} ${res.statusText}`);
  }
  return res.json() as Promise<T>;
}

/**
 * Hasura REST responses come back in a few shapes depending on the route:
 *   - a bare array:                       [ {...}, {...} ]
 *   - an envelope with `details`:         { details: [...] } or { details: {...} }
 *   - an envelope keyed by the table:     { tenants: [...] }, { orders: [...] }
 * This normalizes any of those to an array of rows.
 */
export function toRows<T = Record<string, unknown>>(json: unknown): T[] {
  if (Array.isArray(json)) return json as T[];
  if (json && typeof json === 'object') {
    const obj = json as Record<string, unknown>;
    if (Array.isArray(obj.details)) return obj.details as T[];
    if (obj.details && typeof obj.details === 'object') return [obj.details as T];
    if (Array.isArray(obj.data)) return obj.data as T[];
    // Envelope keyed by the route/table name (Hasura's default for REST endpoints).
    const firstArray = Object.values(obj).find((v) => Array.isArray(v));
    if (Array.isArray(firstArray)) return firstArray as T[];
  }
  return [];
}

function firstRow<T = Record<string, unknown>>(json: unknown): T | null {
  const rows = toRows<T>(json);
  return rows.length ? rows[0] : null;
}

function n(v: unknown): number {
  const num = typeof v === 'number' ? v : Number(v);
  return Number.isFinite(num) ? num : 0;
}

type Row = Record<string, unknown>;
/** ISO datetime helpers for endpoints that expect full timestamps. */
export const startOfDay = (d: string) => (d.includes('T') ? d : `${d}T00:00:00`);
export const endOfDay = (d: string) => (d.includes('T') ? d : `${d}T23:59:59`);

// ════════════════════════════════════════════════════════════════════════════
// USERS
// ════════════════════════════════════════════════════════════════════════════

/** /getusers?roleid=&tenantid=&limit=&offset= — list users in the system. */
export async function getUsers(opts: {
  tenantid: number;
  roleid?: number;
  limit?: number;
  offset?: number;
}): Promise<Row[]> {
  return toRows(
    await hasuraGet('getusers', {
      tenantid: opts.tenantid,
      roleid: opts.roleid,
      limit: opts.limit ?? 10,
      offset: opts.offset ?? 0,
    }),
  );
}

/** /getapproles?configid= — all application roles for a config. */
export async function getAppRoles(configid: number = 15): Promise<Row[]> {
  return toRows(await hasuraGet('getapproles', { configid }));
}

// ════════════════════════════════════════════════════════════════════════════
// ORDERS
// ════════════════════════════════════════════════════════════════════════════

/**
 * /getorders?start=&end=&status=&limit=&offset= — detailed orders in a window.
 * NOTE: this is a global feed (no tenant filter accepted) and `status` is required.
 */
export async function getOrders(opts: {
  start: string; // ISO with time
  end: string;
  status: string; // e.g. 'delivered' | 'created' | 'cancelled'
  limit?: number;
  offset?: number;
}): Promise<Row[]> {
  return toRows(
    await hasuraGet('getorders', {
      start: startOfDay(opts.start),
      end: endOfDay(opts.end),
      status: opts.status,
      limit: opts.limit ?? 10,
      offset: opts.offset ?? 0,
    }),
  );
}

export interface OrderSummary {
  total: number;
  created: number;
  pending: number;
  processing: number;
  delivered: number;
  cancelled: number;
  tenantid?: number;
  tenantname?: string;
}

/** /getordersummary?tenantid=&configid=&fromdate=&todate= — aggregated order counts. */
export async function getOrderSummary(
  tenantid: number,
  fromdate: string,
  todate: string,
  configid: number = DEFAULT_CONFIG_ID,
): Promise<OrderSummary | null> {
  // Workolik returns counts as { total: { aggregate: { count: N } }, ... } plus a
  // `tenants` array. (The fiesta variant returns flat numbers.) Read both shapes.
  const json = await hasuraGet<Row>('getordersummary/', { tenantid, fromdate, todate, configid });
  if (!json || typeof json !== 'object') return null;

  const count = (key: string): number => {
    const node = (json as Row)[key];
    if (node && typeof node === 'object' && 'aggregate' in (node as Row)) {
      const agg = (node as { aggregate?: { count?: unknown } }).aggregate;
      return n(agg?.count);
    }
    return n(node);
  };

  const tenants = (json as { tenants?: Row[] }).tenants;
  const tenantRow = Array.isArray(tenants) && tenants.length ? tenants[0] : null;

  return {
    total: count('total'),
    created: count('created'),
    pending: count('pending'),
    processing: count('processing'),
    delivered: count('delivered'),
    cancelled: count('cancelled'),
    tenantid: tenantRow?.tenantid != null ? n(tenantRow.tenantid) : undefined,
    tenantname: typeof tenantRow?.tenantname === 'string' ? (tenantRow.tenantname as string) : undefined,
  };
}

// ════════════════════════════════════════════════════════════════════════════
// TENANTS
// ════════════════════════════════════════════════════════════════════════════

/** /gettenantinfo?tenantid= — info about a tenant. */
export async function getTenantInfo(tenantid: number): Promise<Row | null> {
  return firstRow(await hasuraGet('gettenantinfo', { tenantid }));
}

/**
 * /gettenantlocations?tenantid= — physical locations linked to a tenant.
 * Piped through the shared cleaner (dedupe + strip test rows) so it matches the
 * Fiesta source. The Hasura and Fiesta tenant-location tables share the same DB,
 * so both return the same duplicate/junk rows without this.
 */
export async function getTenantLocations(tenantid: number): Promise<Row[]> {
  return cleanTenantLocations(toRows(await hasuraGet('gettenantlocations', { tenantid })));
}

/** /getcustomersbytenant?tenantid=&limit=&offset= — customers for a tenant. */
export async function getCustomersByTenant(opts: {
  tenantid: number;
  limit?: number;
  offset?: number;
}): Promise<Row[]> {
  return toRows(
    await hasuraGet('getcustomersbytenant', {
      tenantid: opts.tenantid,
      limit: opts.limit ?? 10,
      offset: opts.offset ?? 0,
    }),
  );
}

/** /gettenantcustomers?tenantid=&locationid=&limit=&offset= — customers under a tenant+location. */
export async function getTenantCustomers(opts: {
  tenantid: number;
  locationid: number;
  limit?: number;
  offset?: number;
}): Promise<Row[]> {
  return toRows(
    await hasuraGet('gettenantcustomers', {
      tenantid: opts.tenantid,
      locationid: opts.locationid,
      limit: opts.limit ?? 10,
      offset: opts.offset ?? 0,
    }),
  );
}

/** /gettenantdeliveries?tenantid=&status=&fromdate=&todate=&keyword=&limit=&offset= */
export async function getTenantDeliveries(opts: {
  tenantid: number;
  status?: string;
  fromdate: string; // ISO with time
  todate: string;
  keyword?: string;
  limit?: number;
  offset?: number;
}): Promise<Row[]> {
  return toRows(
    await hasuraGet('gettenantdeliveries', {
      tenantid: opts.tenantid,
      status: opts.status,
      fromdate: startOfDay(opts.fromdate),
      todate: endOfDay(opts.todate),
      keyword: opts.keyword ?? '%',
      limit: opts.limit ?? 10,
      offset: opts.offset ?? 0,
    }),
  );
}

// ════════════════════════════════════════════════════════════════════════════
// PRODUCTS
// ════════════════════════════════════════════════════════════════════════════

/** /getproductcategories?moduleid= — root product categories for a module. */
export async function getProductCategories(moduleid: number): Promise<Row[]> {
  return toRows(await hasuraGet('getproductcategories', { moduleid }));
}

/** /getsubcategory?moduleid=&categoryid= — a subcategory under a root category. */
export async function getSubcategory(moduleid: number, categoryid: number): Promise<Row[]> {
  return toRows(await hasuraGet('getsubcategory', { moduleid, categoryid }));
}

/** /getproductsubcategories?categoryid= — all subcategories under a root category. */
export async function getProductSubcategories(categoryid: number): Promise<Row[]> {
  return toRows(await hasuraGet('getproductsubcategories', { categoryid }));
}

/** /getproductvariants?tenantid=&subcategoryid= — product variants for a tenant. */
export async function getProductVariants(tenantid: number, subcategoryid: number): Promise<Row[]> {
  return toRows(await hasuraGet('getproductvariants', { tenantid, subcategoryid }));
}

/** /getstockstatement?tenantid=&locationid=&subcategoryid=&keyword=&limit=&offset= */
export async function getStockStatement(opts: {
  tenantid: number;
  locationid: number;
  subcategoryid?: number;
  keyword?: string;
  limit?: number;
  offset?: number;
}): Promise<Row[]> {
  return toRows(
    await hasuraGet('getstockstatement', {
      tenantid: opts.tenantid,
      locationid: opts.locationid,
      subcategoryid: opts.subcategoryid,
      keyword: opts.keyword ?? '%',
      limit: opts.limit ?? 10,
      offset: opts.offset ?? 0,
    }),
  );
}

/** /getproductscount?tenantid=&categoryid=&subcategoryid= — available/out-of-stock counts. */
export async function getProductsCount(opts: {
  tenantid: number;
  categoryid: number;
  subcategoryid?: number;
}): Promise<Row | null> {
  return firstRow(
    await hasuraGet('getproductscount', {
      tenantid: opts.tenantid,
      categoryid: opts.categoryid,
      subcategoryid: opts.subcategoryid,
    }),
  );
}

// ════════════════════════════════════════════════════════════════════════════
// APPS & LOCATIONS
// ════════════════════════════════════════════════════════════════════════════

/** /getapplocations — all currently active locations globally. */
export async function getAppLocations(): Promise<Row[]> {
  return toRows(await hasuraGet('getapplocations'));
}

/** /getapplocationconfig — global configuration for application locations. */
export async function getAppLocationConfig(): Promise<Row[]> {
  return toRows(await hasuraGet('getapplocationconfig'));
}

/** /getapptypes?tag= — application types grouped by a tag (e.g. 'partner', 'DELIVERY'). */
export async function getAppTypes(tag: string): Promise<Row[]> {
  return toRows(await hasuraGet('getapptypes', { tag }));
}

// ════════════════════════════════════════════════════════════════════════════
// PARTNERS
// ════════════════════════════════════════════════════════════════════════════

/** /getpartners?applocationid=&partnerid=&limit=&offset= — active partners. */
export async function getPartners(opts: {
  applocationid: number;
  partnerid?: number;
  limit?: number;
  offset?: number;
}): Promise<Row[]> {
  return toRows(
    await hasuraGet('getpartners', {
      applocationid: opts.applocationid,
      partnerid: opts.partnerid ?? 0,
      limit: opts.limit ?? 10,
      offset: opts.offset ?? 0,
    }),
  );
}

/** /getridershifts?applocationid= — historic + active rider shift records. */
export async function getRiderShifts(applocationid: number): Promise<Row[]> {
  return toRows(await hasuraGet('getridershifts', { applocationid }));
}

// ════════════════════════════════════════════════════════════════════════════
// INVOICE (used by the dashboard; not part of a public docs section)
// ════════════════════════════════════════════════════════════════════════════

export interface InvoiceInsight {
  revenue: number;
  profit: number;
  raw: Row;
}

/** /getinvoiceinsight?tenantid= — revenue / financial roll-up for a tenant. */
export async function getInvoiceInsight(tenantid: number): Promise<InvoiceInsight | null> {
  const row = firstRow<Row>(await hasuraGet('getinvoiceinsight', { tenantid }));
  if (!row) return null;
  const pick = (...keys: string[]) => {
    for (const k of keys) {
      if (row[k] != null && row[k] !== '') return n(row[k]);
    }
    return 0;
  };
  return {
    revenue: pick('revenue', 'totalrevenue', 'grossrevenue', 'sales', 'totalsales', 'totalamount'),
    profit: pick('profit', 'netprofit', 'margin', 'totalprofit'),
    raw: row,
  };
}

// ════════════════════════════════════════════════════════════════════════════
// DASHBOARD AGGREGATE
// ════════════════════════════════════════════════════════════════════════════

export interface DashboardKpis {
  totalOrders: number;
  delivered: number;
  // null when the tenant has no invoice/revenue records (so the card can show "—"
  // instead of a misleading ₹0).
  todaysRevenue: number | null;
  monthlyProfit: number | null;
  tenantName?: string;
}

const ymd = (d: Date) =>
  `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;

/**
 * Pulls the four dashboard KPIs from live data for the given tenant:
 *  - Total Orders  ← month-to-date order summary `.total`
 *  - Delivered     ← month-to-date order summary `.delivered`
 *  - Today's Revenue ← invoice insight revenue (null if the tenant has none)
 *  - Monthly Profit  ← invoice insight profit (null if the tenant has none)
 */
export async function getDashboardKpis(tenantid: number = DEFAULT_TENANT_ID): Promise<DashboardKpis> {
  const today = new Date();
  const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);

  const [monthSummary, insight] = await Promise.all([
    getOrderSummary(tenantid, ymd(monthStart), ymd(today)),
    getInvoiceInsight(tenantid).catch(() => null),
  ]);

  return {
    totalOrders: monthSummary?.total ?? 0,
    delivered: monthSummary?.delivered ?? 0,
    todaysRevenue: insight ? insight.revenue : null,
    monthlyProfit: insight ? insight.profit : null,
    tenantName: monthSummary?.tenantname,
  };
}
