"use strict";

const express = require("express"),
  config = require("config"),
  LdapAuth = require("ldapauth-fork"),
  { sendVerifyMail, sendPasswordChangedMail } = require("../lib/mail"),
  crypto = require("crypto");

const router = express.Router();

let userDB;

let ldap;
if (config.has("ldap.url")) {
  ldap = new LdapAuth(config.get("ldap"));
} else {
  ldap = null;
}

require("../db/users").then(function(db) {
  userDB = db;
});

router.get("/", function(req, res/*, next*/) {
  console.log("/users/");
  return getSessionUser(req).then(function(user) {
    return res.status(200).send(user);
  }).catch(function(error) {
    console.log("User not logged in: " + error);
    return res.status(200).send({});
  });
});

function ldapPromise(username, password) {
  if (!ldap) {
    return Promise.reject("LDAP not being used");
  }
  return new Promise(function(resolve, reject) {
    ldap.authenticate(username.replace(/@.*$/, ""), password, function(error, user) {
      if (error) {
        return reject(error);
      }
      return resolve(user);
    });
  });
}

const ldapJS = require("ldapjs"),
  ldapConfig = config.get("ldap");

  
const ldapSetPassword = function(username, password) {
  const client = ldapJS.createClient({
    url: ldapConfig.url
  });

  return new Promise(function(resolve, reject) {
    client.bind(ldapConfig.bindDn, ldapConfig.bindCredentials, function(err) {
      if (err) {
        return reject("Error binding to LDAP: " + err);
      }

      var change = new ldapJS.Change({
        operation: "replace",
        modification: {
          userPassword : password,
        }
      });

      client.modify("uid=" + username + ",ou=people," + ldapConfig.searchBase, change, function(err) {
        if (err) {
          return reject("Error changing password: " + err);
        }
        return resolve();
      });
    });
  }).catch(function(error) {
    console.error(error);
  }).then(function() {
    client.unbind(function(err) {
      if (err) {
        console.error("Error unbinding: " + err);
      }
    });
  });
};

router.put("/password", function(req, res) {
  console.log("/users/password");

  const changes = {
    currentPassword: req.query.c || req.body.c,
    newPassword: req.query.n || req.body.n
  };

  if (!changes.currentPassword || !changes.newPassword) {
    return res.status(400).send("Missing current password and/or new password.");
  }

  if (changes.currentPassword == changes.newPassword) {
    return res.status(400).send("Attempt to set new password to current password.");
  }

  return getSessionUser(req).then(function(user) {
    if (req.session.userId == "LDAP") {
      return ldapPromise(user.username, changes.currentPassword).then(function() {
        return user;
      }).catch(function() {
        return null;
      });
    }
    
    /* Not an LDAP user, so query in the DB */    
    return userDB.sequelize.query("SELECT id FROM users " +
      "WHERE uid=:username AND password=:password", {
      replacements: {
        username: user.username,
        password: crypto.createHash('sha256').update(changes.currentPassword).digest('base64')
      },
      type: userDB.Sequelize.QueryTypes.SELECT,
      raw: true
    }).then(function(users) {
      if (users.length != 1) {
        return null;
      }
      return user;
    });
  }).then(function(user) {
    if (!user) {
      console.log("Invalid password");
      /* Invalid password */
      res.status(401).send("Invalid password");
      return null;
    }

    let updatePromise;
    if (req.session.userId == "LDAP") {
      updatePromise = ldapSetPassword(user.username, changes.newPassword);
    } else {
      updatePromise = userDB.sequelize.query("UPDATE users SET password=:password WHERE uid=:username", {
        replacements: {
          username: user.username,
          password: crypto.createHash('sha256').update(changes.newPassword).digest('base64')
        }
      });
    }

    updatePromise.then(function() {
      console.log("Password changed for user " + user.username + " to '" + changes.newPassword + "'.");
      
      res.status(200).send(user);
      user.id = req.session.userId;
      return sendPasswordChangedMail(userDB, req, user);
    });
  });
});

router.post("/create", function(req, res) {
  console.log("/users/create");

  const user = {
    uid: req.query.m || req.body.m,
    displayName: req.query.n || req.body.n || "",
    password: req.query.p || req.body.p || "",
    mail: req.query.m || req.body.m,
    notes: req.query.w || req.body.w || ""
  };

  if (!user.uid || !user.password || !user.displayName || !user.notes) {
    return res.status(400).send("Missing email address, password, name, and/or who you know.");
  }

  user.password = crypto.createHash('sha256').update(user.password).digest('base64');

  return userDB.sequelize.query("SELECT * FROM users WHERE uid=:uid", {
    replacements: user,
    type: userDB.Sequelize.QueryTypes.SELECT,
    raw: true
  }).then(function(results) {
    if (results.length != 0) {
      return res.status(400).send("Email address already used.");
    }

    let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    if (!re.exec(user.mail)) {
      console.log("Invalid email address: " + user.mail);
      throw "Invalid email address.";
    }
  }).then(function() {
    return userDB.sequelize.query("INSERT INTO users " + 
      "(uid,displayName,password,mail,memberSince,authenticated,notes) " +
      "VALUES(:uid,:displayName,:password,:mail,CURRENT_TIMESTAMP,0,:notes)", {
      replacements: user
    }).spread(function(results, metadata) {
      req.session.userId = metadata.lastID;
    }).then(function() {
      return getSessionUser(req).then(function(user) {
        res.status(200).send(user);
        user.id = req.session.userId;
        return sendVerifyMail(userDB, req, user);
      });
    }).catch(function(error) {
      console.log("Error creating account: ", error);
      return res.status(401).send(error);
    });
  });
});

