import { AbsoluteTimeRange, dateTime, TimeRange } from '@grafana/data';

export interface TimeRegionConfig {
  from?: string;
  fromDayOfWeek?: number; // 1-7

  to?: string;
  toDayOfWeek?: number; // 1-7

  timezone?: string;
}

interface ParsedTime {
  dayOfWeek?: number; // 1-7
  h?: number; // 0-23
  m?: number; // 0-59
  s?: number; // 0-59
}

export function calculateTimesWithin(cfg: TimeRegionConfig, tRange: TimeRange): AbsoluteTimeRange[] {
  if (!(cfg.fromDayOfWeek || cfg.from) && !(cfg.toDayOfWeek || cfg.to)) {
    return [];
  }

  // So we can mutate
  const timeRegion = { ...cfg };

  if (timeRegion.from && !timeRegion.to) {
    timeRegion.to = timeRegion.from;
  }

  if (!timeRegion.from && timeRegion.to) {
    timeRegion.from = timeRegion.to;
  }

  const hRange = {
    from: parseTimeOfDay(timeRegion.from),
    to: parseTimeOfDay(timeRegion.to),
  };

  if (!timeRegion.fromDayOfWeek && timeRegion.toDayOfWeek) {
    timeRegion.fromDayOfWeek = timeRegion.toDayOfWeek;
  }

  if (!timeRegion.toDayOfWeek && timeRegion.fromDayOfWeek) {
    timeRegion.toDayOfWeek = timeRegion.fromDayOfWeek;
  }

  if (timeRegion.fromDayOfWeek) {
    hRange.from.dayOfWeek = Number(timeRegion.fromDayOfWeek);
  }

  if (timeRegion.toDayOfWeek) {
    hRange.to.dayOfWeek = Number(timeRegion.toDayOfWeek);
  }

  if (hRange.from.dayOfWeek && hRange.from.h == null && hRange.from.m == null) {
    hRange.from.h = 0;
    hRange.from.m = 0;
    hRange.from.s = 0;
  }

  if (hRange.to.dayOfWeek && hRange.to.h == null && hRange.to.m == null) {
    hRange.to.h = 23;
    hRange.to.m = 59;
    hRange.to.s = 59;
  }

  if (!hRange.from || !hRange.to) {
    return [];
  }

  if (hRange.from.h == null) {
    hRange.from.h = 0;
  }

  if (hRange.to.h == null) {
    hRange.to.h = 23;
  }

  const regions: AbsoluteTimeRange[] = [];

  const fromStart = dateTime(tRange.from).utc();
  fromStart.set('hour', 0);
  fromStart.set('minute', 0);
  fromStart.set('second', 0);
  fromStart.set('millisecond', 0);
  fromStart.add(hRange.from.h, 'hours');
  fromStart.add(hRange.from.m, 'minutes');
  fromStart.add(hRange.from.s, 'seconds');

  while (fromStart.unix() <= tRange.to.unix()) {
    while (hRange.from.dayOfWeek && hRange.from.dayOfWeek !== fromStart.isoWeekday()) {
      fromStart.add(24, 'hours');
    }

    if (fromStart.unix() > tRange.to.unix()) {
      break;
    }

    const fromEnd = dateTime(fromStart).utc();

    if (fromEnd.hour) {
      if (hRange.from.h <= hRange.to.h) {
        fromEnd.add(hRange.to.h - hRange.from.h, 'hours');
      } else if (hRange.from.h > hRange.to.h) {
        while (fromEnd.hour() !== hRange.to.h) {
          fromEnd.add(1, 'hours');
        }
      } else {
        fromEnd.add(24 - hRange.from.h, 'hours');

        while (fromEnd.hour() !== hRange.to.h) {
          fromEnd.add(1, 'hours');
        }
      }
    }

    fromEnd.set('minute', hRange.to.m ?? 0);
    fromEnd.set('second', hRange.to.s ?? 0);

    while (hRange.to.dayOfWeek && hRange.to.dayOfWeek !== fromEnd.isoWeekday()) {
      fromEnd.add(24, 'hours');
    }

    const outsideRange =
      (fromStart.unix() < tRange.from.unix() && fromEnd.unix() < tRange.from.unix()) ||
      (fromStart.unix() > tRange.to.unix() && fromEnd.unix() > tRange.to.unix());

    if (!outsideRange) {
      regions.push({ from: fromStart.valueOf(), to: fromEnd.valueOf() });
    }

    fromStart.add(24, 'hours');
  }

  return regions;
}

export function parseTimeOfDay(str?: string): ParsedTime {
  const result: ParsedTime = {};
  if (!str?.length) {
    return result;
  }

  const match = str.split(':');
  if (!match?.length) {
    return result;
  }

  result.h = Math.min(23, Math.max(0, Number(match[0])));
  if (match.length > 1) {
    result.m = Math.min(60, Math.max(0, Number(match[1])));
    if (match.length > 2) {
      result.s = Math.min(60, Math.max(0, Number(match[2])));
    }
  }
  return result;
}

export function formatTimeOfDayString(t?: ParsedTime): string {
  if (!t || (t.h == null && t.m == null && t.s == null)) {
    return '';
  }

  let str = String(t.h ?? 0).padStart(2, '0') + ':' + String(t.m ?? 0).padStart(2, '0');
  if (t.s != null) {
    str += String(t.s ?? 0).padStart(2, '0');
  }

  return str;
}
