import { createHash } from 'crypto';
import { DateTime } from 'luxon';

/** Luxon weekday: 1 = Monday … 7 = Sunday. */
export type LuxonWeekday = 1 | 2 | 3 | 4 | 5 | 6 | 7;

export type ChlorinationHabitationRow = { name: string; value: string };

export type ChlorinationReportDataBlock = {
  sNo: number;
  date: string;
  remark?: string;
  habitations: ChlorinationHabitationRow[];
};

function hashSeedToUint32(seed: string): number {
  const h = createHash('sha256').update(seed, 'utf8').digest();
  return h.readUInt32BE(0);
}

/** Deterministic weekday 1–7 from a stable seed (scheme + billing period). */
export function selectWeekdayForReport(seed: string): LuxonWeekday {
  const n = hashSeedToUint32(seed);
  return ((n % 7) + 1) as LuxonWeekday;
}

/** Rotate order starting at `start` (1–7), length 7 (for deterministic weekday retry). */
function weekdayProbeOrder(start: LuxonWeekday): LuxonWeekday[] {
  const order: LuxonWeekday[] = [];
  for (let i = 0; i < 7; i++) {
    const w = ((((start + i - 1) % 7) + 7) % 7) + 1;
    order.push(w as LuxonWeekday);
  }
  return order;
}

/** Inclusive wall-date range in `zone`; occurrences of `weekday`, earliest first. */
export function listWeekdayOccurrencesInInclusiveRange(
  from: DateTime,
  to: DateTime,
  weekday: LuxonWeekday,
  zone: string
): DateTime[] {
  let start = from.setZone(zone).startOf('day');
  let end = to.setZone(zone).startOf('day');
  if (!start.isValid || !end.isValid || start > end) {
    return [];
  }

  let cursor = start;
  while (cursor.weekday !== weekday && cursor <= end) {
    cursor = cursor.plus({ days: 1 });
  }
  if (cursor > end) {
    return [];
  }

  const out: DateTime[] = [];
  while (cursor <= end) {
    out.push(cursor);
    cursor = cursor.plus({ weeks: 1 });
  }
  return out;
}

/**
 * Picks a weekday that has at least one occurrence in range; if seed's weekday has none,
 * probes other weekdays deterministically until one fits.
 */
export function selectWeekdayWithOccurrenceInRange(seed: string, from: DateTime, to: DateTime, zone: string): LuxonWeekday {
  const first = selectWeekdayForReport(seed);
  for (const w of weekdayProbeOrder(first)) {
    if (listWeekdayOccurrencesInInclusiveRange(from, to, w, zone).length > 0) {
      return w;
    }
  }
  return first;
}

/**
 * Picks a **non-Sunday** weekday (Mon–Sat) that has at least one occurrence in the bill period.
 * Uses `rng` so each job run can get a different day; Sunday (7) is never chosen.
 */
export function selectRandomNonSundayWeekdayWithOccurrenceInRange(
  from: DateTime,
  to: DateTime,
  zone: string,
  rng: () => number = Math.random
): LuxonWeekday {
  const candidates: LuxonWeekday[] = [];
  for (let w = 1; w <= 6; w++) {
    if (listWeekdayOccurrencesInInclusiveRange(from, to, w as LuxonWeekday, zone).length > 0) {
      candidates.push(w as LuxonWeekday);
    }
  }
  if (candidates.length === 0) {
    return 1;
  }
  const idx = Math.floor(rng() * candidates.length);
  return candidates[idx]!;
}

const RESIDUAL_CHLORINE_PPM_VALUES = [0.2, 0.3, 0.4, 0.5] as const;

/**
 * Random residual chlorine from discrete tenths in [0.2, 0.5].
 */
export function generateResidualChlorinePpm(rng: () => number = Math.random): number {
  const idx = Math.floor(rng() * RESIDUAL_CHLORINE_PPM_VALUES.length);
  return RESIDUAL_CHLORINE_PPM_VALUES[Math.min(idx, RESIDUAL_CHLORINE_PPM_VALUES.length - 1)]!;
}

export function buildChlorinationReportData(params: {
  measurementDates: DateTime[];
  villageNames: string[];
  timezone: string;
  rng?: () => number;
}): ChlorinationReportDataBlock[] {
  const { measurementDates, villageNames, timezone, rng = Math.random } = params;
  const random = rng;
  const reportData: ChlorinationReportDataBlock[] = [];
  let sNo = 1;
  for (const dt of measurementDates) {
    const dateStr = dt.setZone(timezone).toFormat('dd/LL/yyyy');
    const habitations: ChlorinationHabitationRow[] = villageNames.map((name) => ({
      name,
      value: generateResidualChlorinePpm(random).toFixed(1),
    }));
    reportData.push({ sNo: sNo++, date: dateStr, habitations });
  }
  return reportData;
}
