import {QnaMessage, QnaMessageType, QnaMessageFactory} from './qnaMessageFactory';
import {CuePoint, ModeratorSettings} from './types';

const NewMessageTimeDelay = 5000;

export class Utils {
  public static ONE_DAY_IN_MS: number = 1000 * 60 * 60 * 24;

  public static getDisplayDate(date: Date | null, dateFormat: string) {
    if (!date) {
      return;
    }

    let dateString = Utils._convertPhpDateFormattingToJsDateFormatting(dateFormat);

    const d = date.getDate();
    const dd = d < 10 ? `0${d}` : d;

    const m = date.getMonth() + 1;
    const mm = m < 10 ? `0${m}` : m;

    const yyyy = date.getFullYear();

    if (dateString.match(/do/) != null) {
      let value;

      if (d === 1 || d === 11 || d === 21 || d === 31) value = `${d}st`;
      else if (d === 2 || d === 12 || d === 22) value = `${d}nd`;
      else if (d === 3 || d === 13 || d === 23) value = `${d}rd`;
      else value = `${d}th`;

      dateString = dateString.replace(/do/g, value);
    } else if (dateString.match(/dd/)) {
      dateString = dateString.replace(/dd/, dd.toString());
    } else if (dateString.match(/d/)) {
      dateString = dateString.replace(/d/, d.toString());
    }

    if (dateString.match(/mmmm/) != null) {
      const month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
      dateString = dateString.replace(/mmmm/, month[m - 1]);
    } else if (dateString.match(/mm/)) {
      dateString = dateString.replace(/mm/, mm.toString());
    } else if (dateString.match(/m/)) {
      dateString = dateString.replace(/m/, m.toString());
    }

    dateString = dateString.replace(/yyyy/, yyyy.toString());

    return dateString;
  }

  private static _convertPhpDateFormattingToJsDateFormatting(dateFormat: string): string {
    if (dateFormat.indexOf('j') === -1) {
      return dateFormat;
    }

    // the list was taken from media-space and approved in `KMS-20129`
    switch (dateFormat) {
      case 'j.n.Y':
        dateFormat = 'd.m.yyyy';
        break;
      case 'j/n/Y':
        dateFormat = 'd/m/yyyy';
        break;
      case 'j F, Y':
        dateFormat = 'd mmmm, yyyy';
        break;
      case 'm/j/Y':
        dateFormat = 'mm/d/yyyy';
        break;
      case 'Y-n-j':
        dateFormat = 'yyyy-m-d';
        break;
      case 'F jS, Y':
        dateFormat = 'mmmm do, yyyy';
        break;
      default:
        dateFormat = 'dd/mm/yyyy';
    }

    return dateFormat;
  }

  public static getDisplayTime(date: Date | null) {
    if (!date) {
      return;
    }

    let hours: number = date.getHours();
    const ampm = hours >= 12 ? 'PM' : 'AM';
    hours = hours % 12;
    hours = hours ? hours : 12; // the hour '0' should be '12'
    let minutes: string | number = date.getMinutes();
    minutes = minutes < 10 ? '0' + minutes : minutes;

    return `${hours}:${minutes} ${ampm}`;
  }

  public static isDateOlderThan24Hours(date: Date | null): boolean {
    if (!date) {
      return false;
    }

    return Date.now() - date.valueOf() >= Utils.ONE_DAY_IN_MS;
  }

  public static getEnumByEnumValue(myEnum: any, enumValue: string) {
    let keys = Object.keys(myEnum).filter(x => myEnum[x] == enumValue);
    return keys.length > 0 ? myEnum[keys[0]] : null;
  }

  public static getValueFromXml(xmlDoc: Document, key: string): string | null {
    let node: Element = xmlDoc.getElementsByTagName(key)[0];
    if (!node || !node.childNodes || !node.childNodes[0]) {
      return null;
    }
    return node.childNodes[0].nodeValue;
  }

  public static createXmlFromObject(obj: Record<string, any>): string {
    let xml = '<metadata>';
    for (let propertyName in obj) {
      xml += `<${propertyName}>${obj[propertyName]}</${propertyName}>`;
    }
    xml += '</metadata>';
    return xml;
  }

  public static findIndex<T>(arr: Array<T>, comparator: (item: T) => boolean): number {
    let index = 0;
    while (index < arr.length) {
      if (comparator(arr[index])) {
        return index;
      }
      index++;
    }
    return -1;
  }

