import { useCallback, useEffect } from 'react';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import useSettingsStore from '../store/settings';
import useLocalStorage from '../hooks/useLocalStorage';

dayjs.extend(duration);
dayjs.extend(relativeTime);
dayjs.extend(utc);
dayjs.extend(timezone);

export const convertToMs = (dateTime) => dayjs(dateTime).valueOf();

export const convertToSec = (dateTime) => dayjs(dateTime).unix();

export const initializeTime = (dateTime) => dayjs(dateTime);

export const startOfDay = (dateTime) => dayjs(dateTime).startOf('day');

export const isBefore = (date1, date2) => dayjs(date1).isBefore(date2);

export const isAfter = (date1, date2) => dayjs(date1).isAfter(date2);

export const isSame = (date1, date2, unit = 'day') =>
  dayjs(date1).isSame(date2, unit);

export const add = (dateTime, value, unit) => dayjs(dateTime).add(value, unit);

export const subtract = (dateTime, value, unit) =>
  dayjs(dateTime).subtract(value, unit);

export const diff = (date1, date2, unit = 'millisecond') =>
  dayjs(date1).diff(date2, unit);

export const format = (dateTime, formatStr) =>
  dayjs(dateTime).format(formatStr);

export const getNow = () => dayjs();

export const toFriendlyDuration = (dateTime, unit) =>
  dayjs.duration(dateTime, unit).humanize();

export const formatExactDuration = (seconds) => {
  if (seconds < 60) return `${seconds.toFixed(1)} seconds`;
  if (seconds < 3600) {
    const m = Math.floor(seconds / 60);
    const s = Math.floor(seconds % 60);
    return `${m} minute${m !== 1 ? 's' : ''}, ${s} second${s !== 1 ? 's' : ''}`;
  }
  if (seconds < 86400) {
    const h = Math.floor(seconds / 3600);
    const m = Math.floor((seconds % 3600) / 60);
    return `${h} hour${h !== 1 ? 's' : ''}, ${m} minute${m !== 1 ? 's' : ''}`;
  }
  const d = Math.floor(seconds / 86400);
  const h = Math.floor((seconds % 86400) / 3600);
  return `${d} day${d !== 1 ? 's' : ''}, ${h} hour${h !== 1 ? 's' : ''}`;
};

export const fromNow = (dateTime) => dayjs(dateTime).fromNow();

export const setTz = (dateTime, timeZone) => dayjs(dateTime).tz(timeZone);

export const setMonth = (dateTime, value) => dayjs(dateTime).month(value);

export const setYear = (dateTime, value) => dayjs(dateTime).year(value);

export const setDay = (dateTime, value) => dayjs(dateTime).date(value);

export const setHour = (dateTime, value) => dayjs(dateTime).hour(value);

export const setMinute = (dateTime, value) => dayjs(dateTime).minute(value);

export const setSecond = (dateTime, value) => dayjs(dateTime).second(value);

export const getMonth = (dateTime) => dayjs(dateTime).month();

export const getYear = (dateTime) => dayjs(dateTime).year();

export const getDay = (dateTime) => dayjs(dateTime).date();

export const getHour = (dateTime) => dayjs(dateTime).hour();

export const getMinute = (dateTime) => dayjs(dateTime).minute();

export const getSecond = (dateTime) => dayjs(dateTime).second();

export const getNowMs = () => Date.now();

export const roundToNearest = (dateTime, minutes) => {
  const current = initializeTime(dateTime);
  const minute = current.minute();
  const snappedMinute = Math.round(minute / minutes) * minutes;

  return snappedMinute === 60
    ? current.add(1, 'hour').minute(0)
    : current.minute(snappedMinute);
};

export const useUserTimeZone = () => {
  const settings = useSettingsStore((s) => s.settings);
  const [timeZone, setTimeZone] = useLocalStorage(
    'time-zone',
    dayjs.tz?.guess
      ? dayjs.tz.guess()
      : Intl.DateTimeFormat().resolvedOptions().timeZone
  );

  useEffect(() => {
    const tz = settings?.['system_settings']?.value?.time_zone;
    if (tz && tz !== timeZone) {
      setTimeZone(tz);
    }
  }, [settings, timeZone, setTimeZone]);

  return timeZone;
};

export const useTimeHelpers = () => {
  const timeZone = useUserTimeZone();

  const toUserTime = useCallback(
    (value) => {
      if (!value) return dayjs(null);
      try {
        return initializeTime(value).tz(timeZone);
      } catch (error) {
        return initializeTime(value);
      }
    },
    [timeZone]
  );

  const userNow = useCallback(() => getNow().tz(timeZone), [timeZone]);

  return { timeZone, toUserTime, userNow };
};

export const RECURRING_DAY_OPTIONS = [
  { value: 6, label: 'Sun' },
  { value: 0, label: 'Mon' },
  { value: 1, label: 'Tue' },
  { value: 2, label: 'Wed' },
  { value: 3, label: 'Thu' },
  { value: 4, label: 'Fri' },
  { value: 5, label: 'Sat' },
];

export const useDateTimeFormat = () => {
  const [timeFormatSetting] = useLocalStorage('time-format', '12h');
  const [dateFormatSetting] = useLocalStorage('date-format', 'mdy');
  // Use user preference for time format
  const timeFormat = timeFormatSetting === '12h' ? 'h:mma' : 'HH:mm';
  const dateFormat = dateFormatSetting === 'mdy' ? 'MMM D' : 'D MMM';

  // Full format strings for detailed date-time displays
  const fullDateFormat =
    dateFormatSetting === 'mdy' ? 'MM/DD/YYYY' : 'DD/MM/YYYY';
  const fullTimeFormat = timeFormatSetting === '12h' ? 'h:mm:ss A' : 'HH:mm:ss';
  const fullDateTimeFormat = `${fullDateFormat}, ${fullTimeFormat}`;

  return {
    timeFormat,
    dateFormat,
    fullDateFormat,
    fullTimeFormat,
    fullDateTimeFormat,
    // Also return raw settings for cases that need them
    timeFormatSetting,
    dateFormatSetting,
  };
};

