import {
  addMilliseconds,
  differenceInCalendarDays,
  subMilliseconds,
} from 'date-fns';
import { formatInTimeZone, fromZonedTime, toZonedTime } from 'date-fns-tz';
import { getHours } from 'date-fns/getHours';
import { getMinutes } from 'date-fns/getMinutes';
import { isValid } from 'date-fns/isValid';
import { parseISO } from 'date-fns/parseISO';
import { uuidv4 } from 'zod';
import { DEFAULT_ORG_TIMEZONE } from '../common/timezone/timezone.constants';
import { PurchaseType } from '../config';

export const DHAKA_OFFSET = 6 * 60 * 60 * 1000;
/** @deprecated Use DEFAULT_ORG_TIMEZONE */
export const TIME_ZONE = DEFAULT_ORG_TIMEZONE;

export const attendancePresentStatuses = [
  'present',
  'remote',
  'half_day',
  'late',
  'on_break',
  'check_in',
] as const;

/** Present statuses for past (non-today) attendance records in month summary */
export const attendancePastPresentStatuses = [
  'present',
  'remote',
  'half_day',
  'late',
] as const;

export const presentExceptionStatuses = [
  'check_in',
  'on_break',
  'late',
  'remote',
  'half_day',
] as const;

/**
 * Converts minutes into human readable format
 * Example: 74 → "1 hour 14 minutes"
 */
export const formatBreakDuration = (minutes: number) => {
  if (!minutes || minutes <= 0) {
    return '0 minutes';
  }
  const hours = Math.floor(minutes / 60);
  const mins = Math.floor(minutes % 60);
  let result = '';
  if (hours > 0) {
    result += `${hours} hour${hours !== 1 ? 's' : ''}`;
  }
  if (mins > 0) {
    if (result.length > 0) result += ' ';
    result += `${mins} minute${mins !== 1 ? 's' : ''}`;
  }
  if (result.length === 0) {
    return '0 minutes';
  }
  return result;
};

export const getUnixTime = (
  type: string = 's',
  extraTime: number = 0,
): number => {
  const time = Date.now();
  const extraMilliseconds = extraTime * 60 * 1000;
  const convertedTime =
    type === 'ms'
      ? Math.floor((time + extraMilliseconds) / 1000)
      : time + extraMilliseconds;
  return convertedTime;
};

export const getOtp = (digits: number = 6): string => {
  // return otp  by marge number, alphabate string randomly
  const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  let otp = '';
  for (let i = 0; i < digits; i++) {
    otp += chars[Math.floor(Math.random() * chars.length)];
  }
  return otp; // Example: 'A1B2C3'
};

export const filterUndefined = (obj: Record<string, any>) => {
  return Object.fromEntries(
    Object.entries(obj).filter(
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      ([_, value]) => value !== undefined && value !== null && value !== '',
    ),
  );
};

export const isEmptyObj = (obj: Record<string, any>) => {
  return Object.entries(obj).length === 0;
};

export const getPurchaseType = (
  amount: number = 0,
  totalAmount: number = 0,
) => {
  if (amount === 0) {
    return PurchaseType.fullPay;
  } else if (amount > 0) {
    return PurchaseType.partial;
  } else if (amount > totalAmount) {
    return PurchaseType.extraPay;
  } else {
    return PurchaseType.duePay;
  }
};

export const uniqueUserid = (name?: string): string => {
  if (!name) return uuidv4() as unknown as string;
  const namePart = name
    .split(' ')
    .map((part) => part.slice(0, 2).toUpperCase())
    .join('_');
  const timestampPart = Date.now().toString().slice(-3);
  const randomPart = Math.random().toString(36).slice(2, 4).toUpperCase();
  return `${namePart}${timestampPart}${randomPart}`; // HR012AB or HR_HA012AB
};

