import Stacker from "../middlewares/stacker.js";
import { GetUserMiddleware } from "../middlewares/user.js";
import { Request, Response } from "express";
import Client from "../../models/client.js";
import Logging from "@hibas123/nodelogging";
import Permission, { IPermission } from "../../models/permissions.js";
import ClientCode from "../../models/client_code.js";
import moment = require("moment");
import { randomBytes } from "crypto";
// import { ObjectId } from "bson";
import Grant, { IGrant } from "../../models/grants.js";
import GetAuthPage from "../../views/authorize.js";
import { ObjectId } from "mongodb";

// const AuthRoute = Stacker(GetUserMiddleware(true), async (req: Request, res: Response) => {
//    let { response_type, client_id, redirect_uri, scope, state, nored } = req.query;
//    const sendError = (type) => {
//       if (redirect_uri === "$local")
//          redirect_uri = "/code";
//       res.redirect(redirect_uri += `?error=${type}&state=${state}`);
//    }
//    /**
//     * error
//          REQUIRED.  A single ASCII [USASCII] error code from the
//          following:
//          invalid_request
//                The request is missing a required parameter, includes an
//                invalid parameter value, includes a parameter more than
//                once, or is otherwise malformed.
//          unauthorized_client
//                The client is not authorized to request an authorization
//                code using this method.
//          access_denied
//                The resource owner or authorization server denied the
//                request.
//     */
//    try {

//       if (response_type !== "code") {
//          return sendError("unsupported_response_type");
//       } else {
//          let client = await Client.findOne({ client_id: client_id })
//          if (!client) {
//             return sendError("unauthorized_client")
//          }

//          if (redirect_uri && client.redirect_url !== redirect_uri) {
//             Logging.log(redirect_uri, client.redirect_url);
//             return res.send("Invalid redirect_uri. Please check the integrity of the site requesting and contact the administrator of the page, you want to authorize!");
//          }

//          let permissions: IPermission[] = [];
//          if (scope) {
//             let perms = (<string>scope).split(";").filter(e => e !== "read_user").map(p => new ObjectId(p));
//             permissions = await Permission.find({ _id: { $in: perms } })

//             if (permissions.length != perms.length) {
//                return sendError("invalid_scope");
//             }
//          }

//          let code = ClientCode.new({
//             user: req.user._id,
//             client: client._id,
//             permissions: permissions.map(p => p._id),
//             validTill: moment().add(30, "minutes").toDate(),
//             code: randomBytes(16).toString("hex")
//          });
//          await ClientCode.save(code);

//          let redir = client.redirect_url === "$local" ? "/code" : client.redirect_url;

//          let ruri = redir + `?code=${code.code}&state=${state}`;
//          if (nored === "true") {
//             res.json({
//                redirect_uri: ruri
//             })
//          } else {
//             res.redirect(ruri);
//          }
//       }
//    } catch (err) {
//       Logging.error(err);
//       sendError("server_error")
//    }
// })

const GetAuthRoute = (view = false) =>
   Stacker(GetUserMiddleware(false), async (req: Request, res: Response) => {
      let {
         response_type,
         client_id,
         redirect_uri,
         scope = "",
         state,
         nored,
      } = req.query as { [key: string]: string };
      const sendError = (type) => {
         if (redirect_uri === "$local") redirect_uri = "/code";
         res.redirect(
            (redirect_uri += `?error=${type}${state ? "&state=" + state : ""}`)
         );
      };

      const scopes = scope.split(";").filter((e: string) => e !== "");

      Logging.debug("Scopes:", scope);

      try {
         if (response_type !== "code") {
            return sendError("unsupported_response_type");
         } else {
            let client = await Client.findOne({ client_id: client_id });
            if (!client) {
               return sendError("unauthorized_client");
            }

            if (redirect_uri && client.redirect_url !== redirect_uri) {
               Logging.log(redirect_uri, client.redirect_url);
               return res.send(
                  "Invalid redirect_uri. Please check the integrity of the site requesting and contact the administrator of the page, you want to authorize!"
               );
            }

            let permissions: IPermission[] = [];
            let proms: PromiseLike<void>[] = [];
            if (scopes) {
               for (let perm of scopes.filter((e) => e !== "read_user")) {
                  let oid = undefined;
                  try {
                     oid = new ObjectId(perm);
                  } catch (err) {
                     Logging.error(err);
                     continue;
                  }
                  proms.push(
                     Permission.findById(oid).then((p) => {
                        if (!p) return Promise.reject(new Error());
                        permissions.push(p);
                     })
                  );
               }
            }

            let err = undefined;
            await Promise.all(proms).catch((e) => {
               err = e;
            });

            if (err) {
               Logging.error(err);
               return sendError("invalid_scope");
            }

            let grant: IGrant | undefined = await Grant.findOne({
               client: client._id,
               user: req.user._id,
            });

            Logging.debug("Grant", grant, permissions);

            let missing_permissions: IPermission[] = [];

            if (grant) {
               missing_permissions = grant.permissions
                  .map((perm) => permissions.find((p) => p._id.equals(perm)))
                  .filter((e) => !!e);
            } else {
               missing_permissions = permissions;
            }

            let client_granted_perm = missing_permissions.filter(
               (e) => e.grant_type == "client"
            );
            if (client_granted_perm.length > 0) {
               return sendError("no_permission");
            }

            if (!grant && missing_permissions.length > 0) {
               await new Promise<void>((yes, no) =>
                  GetUserMiddleware(false, true)(
                     req,
                     res,
                     (err?: Error | string) => (err ? no(err) : yes())
                  )
               ); // Maybe unresolved when redirect is happening

               if (view) {
                  res.send(
                     GetAuthPage(
                        req.__,
                        client.name,
                        permissions.map((perm) => {
                           return {
                              name: perm.name,
                              description: perm.description,
                              logo: client.logo,
                           };
                        })
                     )
                  );
                  return;
               } else {
                  if ((req.body.allow = "true")) {
                     if (!grant)
                        grant = Grant.new({
                           client: client._id,
                           user: req.user._id,
                           permissions: [],
                        });

                     grant.permissions.push(
                        ...missing_permissions.map((e) => e._id)
                     );
                     await Grant.save(grant);
                  } else {
                     return sendError("access_denied");
                  }
               }
            }

            let code = ClientCode.new({
               user: req.user._id,
               client: client._id,
               permissions: permissions.map((p) => p._id),
               validTill: moment().add(30, "minutes").toDate(),
               code: randomBytes(16).toString("hex"),
            });
            await ClientCode.save(code);

            let redir =
               client.redirect_url === "$local" ? "/code" : client.redirect_url;
            let ruri =
               redir + `?code=${code.code}${state ? "&state=" + state : ""}`;
            if (nored === "true") {
               res.json({
                  redirect_uri: ruri,
               });
            } else {
               res.redirect(ruri);
            }
         }
      } catch (err) {
         Logging.error(err);
         sendError("server_error");
      }
   });

export default GetAuthRoute;
