import SSOUtil from "../Utils/SSO";
import URL from "Common/Types/API/URL";
import OneUptimeDate from "Common/Types/Date";
import Email from "Common/Types/Email";
import BadRequestException from "Common/Types/Exception/BadRequestException";
import Exception from "Common/Types/Exception/Exception";
import ServerException from "Common/Types/Exception/ServerException";
import HashedString from "Common/Types/HashedString";
import { JSONObject } from "Common/Types/JSON";
import ObjectID from "Common/Types/ObjectID";
import PositiveNumber from "Common/Types/PositiveNumber";
import { Host, HttpProtocol } from "Common/Server/EnvironmentConfig";
import StatusPagePrivateUserService from "Common/Server/Services/StatusPagePrivateUserService";
import StatusPageService from "Common/Server/Services/StatusPageService";
import StatusPageSsoService from "Common/Server/Services/StatusPageSsoService";
import CookieUtil from "Common/Server/Utils/Cookie";
import Express, {
  ExpressRequest,
  ExpressResponse,
  ExpressRouter,
  NextFunction,
} from "Common/Server/Utils/Express";
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
import logger from "Common/Server/Utils/Logger";
import Response from "Common/Server/Utils/Response";
import StatusPagePrivateUser from "Common/Models/DatabaseModels/StatusPagePrivateUser";
import StatusPageSSO from "Common/Models/DatabaseModels/StatusPageSso";
import xml2js from "xml2js";

// Initialize Express router.
const router: ExpressRouter = Express.getRouter();

// Define a GET route for SSO in a status page context.
router.get(
  "/status-page-sso/:statusPageId/:statusPageSsoId",
  async (
    req: ExpressRequest,
    res: ExpressResponse,
    next: NextFunction,
  ): Promise<void> => {
    try {
      // Check if statusPageId parameter is present.
      if (!req.params["statusPageId"]) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("Status Page ID not found"),
        );
      }

      // Check if statusPageSsoId parameter is present.
      if (!req.params["statusPageSsoId"]) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("Status Page SSO ID not found"),
        );
      }

      // Create ObjectID instance from statusPageId parameter.
      const statusPageId: ObjectID = new ObjectID(req.params["statusPageId"]);

      // Find SSO record in the database with specific query parameters.
      const statusPageSSO: StatusPageSSO | null =
        await StatusPageSsoService.findOneBy({
          query: {
            statusPageId: statusPageId, // Ensure that statusPageId matches.
            _id: req.params["statusPageSsoId"], // Ensure SSO ID matches.
            isEnabled: true, // Ensure the SSO is enabled.
          },
          select: {
            signOnURL: true,
            statusPageId: true,
            _id: true,
          },
          props: {
            isRoot: true,
          },
        });

      if (!statusPageSSO) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("SSO Config not found"),
        );
      }

      // redirect to Identity Provider.

      if (!statusPageSSO.signOnURL) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("Sign On URL not found"),
        );
      }

      const samlRequestUrl: URL = SSOUtil.createSAMLRequestUrl({
        acsUrl: URL.fromString(
          `${HttpProtocol}${Host}/identity/status-page-idp-login/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`,
        ),
        signOnUrl: statusPageSSO.signOnURL!,
        issuerUrl: URL.fromString(
          `${HttpProtocol}${Host}/${statusPageSSO.statusPageId?.toString()}/${statusPageSSO.id?.toString()}`,
        ),
      });

      return Response.redirect(req, res, samlRequestUrl);
    } catch (err) {
      return next(err);
    }
  },
);