  public static threadTimeCompare(qnaMessage: QnaMessage): number {
    if (qnaMessage.type === QnaMessageType.Announcement || qnaMessage.type === QnaMessageType.AnswerOnAir) {
      return qnaMessage.createdAt.valueOf();
    }

    let q_time, a_time;

    if (qnaMessage.type === QnaMessageType.Answer) {
      a_time = qnaMessage.createdAt.valueOf();
    }

    if (qnaMessage.type === QnaMessageType.Question) {
      q_time = qnaMessage.createdAt.valueOf();
    }

    for (let i = 0; i < qnaMessage.replies.length; ++i) {
      let reply: QnaMessage = qnaMessage.replies[i];
      if (reply.type === QnaMessageType.Announcement) {
        if (!a_time) a_time = reply.createdAt.valueOf();
        else if (reply.createdAt.valueOf() > a_time) a_time = reply.createdAt.valueOf();
      }
    }

    if (!a_time && !q_time) {
      // todo log("both a_time and q_time are undefined - data error");
      return 0;
    }

    if (!a_time) {
      return q_time || 0;
    }

    if (!q_time) {
      return a_time || 0;
    }

    return Math.max(a_time, q_time);
  }

  public static isMessageInTimeFrame(qnaMessage: QnaMessage) {
    return qnaMessage.createdAt.getTime() >= new Date().getTime() - NewMessageTimeDelay;
  }

  public static wordBoundaryTrim = (text: string, maxLength: number): string => {
    const subString = text.substring(0, maxLength);
    return subString.substr(0, subString.lastIndexOf(' '));
  };

  public static getXmlFromCue = (cue: CuePoint): string[] => {
    return cue.metadata?.relatedObjects?.QandA_ResponseProfile?.objects?.map((object: any) => object?.xml) || [];
  };

  public static createQnaMessagesArray(pushResponse: any[]): QnaMessage[] {
    return pushResponse.reduce((qnaMessages: QnaMessage[], item: any) => {
      if (item.objectType === 'KalturaAnnotation') {
        const kalturaAnnotation = {
          ...item,
          createdAt: new Date(item.createdAt * 1000),
          updatedAt: new Date(item.updatedAt * 1000)
        };
        const metadataXml: string = Utils.getMetadata(kalturaAnnotation);
        let qnaMessage = QnaMessageFactory.create(kalturaAnnotation, metadataXml);
        if (qnaMessage) {
          qnaMessages.push(qnaMessage);
        }
      }
      return qnaMessages;
    }, []);
  }

  public static getLastSettingsObject(pushResponse: any[]): ModeratorSettings | null {
    const settings = Utils.createQnaSettingsObjects(pushResponse);
    settings.sort((a: ModeratorSettings, b: ModeratorSettings) => {
      return a.createdAt.valueOf() - b.createdAt.valueOf();
    });
    return settings.length >= 1 ? settings[0] : null;
  }

  private static createQnaSettingsObjects(pushResponse: any[]): ModeratorSettings[] {
    return pushResponse.reduce((settings: ModeratorSettings[], item: any) => {
      if (item.objectType === 'KalturaCodeCuePoint') {
        const settingsObject = Utils.createSettingsObject(item);
        if (settingsObject) {
          settings.push(settingsObject);
        }
      }
      return settings;
    }, []);
  }

  private static getMetadata(cuePoint: any): string {
    if (!cuePoint.relatedObjects || !cuePoint.relatedObjects['QandA_ResponseProfile']) {
      throw new Error('Missing QandA_ResponseProfile at cuePoint.relatedObjects');
    }
    const relatedObject = cuePoint.relatedObjects['QandA_ResponseProfile'];
    if (!relatedObject.hasOwnProperty('objects') || relatedObject.objects.length === 0) {
      throw new Error('There are no metadata objects xml at KalturaMetadataListResponse');
    }
    const metadata = relatedObject.objects[0];
    if (!('DOMParser' in window)) {
      throw new Error('DOMParser is not exits at window, cant parse the metadata xml');
    }
    return metadata.xml;
  }

  private static createSettingsObject(settingsCuepoint: any): ModeratorSettings | null {
    try {
      if (!settingsCuepoint || !settingsCuepoint.createdAt || !settingsCuepoint.partnerData) {
        return null;
      }
      const settingsObject = settingsCuepoint.partnerData;
      if (
        !settingsObject['qnaSettings'] ||
        !settingsObject['qnaSettings'].hasOwnProperty('qnaEnabled') ||
        !settingsObject['qnaSettings'].hasOwnProperty('announcementOnly')
      )
        return null;

      return {
        createdAt: settingsCuepoint.createdAt,
        qnaEnabled: settingsObject['qnaSettings']['qnaEnabled'],
        announcementOnly: settingsObject['qnaSettings']['announcementOnly']
      };
    } catch (e) {
      return null;
    }
  }

  public static prepareCuePoints = (cues: CuePoint[], filter: (metadata: any) => boolean): CuePoint[] => {
    return cues.reduce((acc, cue: CuePoint) => {
      if (cue?.type !== 'cuepoint') {
        return acc;
      }
      const {metadata} = cue;
      if (filter(metadata)) {
        return [
          ...acc,
          {
            id: cue.id,
            ...metadata
          }
        ];
      }
      return acc;
    }, [] as CuePoint[]);
  };
}
