import {
  getRootStore,
  Model,
  model,
  modelAction,
  ModelAutoTypeCheckingMode,
  setGlobalConfig,
  tProp,
  types,
} from 'mobx-keystone';
import CryptoJS from 'crypto-js';
import { addDays, addHours, addMinutes, endOfDay, getISODay } from 'date-fns';
import { toJS } from 'mobx';
import type { RootStore } from '../root-store';

setGlobalConfig({
  modelAutoTypeChecking: ModelAutoTypeCheckingMode.AlwaysOn,
});

enum DayOfWeekMap {
  'Mon' = 1,
  'Tue' = 2,
  'Wed' = 3,
  'Thur' = 4,
  'Fri' = 5,
  'Sat' = 6,
  'Sun' = 7,
}

function getRandomInt(min: number, max: number) {
  const mini = Math.ceil(min);
  const maxi = Math.floor(max);
  return Math.floor(Math.random() * (maxi - mini) + mini); // The maximum is exclusive and the minimum is inclusive
}
function getClosestDayOfThisWeek(dayOfWeek: number, fromDate = new Date()) {
  const offsetDays = 0 - (getISODay(fromDate) - dayOfWeek);
  return addDays(fromDate, offsetDays);
}
@model('keystone/Application/Ticket')
export default class Ticket extends Model({
  title: tProp(types.string),
  subtitle: tProp(types.string),
  text: tProp(types.string),
  credits: tProp(types.number),
  price: tProp(types.number),
  id: tProp(types.number, () => getRandomInt(1000000000, 4294967295)),
  idModel: tProp(types.string),
  idTemplate: tProp(types.string),
  startTimeConf: tProp(types.string),
  endTimeConf: tProp(types.string),
  startTime: tProp(types.string, '0'),
  endTime: tProp(types.string, '0'),
  startTimeTemp: tProp(types.number), // MOBX MIGRATE
  endTimeTemp: tProp(types.number), // MOBX MIGRATE
  endTimeValidity: tProp(types.number), // MOBX MIGRATE
  number: tProp(types.number, () => Math.random()),
  device: tProp(types.number, () => Math.random()),
  rd: tProp(types.number, () => Math.random()),
  check: tProp(types.string, ''),
  color: tProp(types.string, 'white'),
  qrData: tProp(types.string, ''),
  startType: tProp(types.number, 0),
  endType: tProp(types.number, 1),
}) {
  onAttachedToRootStore = () => {
    const rootStore = getRootStore<RootStore>(this);
    let user;

    console.log('_defaultStart', rootStore?.ticketList.defaultStart.toString());
    console.log('startTimeTemp', this.startTimeTemp);

    switch (this.startTimeConf) {
      case 'Time of printing':
        this.startTime = new Date().getTime().toString();
        break;
      case 'Date & Time picker':
        this.startTime = this.startTimeTemp.toString();
        break;
      case 'Next week Monday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Mon);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }

      case 'Next week Tuesday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Tue);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'Next week Wednesday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Wed);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'Next week Thursday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Thur);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'Next week Friday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Fri);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'Next week Saturday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Sat);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'Next week Sunday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Sun);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'This week Monday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Mon);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }

      case 'This week Tuesday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Tue);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'This week Wednesday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Wed);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'This week Thursday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Thur);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'This week Friday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Fri);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'This week Saturday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Sat);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'This week Sunday': {
        const h = new Date(this.startTimeTemp).getHours();
        const m = new Date(this.startTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Sun);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.startTime = date.getTime().toString();
        break;
      }
      case 'Start of day':
        // keep empty, time is set add TicketModelList
        break;
      default:
        this.startTime = new Date().getTime().toString();
    }

    switch (this.endTimeConf) {
      case 'Until midnight':
        if (this.startTimeConf === 'Date & Time picker') {
          this.endTime = endOfDay(this.startTimeTemp).getTime().toString();
        } else {
          this.endTime = endOfDay(new Date()).getTime().toString();
        }
        break;
      case 'Date & Time picker': {
        this.endTime = new Date(this.endTimeTemp).getTime().toString();
        break;
      }
      case 'This week Monday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Mon);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'This week Tuesday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Tue);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'This week Wednesday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Wed);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'This week Thursday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Thur);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'This week Friday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Fri);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'This week Saturday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Sat);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'This week Sunday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        const date = getClosestDayOfThisWeek(DayOfWeekMap.Sun);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'Next week Monday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Mon);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'Next week Tuesday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Tue);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'Next week Wednesday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Wed);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'Next week Thursday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Thur);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'Next week Friday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Fri);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'Next week Saturday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Sat);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'Next week Sunday': {
        const h = new Date(this.endTimeTemp).getHours();
        const m = new Date(this.endTimeTemp).getMinutes();
        let date = getClosestDayOfThisWeek(DayOfWeekMap.Sun);
        date = addDays(date, 7);
        date.setHours(h);
        date.setMinutes(m);
        date.setSeconds(0);
        this.endTime = date.getTime().toString();
        break;
      }
      case 'Hours and minutes': {
        // this.startTime is a string holding the time in ms
        // to generate a date from this we can do new Date(parseInt(this.startTime, 10))
        this.endTime = addHours(
          new Date(parseInt(this.startTime, 10)),
          new Date(this.endTimeValidity).getHours()
        )
          .getTime()
          .toString();
        this.endTime = addMinutes(
          new Date(parseInt(this.endTime, 10)),
          new Date(this.endTimeValidity).getMinutes()
        )
          .getTime()
          .toString();
        break;
      }
      case 'End of day':
        // keep empty, time is set add TicketModelList
        break;
      default:
        this.endTime = this.endTimeTemp.toString();
    }
    this.endTime = Math.round(parseInt(this.endTime, 10) / 1000).toString();
    this.startTime = Math.round(parseInt(this.startTime, 10) / 1000).toString();
    this.fetchCheck();
  };

  returnAll() {
    return this;
  }

  returnAllJSON() {
    const object = {
      title: this.title,
      subtitle: this.subtitle,
      text: this.text,
      startTime: this.startTime,
      endTime: this.endTime,
      idTemplate: this.idTemplate,
      id: this.id,
      credits: this.credits,
      qrData: this.qrData,
    };
    return object;
  }

  returnStringArray() {
    const stringArrayFromObject = `[${this.id.toString()},0,${this.startType},${
      this.startTime
    },${this.endType},${this.endTime},${this.credits.toFixed(2)}]`;

    return stringArrayFromObject;
  }

  @modelAction
  returnSignedString() {
    const stringArrayFromObject = `[${this.id.toString()},0,${this.startType},${
      this.startTime
    },${this.endType},${this.endTime},${this.credits.toFixed(2)},"${
      this.check
    }"]`;

    return stringArrayFromObject;
  }

  get allPlain() {
    return toJS(this);
  }

  @modelAction
  setTitle(title: string) {
    this.title = title;
  }

  @modelAction
  setStartTime(startTime: string) {
    this.startTime = startTime;
  }

  @modelAction
  setEndTime(endTime: string) {
    this.endTime = endTime;
  }

  @modelAction
  preparePrint() {
    if (this.startTimeConf === 'Time of printing') {
      this.startTime = new Date().getTime().toString();
    }
    if (this.endTimeConf === 'Until midnight') {
      this.endTime = new Date().getTime().toString();
    }
  }

  @modelAction
  changeCheck(data: string) {
    this.check = data;
  }

  @modelAction
  fetchCheck() {
    this.check = '';
    const rootStore = getRootStore<RootStore>(this);
    const template = rootStore?.templateList.getTemplateById(this.idTemplate);
    if (typeof template?.idTemplate !== 'string') return;

    if (this.check === '') {
      this.check = CryptoJS.enc.Base64.stringify(
        CryptoJS.HmacSHA224(this.returnStringArray(), template.idTemplate)
      );
    }
    this.qrData = this.returnSignedString();
  }
}
