import { Injectable } from '@nestjs/common';
import { CalendarEvent } from '../models/calendar-event';
import { RedmineTypes } from '../models/redmine-types';
import * as Luxon from 'luxon';
import { IssuesService } from '../issues/issues.service';
import { randomUUID } from 'crypto';

/*
BEGIN:VCALENDAR
PRODID:-//Example Corp.//CalDAV Client//EN
VERSION:2.0
BEGIN:VEVENT
UID:1@example.com
SUMMARY:One-off Meeting
DTSTAMP:20041210T183904Z
DTSTART:20041207T120000Z
DTEND:20041207T130000Z
END:VEVENT
BEGIN:VEVENT
UID:2@example.com
SUMMARY:Weekly Meeting
DTSTAMP:20041210T183838Z
DTSTART:20041206T120000Z
DTEND:20041206T130000Z
RRULE:FREQ=WEEKLY
END:VEVENT
BEGIN:VEVENT
UID:2@example.com
SUMMARY:Weekly Meeting
RECURRENCE-ID:20041213T120000Z
DTSTAMP:20041210T183838Z
DTSTART:20041213T130000Z
DTEND:20041213T140000Z
END:VEVENT
END:VCALENDAR
*/

export type IssueAndEvent = {
  issue: RedmineTypes.ExtendedIssue;
  event: CalendarEvent;
};

@Injectable()
export class CalendarService {
  private defaultInterval = 30 * 24 * 60 * 60 * 1000; // 30 days

  constructor(
    public calendarEventsKey: string,
    private issuesService: IssuesService,
  ) {}

  /**
   * @param filter фильтр для первичной выборки данных из couchdb через nano.filter
   * @param interval период в милисекундах
   * @returns
   */
  async getICalData(filter: any, interval?: number): Promise<string> {
    const issues = await this.issuesService.find(filter);
    const actualEvents = this.getActualEvents(issues, interval);
    const formattedEvents = actualEvents
      .map((event) => {
        return this.generateICalendarEvent(event.issue, event.event);
      })
      .filter((event) => {
        return !!event;
      });
    const res = this.generateICalendar(formattedEvents);
    return res;
  }

  /**
   * @param filter фильтр для первичной выборки данных из couchdb через nano.filter
   * @param interval период в милисекундах
   * @returns
   */
  async getRawData(filter: any, interval?: number): Promise<IssueAndEvent[]> {
    const issues = await this.issuesService.find(filter);
    return this.getActualEvents(issues, interval);
  }

  private getActualEvents(
    issues: RedmineTypes.ExtendedIssue[],
    interval?: number,
  ): IssueAndEvent[] {
    if (typeof interval !== 'number') interval = this.defaultInterval;
    const res: IssueAndEvent[] = [];
    for (let i = 0; i < issues.length; i++) {
      const issue = issues[i];
      if (
        !issue[this.calendarEventsKey] ||
        issue[this.calendarEventsKey].length <= 0
      ) {
        continue;
      }
      const events = issue[this.calendarEventsKey];
      for (let j = 0; j < events.length; j++) {
        const event = events[j];
        if (this.actualEvent(event, interval)) {
          res.push({ event: event, issue: issue });
        }
      }
    }
    return res;
  }

  private actualEvent(event: CalendarEvent, interval: number): boolean {
    const now = Luxon.DateTime.now().toMillis();
    const from = now - interval;
    const to = now + interval;
    return Boolean(
      (from <= event.fromTimestamp && event.fromTimestamp <= to) ||
        (from <= event.toTimestamp && event.toTimestamp <= to),
    );
  }

  private generateICalendarEvent(
    issue: RedmineTypes.Issue,
    data: CalendarEvent,
  ): string | null {
    if (!data) return null;
    return `BEGIN:VEVENT
UID:${randomUUID()}@example.com
DTSTAMP:${this.formatTimestamp(data.fromTimestamp, data.fullDay)}
ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com
DTSTART:${this.formatTimestamp(data.fromTimestamp, data.fullDay)}
DTEND:${this.formatTimestamp(data.toTimestamp, data.fullDay)}
SUMMARY:#${issue.id} - ${data.description} - ${issue.subject}
END:VEVENT`;
  }

  private formatTimestamp(timestamp: number, fullDay?: boolean): string {
    const format: string = fullDay ? 'yyyyMMdd' : "yyyyMMdd'T'HHmmss'Z'";
    let datetime = Luxon.DateTime.fromMillis(timestamp);
    if (!fullDay) datetime = datetime.setZone('utc');
    return datetime.toFormat(format);
  }

  private generateICalendar(events: string[]): string {
    return `BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Example Corp.//CalDAV Client//EN
${events.join('\n')}
END:VCALENDAR`;
  }
}
