const express = require('express');
const passport = require('passport');

const config = require('../config');
const AuthService = require('../services/auth');
const ForbiddenError = require('../services/notifications/errors/forbidden');
const EmailSender = require('../services/email');
const wrapAsync = require('../helpers').wrapAsync;
const {
  authLimiter: signinLimiter,
  signupLimiter,
  passwordResetLimiter,
} = require('../middlewares/rateLimiter');

const router = express.Router();

function safeParseUrl(value) {
  if (!value || typeof value !== 'string') {
    return null;
  }

  try {
    return new URL(value);
  } catch (error) {
    try {
      return new URL(`https://${value}`);
    } catch (innerError) {
      return null;
    }
  }
}

function getRequestHost(req) {
  const uiUrl = safeParseUrl(config.uiUrl);
  const fallbackHost = uiUrl
    ? uiUrl.origin
    : config.backUrl || 'http://localhost:3000';
  const origin = safeParseUrl(req.headers.origin);
  const referer = safeParseUrl(req.headers.referer);

  if (uiUrl && origin && origin.host === uiUrl.host) {
    return origin.origin;
  }

  if (uiUrl && referer && referer.host === uiUrl.host) {
    return referer.origin;
  }

  if (origin && !uiUrl) {
    return origin.origin;
  }

  if (referer && !uiUrl) {
    return referer.origin;
  }

  return fallbackHost;
}

/**
 *  @swagger
 *  components:
 *    schemas:
 *      Auth:
 *        type: object
 *        required:
 *          - email
 *          - password
 *        properties:
 *          email:
 *            type: string
 *            default: admin@flatlogic.com
 *            description: User email
 *          password:
 *            type: string
 *            default: password
 *            description: User password
 */

/**
 * @swagger
 * tags:
 *   name: Auth
 *   description: Authorization operations
 */

/**
 * @swagger
 *  /api/auth/signin/local:
 *    post:
 *      tags: [Auth]
 *      summary: Logs user into the system
 *      description: Logs user into the system
 *      requestBody:
 *        description: Set valid user email and password
 *        content:
 *          application/json:
 *            schema:
 *              $ref: "#/components/schemas/Auth"
 *      responses:
 *        200:
 *          description: Successful login
 *        400:
 *          description: Invalid username/password supplied
 *      x-codegen-request-body-name: body
 */

router.post(
  '/signin/local',
  signinLimiter,
  wrapAsync(async (req, res) => {
    const payload = await AuthService.signin(
      req.body.email,
      req.body.password,
      req,
    );
    res.status(200).send(payload);
  }),
);

/**
 * @swagger
 *  /api/auth/me:
 *    get:
 *      security:
 *        - bearerAuth: []
 *      tags: [Auth]
 *      summary: Get current authorized user info
 *      description: Get current authorized user info
 *      responses:
 *        200:
 *          description: Successful retrieval of current authorized user data
 *        400:
 *          description: Invalid username/password supplied
 *      x-codegen-request-body-name: body
 */

router.get(
  '/me',
  passport.authenticate('jwt', { session: false }),
  (req, res) => {
    if (!req.currentUser || !req.currentUser.id) {
      throw new ForbiddenError();
    }

    const payload = req.currentUser;
    delete payload.password;
    res.status(200).send(payload);
  },
);

router.put(
  '/password-reset',
  wrapAsync(async (req, res) => {
    const payload = await AuthService.passwordReset(
      req.body.token,
      req.body.password,
      req,
    );
    res.status(200).send(payload);
  }),
);

router.put(
  '/password-update',
  passport.authenticate('jwt', { session: false }),
  wrapAsync(async (req, res) => {
    const payload = await AuthService.passwordUpdate(
      req.body.currentPassword,
      req.body.newPassword,
      req,
    );
    res.status(200).send(payload);
  }),
);

router.post(
  '/send-email-address-verification-email',
  passport.authenticate('jwt', { session: false }),
  wrapAsync(async (req, res) => {
    if (!req.currentUser) {
      throw new ForbiddenError();
    }

    await AuthService.sendEmailAddressVerificationEmail(req.currentUser.email);
    const payload = true;
    res.status(200).send(payload);
  }),
);

router.post(
  '/send-password-reset-email',
  passwordResetLimiter,
  wrapAsync(async (req, res) => {
    const host = getRequestHost(req);
    await AuthService.sendPasswordResetEmail(req.body.email, 'register', host);
    const payload = true;
    res.status(200).send(payload);
  }),
);

/**
 * @swagger
 *  /api/auth/signup:
 *    post:
 *      tags: [Auth]
 *      summary: Register new user into the system
 *      description: Register new user into the system
 *      requestBody:
 *        description: Set valid user email and password
 *        content:
 *          application/json:
 *            schema:
 *              $ref: "#/components/schemas/Auth"
 *      responses:
 *        200:
 *          description: New user successfully signed up
 *        400:
 *          description: Invalid username/password supplied
 *        500:
 *          description: Some server error
 *      x-codegen-request-body-name: body
 */

router.post(
  '/signup',
  signupLimiter,
  wrapAsync(async (req, res) => {
    const host = getRequestHost(req);
    const payload = await AuthService.signup(
      req.body.email,
      req.body.password,

      req,
      host,
    );
    res.status(200).send(payload);
  }),
);

router.put(
  '/profile',
  passport.authenticate('jwt', { session: false }),
  wrapAsync(async (req, res) => {
    if (!req.currentUser || !req.currentUser.id) {
      throw new ForbiddenError();
    }

    await AuthService.updateProfile(req.body.profile, req.currentUser);
    const payload = true;
    res.status(200).send(payload);
  }),
);

router.put(
  '/verify-email',
  wrapAsync(async (req, res) => {
    const payload = await AuthService.verifyEmail(
      req.body.token,
      req,
      req.headers.referer,
    );
    res.status(200).send(payload);
  }),
);

router.get('/email-configured', (req, res) => {
  const payload = EmailSender.isConfigured;
  res.status(200).send(payload);
});

router.get('/signin/google', (req, res, next) => {
  passport.authenticate('google', {
    scope: ['profile', 'email'],
    state: req.query.app,
  })(req, res, next);
});

router.get(
  '/signin/google/callback',
  passport.authenticate('google', {
    failureRedirect: '/login',
    session: false,
  }),

  function (req, res) {
    socialRedirect(res, req.query.state, req.user.token, config);
  },
);

router.get('/signin/microsoft', (req, res, next) => {
  passport.authenticate('microsoft', {
    scope: ['https://graph.microsoft.com/user.read openid'],
    state: req.query.app,
  })(req, res, next);
});

router.get(
  '/signin/microsoft/callback',
  passport.authenticate('microsoft', {
    failureRedirect: '/login',
    session: false,
  }),
  function (req, res) {
    socialRedirect(res, req.query.state, req.user.token, config);
  },
);

router.use('/', require('../helpers').commonErrorHandler);

function socialRedirect(res, state, token, config) {
  res.redirect(config.uiUrl + '/login?token=' + token);
}

module.exports = router;
