"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const querystring = require("querystring");
require("./index-Br7AzmuA.cjs");
const CalEventParser = require("./CalEventParser-RR7nWuhu.cjs");
const index = require("./i18n-HHdcQbuJ.cjs");
const getAppKeysFromSlug = require("./getAppKeysFromSlug-tS_OdWJj.cjs");
const zod = require("./zod-5gXxWgvM.cjs");
const dayjs = require("dayjs");
class ZohoCalendarService {
  constructor(credential) {
    this.integrationName = "";
    this.zohoAuth = (credential2) => {
      let zohoCredentials = credential2.key;
      const refreshAccessToken = async () => {
        try {
          const appKeys = await getAppKeysFromSlug.getAppKeysFromSlug("zohocalendar");
          const { client_id, client_secret } = zod.appKeysSchema.parse(appKeys);
          const server_location = zohoCredentials.server_location;
          const params = {
            client_id,
            grant_type: "refresh_token",
            client_secret,
            refresh_token: zohoCredentials.refresh_token
          };
          const query = querystring.stringify(params);
          const res = await fetch(`https://accounts.zoho.${server_location}/oauth/v2/token?${query}`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json; charset=utf-8"
            }
          });
          const token = await res.json();
          if (!token.access_token) {
            throw new Error("Invalid token response");
          }
          const key = {
            access_token: token.access_token,
            refresh_token: zohoCredentials.refresh_token,
            expires_in: Math.round(+/* @__PURE__ */ new Date() / 1e3 + token.expires_in),
            server_location
          };
          await index.prisma.credential.update({
            where: { id: credential2.id },
            data: { key }
          });
          zohoCredentials = key;
        } catch (err) {
          this.log.error("Error refreshing zoho token", err);
        }
        return zohoCredentials;
      };
      return {
        getToken: async () => {
          const isExpired = () => new Date(zohoCredentials.expires_in * 1e3).getTime() <= (/* @__PURE__ */ new Date()).getTime();
          return !isExpired() ? Promise.resolve(zohoCredentials) : refreshAccessToken();
        }
      };
    };
    this.fetcher = async (endpoint, init) => {
      const credentials = await this.auth.getToken();
      return fetch(`https://calendar.zoho.${credentials.server_location}/api/v1${endpoint}`, {
        method: "GET",
        ...init,
        headers: {
          Authorization: `Bearer ${credentials.access_token}`,
          "Content-Type": "application/json",
          ...init?.headers
        }
      });
    };
    this.getUserInfo = async () => {
      const credentials = await this.auth.getToken();
      const response = await fetch(`https://accounts.zoho.${credentials.server_location}/oauth/user/info`, {
        method: "GET",
        headers: {
          Authorization: `Bearer ${credentials.access_token}`,
          "Content-Type": "application/json"
        }
      });
      return this.handleData(response, this.log);
    };
    this.parseDateTime = (dateTimeStr) => {
      const dateOnlyFormat = "YYYYMMDD";
      const dateTimeFormat = "YYYYMMDD[T]HHmmss[Z]";
      const format = /^\d{8}Z$/.test(dateTimeStr) ? dateOnlyFormat : dateTimeFormat;
      return dayjs.utc(dateTimeStr, format);
    };
    this.translateEvent = (event) => {
      const zohoEvent = {
        title: event.title,
        description: event.calendarDescription,
        dateandtime: {
          start: dayjs(event.startTime).format("YYYYMMDDTHHmmssZZ"),
          end: dayjs(event.endTime).format("YYYYMMDDTHHmmssZZ"),
          timezone: event.organizer.timeZone
        },
        attendees: event.attendees.map((attendee) => ({ email: attendee.email })),
        isprivate: event.seatsShowAttendees,
        reminders: [
          {
            minutes: "-15",
            action: "popup"
          }
        ],
        location: event.location ? CalEventParser.getLocation(event) : void 0
      };
      return zohoEvent;
    };
    this.integrationName = "zoho_calendar";
    this.auth = this.zohoAuth(credential);
    this.log = index.logger.getSubLogger({
      prefix: [`[[lib] ${this.integrationName}`]
    });
  }
  async createEvent(event) {
    let eventId = "";
    let eventRespData;
    const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
    const calendarId = mainHostDestinationCalendar?.externalId;
    if (!calendarId) {
      throw new Error("no calendar id");
    }
    try {
      const query = querystring.stringify({
        eventdata: JSON.stringify(this.translateEvent(event))
      });
      const eventResponse = await this.fetcher(`/calendars/${calendarId}/events?${query}`, {
        method: "POST"
      });
      eventRespData = await this.handleData(eventResponse, this.log);
      eventId = eventRespData.events[0].uid;
    } catch (error) {
      this.log.error(error);
      throw error;
    }
    try {
      return {
        ...eventRespData.events[0],
        uid: eventRespData.events[0].uid,
        id: eventRespData.events[0].uid,
        type: "zoho_calendar",
        password: "",
        url: "",
        additionalInfo: {}
      };
    } catch (error) {
      this.log.error(error);
      await this.deleteEvent(eventId, event, calendarId);
      throw error;
    }
  }
  /**
   * @param uid
   * @param event
   * @returns
   */
  async updateEvent(uid, event, externalCalendarId) {
    const eventId = uid;
    let eventRespData;
    const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
    const calendarId = externalCalendarId || mainHostDestinationCalendar?.externalId;
    if (!calendarId) {
      this.log.error("no calendar id provided in updateEvent");
      throw new Error("no calendar id provided in updateEvent");
    }
    try {
      const existingEventResponse = await this.fetcher(`/calendars/${calendarId}/events/${uid}`);
      const existingEventData = await this.handleData(existingEventResponse, this.log);
      const query = querystring.stringify({
        eventdata: JSON.stringify({
          ...this.translateEvent(event),
          etag: existingEventData.events[0].etag
        })
      });
      const eventResponse = await this.fetcher(`/calendars/${calendarId}/events/${uid}?${query}`, {
        method: "PUT"
      });
      eventRespData = await this.handleData(eventResponse, this.log);
    } catch (error) {
      this.log.error(error);
      throw error;
    }
    try {
      return {
        ...eventRespData.events[0],
        uid: eventRespData.events[0].uid,
        id: eventRespData.events[0].uid,
        type: "zoho_calendar",
        password: "",
        url: "",
        additionalInfo: {}
      };
    } catch (error) {
      this.log.error(error);
      await this.deleteEvent(eventId, event);
      throw error;
    }
  }
  /**
   * @param uid
   * @param event
   * @returns
   */
  async deleteEvent(uid, event, externalCalendarId) {
    const [mainHostDestinationCalendar] = event.destinationCalendar ?? [];
    const calendarId = externalCalendarId || mainHostDestinationCalendar?.externalId;
    if (!calendarId) {
      this.log.error("no calendar id provided in deleteEvent");
      throw new Error("no calendar id provided in deleteEvent");
    }
    try {
      const existingEventResponse = await this.fetcher(`/calendars/${calendarId}/events/${uid}`);
      const existingEventData = await this.handleData(existingEventResponse, this.log);
      const response = await this.fetcher(`/calendars/${calendarId}/events/${uid}`, {
        method: "DELETE",
        headers: {
          etag: existingEventData.events[0].etag
        }
      });
      await this.handleData(response, this.log);
    } catch (error) {
      this.log.error(error);
      throw error;
    }
  }
  async getBusyData(dateFrom, dateTo, userEmail) {
    const query = querystring.stringify({
      sdate: dateFrom,
      edate: dateTo,
      ftype: "eventbased",
      uemail: userEmail
    });
    const response = await this.fetcher(`/calendars/freebusy?${query}`, {
      method: "GET"
    });
    const data = await this.handleData(response, this.log);
    if (data.fb_not_enabled || data.NODATA)
      return [];
    return data.freebusy.filter((freebusy) => freebusy.fbtype === "busy").map((freebusy) => ({
      // using dayjs utc plugin because by default, dayjs parses and displays in local time, which causes a mismatch
      start: this.parseDateTime(freebusy.startTime).toISOString(),
      end: this.parseDateTime(freebusy.endTime).toISOString()
    })) || [];
  }
  async getUnavailability(range, calendarId) {
    const query = querystring.stringify({
      range: JSON.stringify(range)
    });
    this.log.debug("getUnavailability query", query);
    try {
      const response = await this.fetcher(`/calendars/${calendarId}/events?${query}`);
      const data = await this.handleData(response, this.log);
      if (!data.events || data.events.length === 0)
        return [];
      return data.events.filter((event) => event.isprivate === false).map((event) => {
        const start = dayjs(event.dateandtime.start, "YYYYMMDD[T]HHmmssZ").utc().toISOString();
        const end = dayjs(event.dateandtime.end, "YYYYMMDD[T]HHmmssZ").utc().toISOString();
        return { start, end };
      }) || [];
    } catch (error) {
      this.log.error(error);
      return [];
    }
  }
  async getAvailability(dateFrom, dateTo, selectedCalendars) {
    const selectedCalendarIds = selectedCalendars.filter((e) => e.integration === this.integrationName).map((e) => e.externalId);
    if (selectedCalendarIds.length === 0 && selectedCalendars.length > 0) {
      return Promise.resolve([]);
    }
    try {
      let queryIds = selectedCalendarIds;
      if (queryIds.length === 0) {
        queryIds = (await this.listCalendars()).map((e) => e.externalId) || [];
        if (queryIds.length === 0) {
          return Promise.resolve([]);
        }
      }
      if (!selectedCalendars[0])
        return [];
      const userInfo = await this.getUserInfo();
      const originalStartDate = dayjs(dateFrom);
      const originalEndDate = dayjs(dateTo);
      const diff = originalEndDate.diff(originalStartDate, "days");
      if (diff <= 30) {
        const busyData = await this.getBusyData(
          originalStartDate.format("YYYYMMDD[T]HHmmss[Z]"),
          originalEndDate.format("YYYYMMDD[T]HHmmss[Z]"),
          userInfo.Email
        );
        const unavailabilityData = await Promise.all(
          queryIds.map(
            (calendarId) => this.getUnavailability(
              {
                start: originalStartDate.format("YYYYMMDD[T]HHmmss[Z]"),
                end: originalEndDate.format("YYYYMMDD[T]HHmmss[Z]")
              },
              calendarId
            )
          )
        );
        const unavailability = unavailabilityData.flat();
        return busyData.concat(unavailability);
      } else {
        const busyData = [];
        const loopsNumber = Math.ceil(diff / 30);
        let startDate = originalStartDate;
        let endDate = originalStartDate.add(30, "days");
        for (let i = 0; i < loopsNumber; i++) {
          if (endDate.isAfter(originalEndDate))
            endDate = originalEndDate;
          busyData.push(
            ...await this.getBusyData(
              startDate.format("YYYYMMDD[T]HHmmss[Z]"),
              endDate.format("YYYYMMDD[T]HHmmss[Z]"),
              userInfo.Email
            )
          );
          const unavailabilityData = await Promise.all(
            queryIds.map(
              (calendarId) => this.getUnavailability(
                {
                  start: startDate.format("YYYYMMDD[T]HHmmss[Z]"),
                  end: endDate.format("YYYYMMDD[T]HHmmss[Z]")
                },
                calendarId
              )
            )
          );
          const unavailability = unavailabilityData.flat();
          busyData.push(...unavailability);
          startDate = endDate.add(1, "minutes");
          endDate = startDate.add(30, "days");
        }
        return busyData;
      }
    } catch (error) {
      this.log.error(error);
      return [];
    }
  }
  async listCalendars() {
    try {
      const resp = await this.fetcher(`/calendars`);
      const data = await this.handleData(resp, this.log);
      const userInfo = await this.getUserInfo();
      const result = data.calendars.filter((cal) => {
        if (cal.privilege === "owner") {
          return true;
        }
        return false;
      }).map((cal) => {
        const calendar = {
          externalId: cal.uid ?? "No Id",
          integration: this.integrationName,
          name: cal.name || "No calendar name",
          primary: cal.isdefault,
          email: userInfo.Email ?? ""
        };
        return calendar;
      });
      if (result.some((cal) => !!cal.primary)) {
        return result;
      }
      const respPrimary = await this.fetcher(`/calendars?category=own`);
      const dataPrimary = await this.handleData(respPrimary, this.log);
      return dataPrimary.calendars.map((cal) => {
        const calendar = {
          externalId: cal.uid ?? "No Id",
          integration: this.integrationName,
          name: cal.name || "No calendar name",
          primary: cal.isdefault,
          email: userInfo.Email ?? ""
        };
        return calendar;
      });
    } catch (err) {
      this.log.error("There was an error contacting zoho calendar service: ", err);
      throw err;
    }
  }
  async handleData(response, log) {
    const data = await response.json();
    if (!response.ok) {
      log.debug("zoho request with data", data);
      throw data;
    }
    log.debug("zoho request with data", data);
    return data;
  }
}
exports.default = ZohoCalendarService;