export function getStartEndOfDay(offsetHours: number) {
  const now = new Date();
  const utc = now.getTime() + now.getTimezoneOffset() * 60000;
  const local = new Date(utc + offsetHours * 3600000);

  const start = new Date(local);
  start.setHours(0, 0, 0, 0);

  const end = new Date(local);
  end.setHours(23, 59, 59, 999);

  return {
    start: new Date(start.getTime() - offsetHours * 3600000),
    end: new Date(end.getTime() - offsetHours * 3600000),
  };
}

export function getStartEndOfDate(date: string, offsetHours: number) {
  // Robustly compute UTC start/end instants for the given local calendar date
  // represented by `date` in the timezone with `offsetHours` (e.g. +6).
  // Accepts either an ISO timestamp or a date-only string; if `date` is
  // falsy the current date is used.
  const inputDate = date ? new Date(date) : new Date();

  // Shift the instant by offsetHours to obtain the calendar date in that
  // timezone, then read UTC year/month/day from the shifted instant.
  const shifted = new Date(inputDate.getTime() + offsetHours * 3600000);
  const year = shifted.getUTCFullYear();
  const month = shifted.getUTCMonth();
  const day = shifted.getUTCDate();

  // Compute the UTC timestamp that corresponds to local midnight for that
  // calendar day: UTC = Date.UTC(year, month, day) - offsetHours
  const startUtcMillis = Date.UTC(year, month, day) - offsetHours * 3600000;
  const start = new Date(startUtcMillis);
  const end = new Date(startUtcMillis + 24 * 3600000 - 1);

  return { start, end };
}

export const getMonthUtcRange = (
  input?: Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const date =
    input instanceof Date && !isNaN(input.getTime()) ? input : new Date();

  const year = formatInTimeZone(date, timeZone, 'yyyy');
  const month = formatInTimeZone(date, timeZone, 'MM');

  const startBd = `${year}-${month}-01 00:00:00`;

  const nextMonthDate = new Date(Number(year), Number(month) - 1 + 1, 1);
  const nextYear = formatInTimeZone(nextMonthDate, timeZone, 'yyyy');
  const nextMonth = formatInTimeZone(nextMonthDate, timeZone, 'MM');

  const nextMonthStartBd = `${nextYear}-${nextMonth}-01 00:00:00`;

  const startUtc = fromZonedTime(startBd, timeZone);
  const endUtc = new Date(
    fromZonedTime(nextMonthStartBd, timeZone).getTime() - 1,
  );

  return { startUtc, endUtc };
};

export const getUtcMonthStartEnd = (
  year: number,
  month: number,
  expandedCount = 0,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const localDate = `${year}-${String(month).padStart(2, '0')}-01 00:00:00`;

  const nextMonth = month === 12 ? 1 : month + 1;
  const nextYear = month === 12 ? year + 1 : year;

  const nextMonthStart = `${nextYear}-${String(nextMonth).padStart(
    2,
    '0',
  )}-01 00:00:00`;

  const monthStartUtc = fromZonedTime(localDate, timeZone);
  const nextMonthStartUtc = fromZonedTime(nextMonthStart, timeZone);

  const endUtc = new Date(nextMonthStartUtc.getTime() - 1);

  return {
    startUtc:
      expandedCount > 0
        ? subMilliseconds(monthStartUtc, expandedCount)
        : monthStartUtc,

    endUtc: expandedCount > 0 ? addMilliseconds(endUtc, expandedCount) : endUtc,
  };
};

export const getUtcMonthStartEndFromDate = (
  date?: Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const normalizedDate =
    date instanceof Date && !Number.isNaN(date.getTime()) ? date : new Date();
  const year = Number(formatInTimeZone(normalizedDate, timeZone, 'yyyy'));
  const month = Number(formatInTimeZone(normalizedDate, timeZone, 'M'));
  return getUtcMonthStartEnd(year, month, 0, timeZone);
};

