"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const querystring = require("querystring");
const index$2 = require("./index-3NyJcp7J.cjs");
const defaultHandler = require("./defaultHandler-MSjhitc-.cjs");
const defaultResponder = require("./defaultResponder-M6qNboV6.cjs");
const getAppKeysFromSlug = require("./getAppKeysFromSlug-RzLz5H5D.cjs");
const encodeOAuthState = require("./encodeOAuthState-VBfelnxQ.cjs");
const config = require("./config-6yCOCVff.cjs");
const indexBrowser = require("./index-browser-oVJhV6GU.cjs");
const connectedCalendar = require("./connectedCalendar-xbA085ms.cjs");
const getSafeRedirectUrl = require("./getSafeRedirectUrl-K3gbpWLF.cjs");
const getInstalledAppPath = require("./getInstalledAppPath-3orMA3sa.cjs");
const decodeOAuthState = require("./decodeOAuthState--7fljpCf.cjs");
const index$3 = require("./email-manager-398UKAw1.cjs");
const dayjs = require("dayjs");
const OAUTH_BASE_URL = "https://accounts.zoho.com/oauth/v2";
async function getHandler$1(req, res) {
  const appKeys = await getAppKeysFromSlug.getAppKeysFromSlug(config.config.slug);
  const { client_id } = indexBrowser.appKeysSchema$3.parse(appKeys);
  const state = encodeOAuthState.encodeOAuthState(req);
  const params = {
    client_id,
    response_type: "code",
    redirect_uri: `${index$2.WEBAPP_URL}/api/integrations/zohocalendar/callback`,
    scope: [
      "ZohoCalendar.calendar.ALL",
      "ZohoCalendar.event.ALL",
      "ZohoCalendar.freebusy.READ",
      "AaaServer.profile.READ"
    ],
    access_type: "offline",
    state,
    prompt: "consent"
  };
  const query = querystring.stringify(params);
  res.status(200).json({ url: `${OAUTH_BASE_URL}/auth?${query}` });
}
const add = defaultHandler.defaultHandler({
  GET: Promise.resolve({ default: defaultResponder.defaultResponder(getHandler$1) })
});
const log = index$2.logger.getSubLogger({ prefix: [`[[zohocalendar/api/callback]`] });
function getOAuthBaseUrl(domain) {
  return `https://accounts.zoho.${domain}/oauth/v2`;
}
async function getHandler(req, res) {
  const { code, location } = req.query;
  const state = decodeOAuthState.decodeOAuthState(req);
  if (code && typeof code !== "string") {
    res.status(400).json({ message: "`code` must be a string" });
    return;
  }
  if (location && typeof location !== "string") {
    res.status(400).json({ message: "`location` must be a string" });
    return;
  }
  if (!req.session?.user?.id) {
    return res.status(401).json({ message: "You must be logged in to do this" });
  }
  const appKeys = await getAppKeysFromSlug.getAppKeysFromSlug(config.config.slug);
  const { client_id, client_secret } = indexBrowser.appKeysSchema$3.parse(appKeys);
  const params = {
    client_id,
    grant_type: "authorization_code",
    client_secret,
    redirect_uri: `${index$2.WEBAPP_URL}/api/integrations/${config.config.slug}/callback`,
    code
  };
  let server_location;
  if (location === "us") {
    server_location = "com";
  } else if (location === "au") {
    server_location = "com.au";
  } else {
    server_location = location;
  }
  const query = querystring.stringify(params);
  const response = await fetch(`${getOAuthBaseUrl(server_location || "com")}/token?${query}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json; charset=utf-8"
    }
  });
  const responseBody = await JSON.parse(await response.text());
  if (!response.ok || responseBody.error) {
    log.error("get access_token failed", responseBody);
    return res.redirect(`/apps/installed?error=${JSON.stringify(responseBody)}`);
  }
  const key = {
    access_token: responseBody.access_token,
    refresh_token: responseBody.refresh_token,
    expires_in: Math.round(+/* @__PURE__ */ new Date() / 1e3 + responseBody.expires_in),
    server_location: server_location || "com"
  };
  function getCalenderUri(domain) {
    return `https://calendar.zoho.${domain}/api/v1/calendars`;
  }
  const calendarResponse = await fetch(getCalenderUri(server_location || "com"), {
    method: "GET",
    headers: {
      Authorization: `Bearer ${key.access_token}`,
      "Content-Type": "application/json"
    }
  });
  const data = await calendarResponse.json();
  const primaryCalendar = data.calendars.find((calendar) => calendar.isdefault);
  if (primaryCalendar.uid) {
    const credential = await index$2.prisma.credential.create({
      data: {
        type: config.config.type,
        key,
        userId: req.session.user.id,
        appId: config.config.slug
      }
    });
    const selectedCalendarWhereUnique = {
      userId: req.session?.user.id,
      integration: config.config.type,
      externalId: primaryCalendar.uid
    };
    try {
      await index$2.prisma.selectedCalendar.create({
        data: {
          ...selectedCalendarWhereUnique,
          credentialId: credential.id
        }
      });
    } catch (error) {
      let errorMessage = "something_went_wrong";
      if (error instanceof indexBrowser.indexBrowser.Prisma.PrismaClientKnownRequestError && error.code === "P2002") {
        if (await connectedCalendar.renewSelectedCalendarCredentialId(selectedCalendarWhereUnique, credential.id)) {
          res.redirect(
            getSafeRedirectUrl.getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath.getInstalledAppPath({ variant: "calendar", slug: config.config.slug })
          );
          return;
        }
        errorMessage = "account_already_linked";
      }
      await index$2.prisma.credential.delete({ where: { id: credential.id } });
      res.redirect(
        `${getSafeRedirectUrl.getSafeRedirectUrl(state?.onErrorReturnTo) ?? getInstalledAppPath.getInstalledAppPath({ variant: config.config.variant, slug: config.config.slug })}?error=${errorMessage}`
      );
      return;
    }
  }
  res.redirect(
    getSafeRedirectUrl.getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath.getInstalledAppPath({ variant: config.config.variant, slug: config.config.slug })
  );
}
const callback = defaultHandler.defaultHandler({
  GET: Promise.resolve({ default: defaultResponder.defaultResponder(getHandler) })
});
const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
  __proto__: null,
  add,
  callback
}, Symbol.toStringTag, { value: "Module" }));
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 } = indexBrowser.appKeysSchema$3.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$2.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 ? index$3.getLocation(event) : void 0
      };
      return zohoEvent;
    };
    this.integrationName = "zoho_calendar";
    this.auth = this.zohoAuth(credential);
    this.log = index$2.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, log2) {
    const data = await response.json();
    if (!response.ok) {
      log2.debug("zoho request with data", data);
      throw data;
    }
    log2.debug("zoho request with data", data);
    return data;
  }
}
const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
  __proto__: null,
  CalendarService: ZohoCalendarService
}, Symbol.toStringTag, { value: "Module" }));
exports.api = index$1;
exports.lib = index;
