import { format } from 'date-fns';
import { UUID_REGEX } from '../constants.js';
import type { TimelessDateString } from '../types/index.js';

function parseIntRound(v: number) {
  return Math.trunc(v + Math.sign(v) / 2);
}

export function stringNumberRounded(number: string): number {
  return parseIntRound((parseFloat(number) + Number.EPSILON) * 100) / 100;
}

export function numberRounded(number: number): number {
  return parseIntRound((number + Number.EPSILON) * 100) / 100;
}

export function isTimelessDateString(date: string): date is TimelessDateString {
  const parts = date.split('-');
  if (parts.length !== 3) {
    return false;
  }
  const [year, month, day] = parts;
  //year
  const yearNum = Number(year);
  if (Number.isNaN(yearNum) || yearNum < 2000 || yearNum > 2049) {
    return false;
  }
  // month
  const monthNum = Number(month);
  if (Number.isNaN(monthNum) || monthNum < 1 || monthNum > 12) {
    return false;
  }
  // day
  const dayNum = Number(day);
  if (Number.isNaN(dayNum) || dayNum < 1 || dayNum > 31) {
    return false;
  }
  return true;
}

export function isUUID(raw: string) {
  return UUID_REGEX.test(raw);
}

function convertMonthNameToNumber(monthName: string): string | null {
  const lowerCasedName = monthName.toLocaleLowerCase();
  let month: string | null = null;
  switch (lowerCasedName) {
    case 'january':
    case 'jan':
      month = '01';
      break;
    case 'february':
    case 'feb':
      month = '02';
      break;
    case 'march':
    case 'mar':
      month = '03';
      break;
    case 'april':
    case 'apr':
      month = '04';
      break;
    case 'may':
      month = '05';
      break;
    case 'june':
    case 'jun':
      month = '06';
      break;
    case 'july':
    case 'jul':
      month = '07';
      break;
    case 'august':
    case 'aug':
      month = '08';
      break;
    case 'september':
    case 'sep':
      month = '09';
      break;
    case 'october':
    case 'oct':
      month = '10';
      break;
    case 'november':
    case 'nov':
      month = '11';
      break;
    case 'december':
    case 'dec':
      month = '12';
      break;
    default:
      break;
  }
  return month;
}

/**
 * @description
 * Extract month from description
 * @param rawDescription string - description to extract month from
 * @param eventDate Date - optional, if provided, will use it to determine year
 * @returns month in format yyyy-mm, else null
 */
export function getMonthFromDescription(rawDescription: string, eventDate?: Date): string[] | null {
  if (!rawDescription.length) {
    return null;
  }
  const description = rawDescription?.toLocaleLowerCase();
  // search for "yyyy-mm" in description
  const dateRegex = /\b(\d{4})-(\d{2})\b/;
  const matches = description.match(dateRegex);
  if (matches?.length) {
    const month = matches[0];
    return [month];
  }

  // search for "mm-yyyy" in description
  const dateRegex2 = /\b(\d{2})-(\d{4})\b/;
  const matches2 = description.match(dateRegex2);
  if (matches2?.length) {
    const month = matches2[0];
    const adjustedMonth = month.split('-').reverse().join('-');
    return [adjustedMonth];
  }

  // search for "mm/yyyy" in description
  const dateRegex3 = /\b(\d{2})\/(\d{4})\b/;
  const matches3 = description.match(dateRegex3);
  if (matches3?.length) {
    // case two month
    const dateRegex3double = /\b(\d{2})-(\d{2})\/(\d{4})\b/;
    const matches3double = description.match(dateRegex3double);
    if (matches3double?.length) {
      const [_dateString, monthA, monthB, year] = matches3double;
      return [`${year}-${monthA}`, `${year}-${monthB}`];
    }
    // case one month
    const month = matches3[0];
    const adjustedMonth = month.split('/').reverse().join('-');
    return [adjustedMonth];
  }

  // search for "mm/yy" in description
  const dateRegex4 = /\b(\d{2})\/(\d{2})\b/;
  const matches4 = description.match(dateRegex4);
  if (matches4?.length) {
    const month = matches4[0];
    const adjustedMonth = '20' + month.split('/').reverse().join('-');
    return [adjustedMonth];
  }

  // search for month name in description
  const dateRegex5 =
    /\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|(nov|dec)(?:ember)?)\b/g;

  const monthNames = description.match(dateRegex5);
  if (monthNames?.length) {
    const dateStrings = [];
    for (const monthName of monthNames) {
      const month = convertMonthNameToNumber(monthName);

      if (month) {
        // try to search for year in description
        const dateRegex5 =
          /\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|(nov|dec)(?:ember)?) (?:19[7-9]\d|2\d{3})\b/g;
        const matches5 = description.match(dateRegex5);
        if (matches5?.length) {
          const year = matches5[0].split(' ')[1];
          if (year) {
            const adjustedMonth = `${year}-${month}`;
            dateStrings.push(adjustedMonth);
            continue;
          }
        }

        if (eventDate) {
          let year = eventDate.getFullYear();

          // case date is in Jan/Feb and salary month is Nov/Dec, use date's prev year
          if (eventDate.getMonth() < 2 && month > '10') {
            year--;
          }
          const adjustedMonth = `${year}-${month}`;
          dateStrings.push(adjustedMonth);
        }
      }
    }
    if (dateStrings.length) {
      return dateStrings;
    }
  }
  return null;
}

export function dateToTimelessDateString(date: Date): TimelessDateString {
  return format(date, 'yyyy-MM-dd') as TimelessDateString;
}

export function optionalDateToTimelessDateString(date?: Date | null): TimelessDateString | null {
  if (!date) {
    return null;
  }
  return dateToTimelessDateString(date) as TimelessDateString;
}

// Convert to 32bit integer
export function hashStringToInt(text: string): number {
  let hash = 0;
  if (text.length === 0) return hash;
  for (let i = 0; i < text.length; i++) {
    const chr = text.charCodeAt(i);
    hash = (hash << 5) - hash + chr;
    hash |= 0;
  }
  return hash;
}

export function reassureOwnerIdExists<
  T extends {
    ownerId?: string | null | void;
  },
>(params: T, contextOwnerId: string): Omit<T, 'ownerId'> & { ownerId: string } {
  const ownerId = params.ownerId ?? contextOwnerId;
  if (!ownerId) {
    throw new Error('Owner ID is required but could not be determined from parameters or context');
  }
  return {
    ...params,
    ownerId,
  };
}