export const getUtcStartEndOfDate = (
  input: string | Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  try {
    let date: Date;

    if (typeof input === 'string') {
      // If input is already "yyyy-MM-dd"
      if (/^\d{4}-\d{2}-\d{2}$/.test(input)) {
        date = fromZonedTime(`${input} 00:00:00`, timeZone);
      } else {
        date = new Date(input);
      }
    } else {
      date = input;
    }

    if (isNaN(date.getTime())) {
      date = new Date();
    }

    const localDateStr = formatInTimeZone(date, timeZone, 'yyyy-MM-dd');

    const startBd = `${localDateStr} 00:00:00`;
    const endBd = `${localDateStr} 23:59:59.999`;

    return {
      startUtc: fromZonedTime(startBd, timeZone),
      endUtc: fromZonedTime(endBd, timeZone),
    };
  } catch (error) {
    console.error('Error in getUtcStartEndOfDate:', error);
    const fallbackDate = new Date();
    const localDateStr = formatInTimeZone(fallbackDate, timeZone, 'yyyy-MM-dd');

    return {
      startUtc: fromZonedTime(`${localDateStr} 00:00:00`, timeZone),
      endUtc: fromZonedTime(`${localDateStr} 23:59:59.999`, timeZone),
    };
  }
};

export const getUtcDateRange = (
  start: string | Date,
  end: string | Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const startDate = toDate(start) ?? new Date();
  const endDate = toDate(end) ?? new Date();

  const startBd = formatInTimeZone(startDate, timeZone, 'yyyy-MM-dd');
  const endBd = formatInTimeZone(endDate, timeZone, 'yyyy-MM-dd');

  return {
    startUtc: fromZonedTime(`${startBd} 00:00:00`, timeZone),
    endUtc: fromZonedTime(`${endBd} 23:59:59`, timeZone),
  };
};

export const getUtcWeekStartEndFromDate = (
  date?: Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const normalizedDate =
    date instanceof Date && !Number.isNaN(date.getTime()) ? date : new Date();

  const localDateStr = formatInTimeZone(normalizedDate, timeZone, 'yyyy-MM-dd');

  const localDate = parseISO(localDateStr);

  // JS week:
  // Sunday = 0
  // Monday = 1
  // ...
  // Saturday = 6
  const day = localDate.getDay();

  // Your week starts on Saturday
  // If today is Saturday => diff = 0
  // Sunday => 1
  // Monday => 2
  const diffToSaturday = (day + 1) % 7;

  const weekStart = new Date(localDate);
  weekStart.setDate(localDate.getDate() - diffToSaturday);
  weekStart.setHours(0, 0, 0, 0);

  const weekEnd = new Date(weekStart);
  weekEnd.setDate(weekStart.getDate() + 7);

  const startUtc = fromZonedTime(weekStart, timeZone);
  const endUtc = new Date(fromZonedTime(weekEnd, timeZone).getTime() - 1);

  return {
    startUtc,
    endUtc,
  };
};

export const roundHours = (hours: number) => {
  return Number(hours.toFixed(2));
};

export const toDate = (value: unknown): Date | null => {
  if (!value) return null;
  if (value instanceof Date) return value;
  if (typeof value === 'string') {
    const parsed = parseISO(value);
    if (isValid(parsed)) return parsed;
    const alt = new Date(value);
    return isValid(alt) ? alt : null;
  }
  return null;
};

export const getMinutesFromDate = (
  value: Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  const v = toDate(value);
  const zonedDate = toZonedTime(v, timeZone);
  return getHours(zonedDate) * 60 + getMinutes(zonedDate);
};

export function getDateDuration(start: Date, end: Date): number {
  if (!start || !end) return 0;

  const days = differenceInCalendarDays(new Date(start), new Date(end)) + 1;

  return Math.max(0, days);
}

export const getUserTimzoneDate = (
  date: Date,
  timeZone: string = DEFAULT_ORG_TIMEZONE,
) => {
  return formatInTimeZone(date, timeZone, 'dd MMM yyyy');
};
