import {
  ALERT_MAIL_GROUPS,
  isAlertMailGroup,
  type AlertMailGroup,
} from "@/services/alertes-mail.service";
import { dispatchAlertMailOnce } from "@/services/alertes-mail-dispatch.service";
import { sendAlertMailWithGraph } from "@/services/alertes-mail-graph.service";

type SchedulerState = {
  timer: ReturnType<typeof setInterval> | null;
  starting: boolean;
  runningGroups: Set<AlertMailGroup>;
  completedDateByGroup: Map<AlertMailGroup, string>;
  retryAfterByGroup: Map<AlertMailGroup, number>;
};

type ParisDateTime = {
  dateKey: string;
  weekday: string;
  hour: number;
  minute: number;
};

const schedulerGlobal = globalThis as typeof globalThis & {
  __rhConnectAlertMailScheduler?: SchedulerState;
};

function getSchedulerState() {
  schedulerGlobal.__rhConnectAlertMailScheduler ??= {
    timer: null,
    starting: false,
    runningGroups: new Set<AlertMailGroup>(),
    completedDateByGroup: new Map<AlertMailGroup, string>(),
    retryAfterByGroup: new Map<AlertMailGroup, number>(),
  };

  return schedulerGlobal.__rhConnectAlertMailScheduler;
}

function getParisDateTime(date = new Date()): ParisDateTime {
  const parts = new Intl.DateTimeFormat("en-GB", {
    timeZone: "Europe/Paris",
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    weekday: "short",
    hour: "2-digit",
    minute: "2-digit",
    hourCycle: "h23",
  }).formatToParts(date);
  const values = new Map(parts.map((part) => [part.type, part.value]));
  const year = values.get("year");
  const month = values.get("month");
  const day = values.get("day");
  const weekday = values.get("weekday");
  const hour = Number(values.get("hour"));
  const minute = Number(values.get("minute"));

  if (!year || !month || !day || !weekday || !Number.isInteger(hour) || !Number.isInteger(minute)) {
    throw new Error("ALERT_MAIL_UNABLE_TO_RESOLVE_PARIS_TIME");
  }

  return { dateKey: `${year}-${month}-${day}`, weekday, hour, minute };
}

function parseIntegerSetting(name: string, fallback: number, minimum: number, maximum: number) {
  const value = process.env[name]?.trim();
  if (!value) return fallback;

  const parsed = Number(value);
  if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) {
    throw new Error(`INVALID_${name}`);
  }
  return parsed;
}

function getConfiguredGroups() {
  const rawGroups = process.env.ALERT_MAIL_SCHEDULE_GROUPS?.trim();
  if (!rawGroups) return [...ALERT_MAIL_GROUPS];

  const groups = [...new Set(rawGroups.split(/[;,\s]+/).map((group) => group.trim().toLowerCase()).filter(Boolean))];
  const invalidGroups = groups.filter((group) => !isAlertMailGroup(group));

  if (invalidGroups.length > 0) {
    throw new Error(`ALERT_MAIL_INVALID_SCHEDULE_GROUPS:${invalidGroups.join(",")}`);
  }

  return groups as AlertMailGroup[];
}

function hasReachedSchedule(date: ParisDateTime, hour: number, minute: number) {
  return date.hour > hour || (date.hour === hour && date.minute >= minute);
}

export async function runAlertMailSchedulerTick(date = new Date()) {
  const mode = process.env.ALERT_MAIL_SCHEDULE_MODE?.trim().toLowerCase() ?? "disabled";
  if (mode === "disabled") return;
  if (mode !== "daily") throw new Error(`ALERT_MAIL_INVALID_SCHEDULE_MODE:${mode}`);

  const hour = parseIntegerSetting("ALERT_MAIL_SCHEDULE_HOUR", 7, 0, 23);
  const minute = parseIntegerSetting("ALERT_MAIL_SCHEDULE_MINUTE", 0, 0, 59);
  const retryMinutes = parseIntegerSetting("ALERT_MAIL_SCHEDULE_RETRY_MINUTES", 15, 1, 1_440);
  const parisDate = getParisDateTime(date);

  if (["Sat", "Sun"].includes(parisDate.weekday) || !hasReachedSchedule(parisDate, hour, minute)) return;

  const state = getSchedulerState();

  await Promise.all(getConfiguredGroups().map(async (group) => {
    if (state.runningGroups.has(group) || state.completedDateByGroup.get(group) === parisDate.dateKey || (state.retryAfterByGroup.get(group) ?? 0) > date.getTime()) return;

    state.runningGroups.add(group);
    try {
      const result = await dispatchAlertMailOnce({ group, send: sendAlertMailWithGraph });
      state.completedDateByGroup.set(group, parisDate.dateKey);
      state.retryAfterByGroup.delete(group);
      console.info("[alert-mail-scheduler] passage terminé", { date: parisDate.dateKey, group, ...result });
    } catch (error) {
      state.retryAfterByGroup.set(group, date.getTime() + retryMinutes * 60_000);
      console.error("[alert-mail-scheduler] échec du passage", { date: parisDate.dateKey, group, retryInMinutes: retryMinutes, error });
    } finally {
      state.runningGroups.delete(group);
    }
  }));
}

export function startAlertMailScheduler() {
  const mode = process.env.ALERT_MAIL_SCHEDULE_MODE?.trim().toLowerCase() ?? "disabled";

  if (mode === "disabled") {
    console.info("[alert-mail-scheduler] désactivé");
    return;
  }

  const state = getSchedulerState();
  if (state.timer || state.starting) return;
  state.starting = true;

  const tick = () => void runAlertMailSchedulerTick().catch((error) => {
    console.error("[alert-mail-scheduler] configuration invalide", error);
  });

  setTimeout(tick, 2_000).unref();
  state.timer = setInterval(tick, 60_000);
  state.timer.unref();
  state.starting = false;

  console.info("[alert-mail-scheduler] démarré", { mode, timezone: "Europe/Paris" });
}