export const toTimeString = (value) => {
  if (!value) return '00:00';
  if (typeof value === 'string') {
    const parsed = dayjs(value, ['HH:mm', 'HH:mm:ss', 'h:mm A'], true);
    if (parsed.isValid()) return parsed.format('HH:mm');
    return value;
  }
  const parsed = initializeTime(value);
  return parsed.isValid() ? parsed.format('HH:mm') : '00:00';
};

export const parseDate = (value) => {
  if (!value) return null;
  const parsed = dayjs(value, ['YYYY-MM-DD', dayjs.ISO_8601], true);
  return parsed.isValid() ? parsed.toDate() : null;
};

const TIMEZONE_FALLBACKS = [
  'UTC',
  'America/New_York',
  'America/Chicago',
  'America/Denver',
  'America/Los_Angeles',
  'America/Phoenix',
  'America/Anchorage',
  'Pacific/Honolulu',
  'Europe/London',
  'Europe/Paris',
  'Europe/Berlin',
  'Europe/Madrid',
  'Europe/Warsaw',
  'Europe/Moscow',
  'Asia/Dubai',
  'Asia/Kolkata',
  'Asia/Shanghai',
  'Asia/Tokyo',
  'Asia/Seoul',
  'Australia/Sydney',
];

const getSupportedTimeZones = () => {
  try {
    if (typeof Intl.supportedValuesOf === 'function') {
      return Intl.supportedValuesOf('timeZone');
    }
  } catch (error) {
    console.warn('Unable to enumerate supported time zones:', error);
  }
  return TIMEZONE_FALLBACKS;
};

const getTimeZoneOffsetMinutes = (date, timeZone) => {
  try {
    const dtf = new Intl.DateTimeFormat('en-US', {
      timeZone,
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hourCycle: 'h23',
    });
    const parts = dtf.formatToParts(date).reduce((acc, part) => {
      if (part.type !== 'literal') acc[part.type] = part.value;
      return acc;
    }, {});
    const asUTC = Date.UTC(
      Number(parts.year),
      Number(parts.month) - 1,
      Number(parts.day),
      Number(parts.hour),
      Number(parts.minute),
      Number(parts.second)
    );
    return (asUTC - date.getTime()) / 60000;
  } catch (error) {
    console.warn(`Failed to compute offset for ${timeZone}:`, error);
    return 0;
  }
};

const formatOffset = (minutes) => {
  const rounded = Math.round(minutes);
  const sign = rounded < 0 ? '-' : '+';
  const absolute = Math.abs(rounded);
  const hours = String(Math.floor(absolute / 60)).padStart(2, '0');
  const mins = String(absolute % 60).padStart(2, '0');
  return `UTC${sign}${hours}:${mins}`;
};

export const buildTimeZoneOptions = (preferredZone) => {
  const zones = getSupportedTimeZones();
  const referenceYear = new Date().getUTCFullYear();
  const janDate = new Date(Date.UTC(referenceYear, 0, 1, 12, 0, 0));
  const julDate = new Date(Date.UTC(referenceYear, 6, 1, 12, 0, 0));

  const options = zones
    .map((zone) => {
      const janOffset = getTimeZoneOffsetMinutes(janDate, zone);
      const julOffset = getTimeZoneOffsetMinutes(julDate, zone);
      const currentOffset = getTimeZoneOffsetMinutes(new Date(), zone);
      const minOffset = Math.min(janOffset, julOffset);
      const maxOffset = Math.max(janOffset, julOffset);
      const usesDst = minOffset !== maxOffset;
      const labelParts = [`now ${formatOffset(currentOffset)}`];
      if (usesDst) {
        labelParts.push(
          `DST range ${formatOffset(minOffset)} to ${formatOffset(maxOffset)}`
        );
      }
      return {
        value: zone,
        label: `${zone} (${labelParts.join(' | ')})`,
        numericOffset: minOffset,
      };
    })
    .sort((a, b) => {
      if (a.numericOffset !== b.numericOffset) {
        return a.numericOffset - b.numericOffset;
      }
      return a.value.localeCompare(b.value);
    });
  if (
    preferredZone &&
    !options.some((option) => option.value === preferredZone)
  ) {
    const currentOffset = getTimeZoneOffsetMinutes(new Date(), preferredZone);
    options.push({
      value: preferredZone,
      label: `${preferredZone} (now ${formatOffset(currentOffset)})`,
      numericOffset: currentOffset,
    });
    options.sort((a, b) => {
      if (a.numericOffset !== b.numericOffset) {
        return a.numericOffset - b.numericOffset;
      }
      return a.value.localeCompare(b.value);
    });
  }
  return options;
};

export const getDefaultTimeZone = () => {
  try {
    return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
  } catch (error) {
    return 'UTC';
  }
};

export const MONTH_NAMES = [
  'january',
  'february',
  'march',
  'april',
  'may',
  'june',
  'july',
  'august',
  'september',
  'october',
  'november',
  'december',
];

export const MONTH_ABBR = [
  'jan',
  'feb',
  'mar',
  'apr',
  'may',
  'jun',
  'jul',
  'aug',
  'sep',
  'oct',
  'nov',
  'dec',
];
