import getServiceWidget from "utils/config/service-helpers";
import createLogger from "utils/logger";
import { formatApiCall } from "utils/proxy/api-helpers";
import genericProxyHandler from "utils/proxy/handlers/generic";
import calendarProxyHandler from "widgets/calendar/proxy";
import widgets from "widgets/widgets";

const logger = createLogger("servicesProxy");

export default async function handler(req, res) {
  try {
    const { service, group, index } = req.query;
    const serviceWidget = await getServiceWidget(group, service, index);
    let type = serviceWidget?.type;

    // exceptions
    if (type === "calendar") type = "ical";
    else if (service === "unifi_console" && group === "unifi_console") type = "unifi_console";

    const widget = widgets[type];

    if (!widget) {
      logger.debug("Unknown proxy service type: %s", type);
      return res.status(403).json({ error: "Unknown proxy service type" });
    }

    const serviceProxyHandler = widget.proxyHandler || genericProxyHandler;

    if (serviceProxyHandler instanceof Function) {
      // quick return for no endpoint services, calendar is an exception
      if (!req.query.endpoint || serviceProxyHandler === calendarProxyHandler) {
        return serviceProxyHandler(req, res);
      }

      // map opaque endpoints to their actual endpoint
      if (widget?.mappings) {
        const mapping = widget?.mappings?.[req.query.endpoint];
        const mappingParams = mapping?.params;
        const optionalParams = mapping?.optionalParams;
        const map = mapping?.map;
        const endpoint = mapping?.endpoint;
        const endpointProxy = mapping?.proxyHandler || serviceProxyHandler;

        if (mapping?.method && mapping.method !== req.method) {
          logger.debug("Unsupported method: %s", req.method);
          return res.status(403).json({ error: "Unsupported method" });
        }

        if (!endpoint) {
          logger.debug("Unsupported service endpoint: %s", type);
          return res.status(403).json({ error: "Unsupported service endpoint" });
        }

        req.method = mapping?.method || "GET";
        if (mapping?.body) req.body = mapping?.body;
        req.query.endpoint = endpoint;

        if (req.query.segments) {
          const segments = JSON.parse(req.query.segments);
          let validSegments = true;
          Object.keys(segments).forEach((key) => {
            if (!mapping.segments.includes(key)) {
              logger.debug("Unsupported segment: %s", key);
              validSegments = false;
            } else if (segments[key].includes("/") || segments[key].includes("\\") || segments[key].includes("..")) {
              logger.debug("Unsupported segment value: %s", segments[key]);
              validSegments = false;
            }
          });
          if (!validSegments) return res.status(403).json({ error: "Unsupported segment" });
          req.query.endpoint = formatApiCall(endpoint, segments);
        }

        if (req.query.query && (mappingParams || optionalParams)) {
          const queryParams = JSON.parse(req.query.query);

          let filteredOptionalParams = [];
          if (optionalParams) filteredOptionalParams = optionalParams.filter((p) => queryParams[p] !== undefined);

          let params = [];
          if (mappingParams) params = params.concat(mappingParams);
          if (filteredOptionalParams) params = params.concat(filteredOptionalParams);

          const query = new URLSearchParams(params.map((p) => [p, queryParams[p]]));
          req.query.endpoint = `${req.query.endpoint}?${query}`;
        }

        if (mapping?.headers) {
          req.extraHeaders = mapping.headers;
        }

        if (endpointProxy instanceof Function) {
          return endpointProxy(req, res, map);
        }

        return serviceProxyHandler(req, res, map);
      }

      if (widget.allowedEndpoints instanceof RegExp) {
        if (widget.allowedEndpoints.test(req.query.endpoint)) {
          return serviceProxyHandler(req, res);
        }
      }

      logger.debug("Unmapped proxy request.");
      return res.status(403).json({ error: "Unmapped proxy request." });
    }

    logger.debug("Unknown proxy service type: %s", type);
    return res.status(403).json({ error: "Unknown proxy service type" });
  } catch (e) {
    if (e) logger.error(e);
    return res.status(500).send({ error: "Unexpected error" });
  }
}
