/**
 * Try to unlock the door with auth mode
 */

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 { AuthMethod } from "../../../types/AuthMethod";
import { DoorStatus } from "../../../types/DoorStatus";
import { getMetricFromRegistry, withMetrics } from "../../../common/DoormanHandler";
import { AuthMetrics, registerMetrics } from "../../../metrics/AuthMetrics";
import { Counter } from "prom-client";
import { DoorConfig, getDoorConfigID } from "../../../schema/DoorConfig";
import { createLockStatusWithTimeout, getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
import { setResponseJson } from "../../../utils/responseUtils";
import { AuthRequest, AuthRequestSchema } from "../../../../../doorman-schema/src";

export interface AuthRequestTwilio extends ServerlessEventObject<AuthRequest, UserAgentHeader> { };

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

  registerMetrics(metricsRegistry);

  let req = AuthRequestSchema.parse(event);

  // now we have validated obj
  let door = req.door;
  let pin = req.key;

  const db = createDynaBridgeClient(context);

  const config: DoorConfig | undefined = await db.entities.doorConfig.findById(getDoorConfigID(door));

  if (!config) {
    getMetricFromRegistry<Counter>(metricsRegistry, AuthMetrics.DOOR_CONFIG_NOT_FOUND).inc({ door }, 1);
    setResponseJson(response, 404, {
      msg: `Door ${door} not found`,
    });

    return callback(null, response);
  }

  let correctPin = config.pin;
  let discordUsers = config.discordUsers;

  let method: AuthMethod | undefined;

  if (correctPin === pin) {
    method = AuthMethod.PIN;
  }

  if (discordUsers.includes(pin)) {
    method = AuthMethod.DISCORD;
  }

  if (!method) {
    setResponseJson(response, 401, {
      msg: "Invalid PIN",
    });
    return callback(null, response);
  }

  getMetricFromRegistry<Counter>(metricsRegistry, AuthMetrics.AUTH_METHOD)
    .inc({ method }, 1);

  const fingerprint = {
    method,
    userAgent: event.request.headers['user-agent'],
    ip: req.ip,
  };

  // take timeout from the query string
  const timeout = req.timeout || config.timeout;

  // check lock status if locked, then unlock. If unlocked then lock
  const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
  const isOpen = isLockOpen(lock);

  if (isOpen && lock && !req.ip) {
    const fingerprint = JSON.parse(lock.fingerprint);

    setResponseJson(response, 200, {
      status: DoorStatus.CLOSED,
      fingerprint,
    });

    await db.entities.lockStatus.deleteById(getLockStatusID(door));
  } else {
    await db.entities.lockStatus.save(createLockStatusWithTimeout(door, timeout, fingerprint));

    setResponseJson(response, 200, {
      msg: `Opened the door "${door}" for ${timeout}s`
    });
  }

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

  return callback(null, response);
});