router.post(
  "/status-page-idp-login/:statusPageId/:statusPageSsoId",
  async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
    try {
      const samlResponseBase64: string = req.body.SAMLResponse;

      const samlResponse: string = Buffer.from(
        samlResponseBase64,
        "base64",
      ).toString();

      const response: JSONObject =
        await xml2js.parseStringPromise(samlResponse);

      let issuerUrl: string = "";
      let email: Email | null = null;

      if (!req.params["statusPageId"]) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("Status Page ID not found"),
        );
      }

      if (!req.params["statusPageSsoId"]) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("Status Page SSO ID not found"),
        );
      }

      const statusPageId: ObjectID = new ObjectID(req.params["statusPageId"]);

      const statusPageSSO: StatusPageSSO | null =
        await StatusPageSsoService.findOneBy({
          query: {
            statusPageId: statusPageId,
            _id: req.params["statusPageSsoId"],
            isEnabled: true,
          },
          select: {
            signOnURL: true,
            issuerURL: true,
            publicCertificate: true,
            projectId: true,
          },
          props: {
            isRoot: true,
          },
        });

      if (!statusPageSSO) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("SSO Config not found"),
        );
      }

      if (!statusPageSSO.projectId) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("SSO Config Project ID not found"),
        );
      }

      const projectId: ObjectID = statusPageSSO.projectId;

      // redirect to Identity Provider.

      if (!statusPageSSO.issuerURL) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("Issuer URL not found"),
        );
      }

      // redirect to Identity Provider.

      if (!statusPageSSO.signOnURL) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("Sign on URL not found"),
        );
      }

      if (!statusPageSSO.publicCertificate) {
        return Response.sendErrorResponse(
          req,
          res,
          new BadRequestException("Public Certificate not found"),
        );
      }

      try {
        SSOUtil.isPayloadValid(response);

        if (
          !SSOUtil.isSignatureValid(
            samlResponse,
            statusPageSSO.publicCertificate,
          )
        ) {
          return Response.sendErrorResponse(
            req,
            res,
            new BadRequestException("Signature is not valid"),
          );
        }

        issuerUrl = SSOUtil.getIssuer(response);

        email = SSOUtil.getEmail(response);
      } catch (err: unknown) {
        if (err instanceof Exception) {
          return Response.sendErrorResponse(req, res, err);
        }
        return Response.sendErrorResponse(req, res, new ServerException());
      }

      if (statusPageSSO.issuerURL.toString() !== issuerUrl) {
        return Response.sendErrorResponse(
          req,

          res,
          new BadRequestException("Issuer URL does not match"),
        );
      }

      // Check if he already belongs to the project, If he does - then log in.

      let alreadySavedUser: StatusPagePrivateUser | null =
        await StatusPagePrivateUserService.findOneBy({
          query: { email: email, statusPageId: statusPageId },
          select: {
            _id: true,
            email: true,
            statusPageId: true,
            projectId: true,
          },
          props: {
            isRoot: true,
          },
        });

      if (!alreadySavedUser) {
        /// Create a user.

        alreadySavedUser = new StatusPagePrivateUser();
        alreadySavedUser.projectId = projectId;
        alreadySavedUser.statusPageId = statusPageId;
        alreadySavedUser.email = email;
        alreadySavedUser.password = new HashedString(
          ObjectID.generate().toString(),
        );
        alreadySavedUser.isSsoUser = true;

        alreadySavedUser = await StatusPagePrivateUserService.create({
          data: alreadySavedUser,
          props: { isRoot: true },
        });
      }

      const token: string = JSONWebToken.sign({
        data: alreadySavedUser,
        expiresInSeconds: OneUptimeDate.getSecondsInDays(
          new PositiveNumber(30),
        ),
      });

      CookieUtil.setCookie(
        res,
        CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
        token,

        {
          httpOnly: true,
          maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
        },
      );

      // get status page URL.
      const statusPageURL: string =
        await StatusPageService.getStatusPageFirstURL(statusPageId);

      return Response.redirect(
        req,
        res,
        URL.fromString(statusPageURL).addQueryParams({
          token: token,
        }),
      );
    } catch (err) {
      logger.error(err);
      Response.sendErrorResponse(req, res, new ServerException());
    }
  },
);

export default router;
