"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const CalEventParser = require("./CalEventParser-RR7nWuhu.cjs");
const index = require("./i18n-HHdcQbuJ.cjs");
const refreshOAuthTokens = require("./refreshOAuthTokens-GXGj_21S.cjs");
const getAppKeysFromSlug = require("./getAppKeysFromSlug-tS_OdWJj.cjs");
const z = require("zod");
const FEISHU_HOST = "open.feishu.cn";
const getAppKeys = () => getAppKeysFromSlug.getAppKeysFromSlug("feishu-calendar");
const isExpired = (expiryDate) => !expiryDate || expiryDate < Math.round(Number(/* @__PURE__ */ new Date()) / 1e3);
async function handleFeishuError(response, log2) {
  const data = await response.json();
  if (!response.ok || data.code !== 0) {
    log2.error("feishu error with error: ", data, ", logid is:", response.headers.get("X-Tt-Logid"));
    log2.debug("feishu request with data", data);
    throw data;
  }
  log2.info("feishu request with logid:", response.headers.get("X-Tt-Logid"));
  log2.debug("feishu request with data", data);
  return data;
}
const log = index.logger.getSubLogger({ prefix: [`[[FeishuAppCredential]`] });
function makePoolingPromise(promiseCreator, times = 24, delay = 5 * 1e3) {
  return new Promise((resolve, reject) => {
    promiseCreator().then(resolve).catch((err) => {
      if (times <= 0) {
        reject(err);
        return;
      }
      setTimeout(() => {
        makePoolingPromise(promiseCreator, times - 1, delay).then(resolve).catch(reject);
      }, delay);
    });
  });
}
const appKeysSchema = z.z.object({
  app_id: z.z.string().min(1),
  app_secret: z.z.string().min(1),
  app_access_token: z.z.string().optional(),
  app_ticket: z.z.string().optional(),
  expire_date: z.z.number().optional(),
  open_verification_token: z.z.string().min(1)
});
const getValidAppKeys = async () => {
  const appKeys = await getAppKeys();
  const validAppKeys = appKeysSchema.parse(appKeys);
  return validAppKeys;
};
const getAppTicketFromKeys = async () => {
  const appKeys = await getValidAppKeys();
  const appTicketNew = appKeys?.app_ticket;
  if (appTicketNew) {
    return appTicketNew;
  }
  throw Error("feishu appTicketNew not found in getAppTicketFromKeys");
};
const getAppTicket = async () => {
  log.debug("get app ticket invoked");
  const appKeys = await getValidAppKeys();
  if (typeof appKeys.app_ticket === "string" && appKeys.app_ticket !== "") {
    log.debug("has app ticket", appKeys.app_ticket);
    return appKeys.app_ticket;
  }
  log.info("Invoke app-ticket resend", appKeys.app_ticket);
  fetch(`https://${FEISHU_HOST}/open-apis/auth/v3/app_ticket/resend`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      app_id: appKeys.app_id,
      app_secret: appKeys.app_secret
    })
  });
  const appTicketNew = await makePoolingPromise(getAppTicketFromKeys);
  if (appTicketNew) {
    log.debug("has new app ticket", appTicketNew);
    return appTicketNew;
  }
  log.error("app ticket not found");
  throw new Error("No app ticket found");
};
const getAppAccessToken = async () => {
  log.debug("get app access token invoked");
  const appKeys = await getValidAppKeys();
  const appAccessToken = appKeys.app_access_token;
  const expireDate = appKeys.expire_date;
  if (appAccessToken && expireDate && !isExpired(expireDate)) {
    log.debug("get app access token not expired");
    return appAccessToken;
  }
  const appTicket = await getAppTicket();
  const fetchAppAccessToken = (app_ticket) => fetch(`https://${FEISHU_HOST}/open-apis/auth/v3/app_access_token`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      app_id: appKeys.app_id,
      app_secret: appKeys.app_secret,
      app_ticket
    })
  });
  const resp = await fetchAppAccessToken(appTicket);
  const data = await resp.json();
  if (!resp.ok || data.code !== 0) {
    index.logger.error("feishu error with error: ", data, ", logid is:", resp.headers.get("X-Tt-Logid"));
    if (data.code === 10012) {
      await index.prisma.app.update({
        where: { slug: "feishu-calendar" },
        data: { keys: { ...appKeys, app_ticket: "" } }
      });
      throw new Error("app_ticket invalid, please try again");
    }
  }
  const newAppAccessToken = data.app_access_token;
  const newExpireDate = Math.round(Number(/* @__PURE__ */ new Date()) / 1e3 + data.expire);
  await index.prisma.app.update({
    where: { slug: "feishu-calendar" },
    data: {
      keys: {
        ...appKeys,
        app_access_token: newAppAccessToken,
        expire_date: newExpireDate
      }
    }
  });
  return newAppAccessToken;
};
function parseEventTime2Timestamp(eventTime) {
  return String(+new Date(eventTime) / 1e3);
}
class FeishuCalendarService {
  constructor(credential) {
    this.url = `https://${FEISHU_HOST}/open-apis`;
    this.integrationName = "";
    this.feishuAuth = (credential2) => {
      const feishuAuthCredentials = credential2.key;
      return {
        getToken: () => !isExpired(feishuAuthCredentials.expiry_date) ? Promise.resolve(feishuAuthCredentials.access_token) : this.refreshAccessToken(credential2)
      };
    };
    this.refreshAccessToken = async (credential2) => {
      const feishuAuthCredentials = credential2.key;
      const refreshExpireDate = feishuAuthCredentials.refresh_expires_date;
      const refreshToken = feishuAuthCredentials.refresh_token;
      if (isExpired(refreshExpireDate) || !refreshToken) {
        await index.prisma.credential.delete({ where: { id: credential2.id } });
        throw new Error("Feishu Calendar refresh token expired");
      }
      try {
        const appAccessToken = await getAppAccessToken();
        const resp = await refreshOAuthTokens.refreshOAuthTokens(
          async () => await fetch(`${this.url}/authen/v1/refresh_access_token`, {
            method: "POST",
            headers: {
              Authorization: `Bearer ${appAccessToken}`,
              "Content-Type": "application/json; charset=utf-8"
            },
            body: JSON.stringify({
              grant_type: "refresh_token",
              refresh_token: refreshToken
            })
          }),
          "feishu-calendar",
          credential2.userId
        );
        const data = await handleFeishuError(resp, this.log);
        this.log.debug(
          "FeishuCalendarService refreshAccessToken data refresh_expires_in",
          data.data.refresh_expires_in,
          "and access token expire in",
          data.data.expires_in
        );
        const newFeishuAuthCredentials = {
          refresh_token: data.data.refresh_token,
          refresh_expires_date: Math.round(+/* @__PURE__ */ new Date() / 1e3 + data.data.refresh_expires_in),
          access_token: data.data.access_token,
          expiry_date: Math.round(+/* @__PURE__ */ new Date() / 1e3 + data.data.expires_in)
        };
        await index.prisma.credential.update({
          where: {
            id: credential2.id
          },
          data: {
            key: newFeishuAuthCredentials
          }
        });
        return newFeishuAuthCredentials.access_token;
      } catch (error) {
        this.log.error("FeishuCalendarService refreshAccessToken error", error);
        throw error;
      }
    };
    this.fetcher = async (endpoint, init) => {
      let accessToken = "";
      try {
        accessToken = await this.auth.getToken();
      } catch (error) {
        throw new Error("get access token error");
      }
      return fetch(`${this.url}${endpoint}`, {
        method: "GET",
        headers: {
          Authorization: `Bearer ${accessToken}`,
          "Content-Type": "application/json",
          ...init?.headers
        },
        ...init
      });
    };
    this.createAttendees = async (event, eventId, credentialId) => {
      const mainHostDestinationCalendar = event.destinationCalendar ? event.destinationCalendar.find((cal) => cal.credentialId === credentialId) ?? event.destinationCalendar[0] : void 0;
      const calendarId = mainHostDestinationCalendar?.externalId;
      if (!calendarId) {
        this.log.error("no calendar id provided in createAttendees");
        throw new Error("no calendar id provided in createAttendees");
      }
      const attendeeResponse = await this.fetcher(
        `/calendar/v4/calendars/${calendarId}/events/${eventId}/attendees/create_attendees`,
        {
          method: "POST",
          body: JSON.stringify({
            attendees: this.translateAttendees(event),
            need_notification: false
          })
        }
      );
      return handleFeishuError(attendeeResponse, this.log);
    };
    this.listCalendars = async () => {
      try {
        const resp = await this.fetcher(`/calendar/v4/calendars`);
        const data = await handleFeishuError(resp, this.log);
        const result = data.data.calendar_list.filter((cal) => {
          if (cal.type !== "primary" && cal.type !== "shared") {
            return false;
          }
          if (cal.permissions === "private") {
            return false;
          }
          if (cal.role === "owner" || cal.role === "writer") {
            return true;
          }
          return false;
        }).map((cal) => {
          const calendar = {
            externalId: cal.calendar_id ?? "No Id",
            integration: this.integrationName,
            name: cal.summary_alias || cal.summary || "No calendar name",
            primary: cal.type === "primary",
            email: cal.calendar_id ?? ""
          };
          return calendar;
        });
        if (result.some((cal) => !!cal.primary)) {
          return result;
        }
        const respPrimary = await this.fetcher(`/calendar/v4/calendars/primary`, {
          method: "POST"
        });
        const dataPrimary = await handleFeishuError(respPrimary, this.log);
        return dataPrimary.data.calendars.map((item) => {
          const cal = item.calendar;
          const calendar = {
            externalId: cal.calendar_id ?? "No Id",
            integration: this.integrationName,
            name: cal.summary_alias || cal.summary || "No calendar name",
            primary: cal.type === "primary",
            email: cal.calendar_id ?? ""
          };
          return calendar;
        });
      } catch (err) {
        this.log.error("There was an error contacting feishu calendar service: ", err);
        throw err;
      }
    };
    this.translateEvent = (event) => {
      const feishuEvent = {
        summary: event.title,
        description: event.calendarDescription,
        start_time: {
          timestamp: parseEventTime2Timestamp(event.startTime),
          timezone: event.organizer.timeZone
        },
        end_time: {
          timestamp: parseEventTime2Timestamp(event.endTime),
          timezone: event.organizer.timeZone
        },
        attendee_ability: "none",
        free_busy_status: "busy",
        reminders: [
          {
            minutes: 5
          }
        ]
      };
      if (event.location) {
        feishuEvent.location = { name: CalEventParser.getLocation(event) };
      }
      return feishuEvent;
    };
    this.translateAttendees = (event) => {
      const attendeeArray = [];
      event.attendees.filter((att) => att.email).forEach((att) => {
        const attendee = {
          type: "third_party",
          is_optional: false,
          third_party_email: att.email
        };
        attendeeArray.push(attendee);
      });
      event.team?.members.forEach((member) => {
        if (member.email !== this.credential.user?.email) {
          const attendee = {
            type: "third_party",
            is_optional: false,
            third_party_email: member.email
          };
          attendeeArray.push(attendee);
        }
      });
      return attendeeArray;
    };
    this.integrationName = "feishu_calendar";
    this.auth = this.feishuAuth(credential);
    this.log = index.logger.getSubLogger({ prefix: [`[[lib] ${this.integrationName}`] });
    this.credential = credential;
  }
  async createEvent(event, credentialId) {
    let eventId = "";
    let eventRespData;
    const mainHostDestinationCalendar = event.destinationCalendar ? event.destinationCalendar.find((cal) => cal.credentialId === this.credential.id) ?? event.destinationCalendar[0] : void 0;
    const calendarId = mainHostDestinationCalendar?.externalId;
    if (!calendarId) {
      throw new Error("no calendar id");
    }
    try {
      const eventResponse = await this.fetcher(`/calendar/v4/calendars/${calendarId}/events/create_event`, {
        method: "POST",
        body: JSON.stringify(this.translateEvent(event))
      });
      eventRespData = await handleFeishuError(eventResponse, this.log);
      eventId = eventRespData.data.event.event_id;
    } catch (error) {
      this.log.error(error);
      throw error;
    }
    try {
      await this.createAttendees(event, eventId, credentialId);
      return {
        ...eventRespData,
        uid: eventRespData.data.event.event_id,
        id: eventRespData.data.event.event_id,
        type: "feishu_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?.find(
      (cal) => cal.externalId === externalCalendarId
    );
    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 eventResponse = await this.fetcher(
        `/calendar/v4/calendars/${calendarId}/events/${eventId}/patch_event`,
        {
          method: "PATCH",
          body: JSON.stringify(this.translateEvent(event))
        }
      );
      eventRespData = await handleFeishuError(eventResponse, this.log);
    } catch (error) {
      this.log.error(error);
      throw error;
    }
    try {
      return {
        ...eventRespData,
        uid: eventRespData.data.event.event_id,
        id: eventRespData.data.event.event_id,
        type: "feishu_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?.find(
      (cal) => cal.externalId === externalCalendarId
    );
    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 response = await this.fetcher(`/calendar/v4/calendars/${calendarId}/events/${uid}`, {
        method: "DELETE"
      });
      await handleFeishuError(response, this.log);
    } catch (error) {
      this.log.error(error);
      throw error;
    }
  }
  async getAvailability(dateFrom, dateTo, selectedCalendars) {
    const selectedCalendarIds = selectedCalendars.filter((e) => e.integration === this.integrationName).map((e) => e.externalId).filter(Boolean);
    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).filter(Boolean) || [];
        if (queryIds.length === 0) {
          return Promise.resolve([]);
        }
      }
      const response = await this.fetcher(`/calendar/v4/freebusy/batch_get`, {
        method: "POST",
        body: JSON.stringify({
          time_min: dateFrom,
          time_max: dateTo,
          calendar_ids: queryIds
        })
      });
      const data = await handleFeishuError(response, this.log);
      const busyData = data.data.freebusy_list?.reduce((acc, cur) => {
        acc.push({
          start: cur.start_time,
          end: cur.end_time
        });
        return acc;
      }, []) || [];
      return busyData;
    } catch (error) {
      this.log.error(error);
      return [];
    }
  }
}
exports.default = FeishuCalendarService;
