
/**
 * Edit API for doors
 */

import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
import { TwilioContext } from "../../../types/TwilioContext";
import { UserAgentHeader } from "../../../utils/blockUserAgent";
import { createDynaBridgeClient } from "../../../utils/ddb";
import { sendMessageToUser } from "../../../utils/discord";
import { createEditDoorConfig, EditDoorConfigReqSchema, editDoorToDoorConfig, getDoorConfigID, getEditDoorConfigID } from "../../../schema/DoorConfig";
import { z } from "zod";
import { withMetrics } from "../../../common/DoormanHandler";
import { setResponseJson } from "../../../utils/responseUtils";

import { zu } from "zod_utilz";

// @ts-ignore
import isDeepSubset from "is-deep-subset";

export const EditRequestSchema = z.object({
  door: z.string(),
  approvalId: z.string().optional(),
  newConfig: zu.stringToJSON().optional(),
});

export type EditRequest = z.infer<typeof EditRequestSchema>;

export interface EditRequestTwilio extends ServerlessEventObject<EditRequest, UserAgentHeader> { }

export const handler: ServerlessFunctionSignature<TwilioContext, EditRequestTwilio> = withMetrics("edit", async (context, event, callback, metricsRegistry) => {
  const response = new Twilio.Response();

  const req = EditRequestSchema.parse(event);

  let door = req.door;
  let approvalId = req.approvalId;
  let newConfigRaw = req.newConfig;

  const db = createDynaBridgeClient(context);

  // approve path
  if (approvalId) {
    const newConfig = await db.entities.editDoorConfig.findById(getEditDoorConfigID(door));

    if (!newConfig || newConfig.approvalId !== approvalId) {
      response.setStatusCode(400);
      return callback(null, response);
    }

    await db.entities.doorConfig.save(editDoorToDoorConfig(newConfig));

    // send update to discord users
    const updateMessage = `Configuration change \`${approvalId}\` was approved @ Door "${door}"`;

    const discordPromises = newConfig.discordUsers.map((user) => {
      return sendMessageToUser(
        context,
        user,
        updateMessage,
      ).catch(e => console.error(e))
    });

    await Promise.all(discordPromises);

    response
      .setStatusCode(200)
      .setBody(`Configuration change ${approvalId} approved!`);
    return callback(null, response);
  }

  if (!newConfigRaw) {
    setResponseJson(response, 400, {
      msg: "Missing new config",
    });
    return callback(null, response);
  }

  const newConfig = EditDoorConfigReqSchema.parse(newConfigRaw);

  const config = await db.entities.doorConfig.findById(getDoorConfigID(door));

  if (!config) {
    setResponseJson(response, 404, {
      msg: `Door not found ${door}`,
    });
    return callback(null, response);
  }

  // set to old PIN if it is missing
  if (newConfig.pin === "") {
    newConfig.pin = config.pin;
  }

  // if nothing changed, we should throw since this is a pointless change
  if (isDeepSubset(config, newConfig)) {
    setResponseJson(response, 400, {
      msg: "Nothing changed in the new config",
    });

    return callback(null, response);
  }

  const editDoorConfig = createEditDoorConfig(door, newConfig);
  await db.entities.editDoorConfig.save(editDoorConfig);

  const params: EditRequest = {
    door,
    approvalId: editDoorConfig.approvalId,
  };

  const approvalUrl = `https://doorman.chromart.cc/api/door/edit?` + (new URLSearchParams(params as any)).toString();
  console.log(approvalUrl);

  // send update to discord users
  const approvalMessage = `Configuration change \`${editDoorConfig.approvalId}\` requested @ Door "${door}" [click to approve it](${approvalUrl})\`\`\`${JSON.stringify(newConfig, null, 4)}\`\`\``;

  const discordPromises = config.discordUsers.map((user) => {
    return sendMessageToUser(
      context,
      user,
      approvalMessage,
    ).catch(e => console.error(e))
  });

  await Promise.all(discordPromises);

  setResponseJson(response, 200, {
    msg: 'Created configuration change',
  });

  // destroy the internal client after
  // @ts-ignore
  db.ddbClient.destroy();

  return callback(null, response);
});