const getSessionUser = function(req) {
  return Promise.resolve().then(function() {
    if (!req.session || !req.session.userId) {
      throw "Unauthorized. You must be logged in.";
    }

    if (req.session.userId == "LDAP") {
      if (req.session.ldapUser) {
        return req.session.ldapUser;
      }
      req.session.userId = null;
      req.session.ldapUser = null;
      throw "Invalid LDAP session";
    }
  
    let query = "SELECT " +
        "uid AS username,displayName,mailVerified,authenticated,memberSince AS name,mail " +
        "FROM users WHERE id=:id";
    return userDB.sequelize.query(query, {
      replacements: {
        id: req.session.userId
      },
      type: userDB.Sequelize.QueryTypes.SELECT,
      raw: true
    }).then(function(results) {
      if (results.length != 1) {
        throw "Invalid account.";
      }

      let user = results[0];

      if (!user.mailVerified) {
        user.restriction = user.restriction || "Email address not verified.";
        return user;
      }

      if (!user.authenticated) {
        user.restriction = user.restriction || "Accout not authorized.";
        return user;
      }

      return user;
    });
  }).then(function(user) {
    req.user = user;

    /* If the user already has a restriction, or there are no album user restrictions,
     * return the user to the next promise */
    if (user.restriction || !config.has("restrictions")) {
      return user;
    }

    let allowed = config.get("restrictions");
    if (!Array.isArray(allowed)) {
      allowed = [ allowed ];
    }
    for (let i = 0; i < allowed.length; i++) {
      if (allowed[i] == user.username) {
        return user;
      }
    }
    console.log("Unauthorized (logged in) access by user: " + user.username);
    user.restriction = "Unauthorized access attempt to restricted album.";

    return user;
  }).then(function(user) {
    /* If there are maintainers on this album, check if this user is a maintainer */
    if (config.has("maintainers")) {
      let maintainers = config.get("maintainers");
      if (maintainers.indexOf(user.username) != -1) {
        user.maintainer = true;
        if (user.restriction) {
          console.warn("User " + user.username + " is a maintainer AND has a restriction which will be ignored: " + user.restriction);
          delete user.restriction;
        }
      }
    }

    return user;
  }).then(function(user) {
    /* Strip out any fields that shouldn't be there. The allowed fields are: */
    let allowed = [
      "maintainer", "username", "displayName", "mailVerified", "authenticated", "name", "mail", "restriction"
    ];
    for (let field in user) {
      if (allowed.indexOf(field) == -1) {
        delete user[field];
      }
    }
    return user;
  });
}

router.post("/login", function(req, res) {
  console.log("/users/login");

  let username = req.query.u || req.body.u || "",
    password = req.query.p || req.body.p || "";

  console.log("Login attempt");

  if (!username || !password) {
    return res.status(400).send("Missing username and/or password");
  }

  /* We use LDAP as the primary authenticator; if the user is not
   * found there, we look the user up in the site-specific user database */
   
  return ldapPromise(username, password).then(function(ldap) {
    let user = {};
    user.id = "LDAP";
    user.displayName = ldap.displayName;
    user.username = ldap.uid;
    user.mail = ldap.mail;
    user.authenticated = 1;
    user.mailVerified = 1;
    req.session.userId = "LDAP";
    req.session.ldapUser = user;
    return user;
  }).catch(function() {
    console.log("User not found in LDAP. Looking up in DB.");
    let query = "SELECT " +
        "id,mailVerified,authenticated,uid AS username,displayName AS name,mail " +
        "FROM users WHERE uid=:username AND password=:password";
    return userDB.sequelize.query(query, {
      replacements: {
        username: username,
        password: crypto.createHash('sha256').update(password).digest('base64')
      },
      type: userDB.Sequelize.QueryTypes.SELECT
    }).then(function(users) {
      if (users.length != 1) {
        return null;
      }
      let user = users[0];
      req.session.userId = user.id;
      return user;
    });
  }).then(function(user) {
    if (!user) {
      console.log(username + " not found (or invalid password.)");
      req.session.userId = null;
      return res.status(401).send("Invalid login credentials");      
    }

    let message = "Logged in as " + user.username + " (" + user.id + ")";
    if (!user.mailVerified) {
      console.log(message + ", who is not verified email.");
    } else if (!user.authenticated) {
      console.log(message + ", who is not authenticated.");
    } else {
      console.log(message);
    }

    return getSessionUser(req).then(function(user) {
      return res.status(200).send(user);
    });
  }).catch(function(error) {
    console.log(error);
    return res.status(403).send(error);
  });
});

router.get("/logout", function(req, res) {
  console.log("/users/logout");

  if (req.session && req.session.userId) {
    if (req.session.userId == "LDAP") {
      req.session.ldapUser = null;
    }
    req.session.userId = null;
  }
  res.status(200).send({});
});

module.exports = {
  router,
  getSessionUser
};
