const DEFAULT_TIMEZONE = 'Asia/Shanghai';

function pad2(value) {
  return String(value).padStart(2, '0');
}

export function normalizeTimezone(timezone) {
  return String(timezone || process.env.H5_DEFAULT_TIMEZONE || DEFAULT_TIMEZONE).trim() || DEFAULT_TIMEZONE;
}

export function getLocalParts(epochMs = Date.now(), timezone = DEFAULT_TIMEZONE) {
  const formatter = new Intl.DateTimeFormat('en-CA', {
    timeZone: normalizeTimezone(timezone),
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false,
  });
  const parts = Object.fromEntries(
    formatter.formatToParts(new Date(epochMs)).map((part) => [part.type, part.value]),
  );
  return {
    year: Number(parts.year),
    month: Number(parts.month),
    day: Number(parts.day),
    hour: Number(parts.hour === '24' ? 0 : parts.hour),
    minute: Number(parts.minute),
    second: Number(parts.second),
  };
}

export function localDateKey(epochMs = Date.now(), timezone = DEFAULT_TIMEZONE) {
  const parts = getLocalParts(epochMs, timezone);
  return `${parts.year}-${pad2(parts.month)}-${pad2(parts.day)}`;
}

export function localDateLabel(epochMs = Date.now(), timezone = DEFAULT_TIMEZONE) {
  const parts = getLocalParts(epochMs, timezone);
  return `${parts.month}月${parts.day}日`;
}

export function startOfLocalDay(epochMs = Date.now(), timezone = DEFAULT_TIMEZONE) {
  const parts = getLocalParts(epochMs, timezone);
  return zonedTimeToEpochMs(
    {
      year: parts.year,
      month: parts.month,
      day: parts.day,
      hour: 0,
      minute: 0,
      second: 0,
    },
    timezone,
  );
}

export function addLocalDays(epochMs, days, timezone = DEFAULT_TIMEZONE) {
  const parts = getLocalParts(epochMs, timezone);
  const utc = Date.UTC(parts.year, parts.month - 1, parts.day + Number(days || 0), parts.hour, parts.minute, parts.second);
  const shifted = new Date(utc);
  return zonedTimeToEpochMs(
    {
      year: shifted.getUTCFullYear(),
      month: shifted.getUTCMonth() + 1,
      day: shifted.getUTCDate(),
      hour: shifted.getUTCHours(),
      minute: shifted.getUTCMinutes(),
      second: shifted.getUTCSeconds(),
    },
    timezone,
  );
}

export function zonedTimeToEpochMs(parts, timezone = DEFAULT_TIMEZONE) {
  const tz = normalizeTimezone(timezone);
  let guess = Date.UTC(
    Number(parts.year),
    Number(parts.month) - 1,
    Number(parts.day),
    Number(parts.hour ?? 0),
    Number(parts.minute ?? 0),
    Number(parts.second ?? 0),
    0,
  );

  for (let i = 0; i < 3; i += 1) {
    const actual = getLocalParts(guess, tz);
    const actualAsUtc = Date.UTC(
      actual.year,
      actual.month - 1,
      actual.day,
      actual.hour,
      actual.minute,
      actual.second,
      0,
    );
    const desiredAsUtc = Date.UTC(
      Number(parts.year),
      Number(parts.month) - 1,
      Number(parts.day),
      Number(parts.hour ?? 0),
      Number(parts.minute ?? 0),
      Number(parts.second ?? 0),
      0,
    );
    const delta = actualAsUtc - desiredAsUtc;
    if (delta === 0) break;
    guess -= delta;
  }
  return guess;
}

export function nextDailyRunAt({ hour, minute = 0, timezone = DEFAULT_TIMEZONE, now = Date.now() }) {
  const tz = normalizeTimezone(timezone);
  const todayStart = startOfLocalDay(now, tz);
  const todayParts = getLocalParts(todayStart, tz);
  let runAt = zonedTimeToEpochMs(
    {
      year: todayParts.year,
      month: todayParts.month,
      day: todayParts.day,
      hour: Number(hour),
      minute: Number(minute),
      second: 0,
    },
    tz,
  );
  if (runAt <= now) {
    const tomorrowStart = addLocalDays(todayStart, 1, tz);
    const tomorrowParts = getLocalParts(tomorrowStart, tz);
    runAt = zonedTimeToEpochMs(
      {
        year: tomorrowParts.year,
        month: tomorrowParts.month,
        day: tomorrowParts.day,
        hour: Number(hour),
        minute: Number(minute),
        second: 0,
      },
      tz,
    );
  }
  return runAt;
}

export function formatLocalTime(epochMs, timezone = DEFAULT_TIMEZONE) {
  const parts = getLocalParts(epochMs, timezone);
  return `${pad2(parts.hour)}:${pad2(parts.minute)}`;
}
