package foam.nanos.auth;

import foam.core.FObject;
import foam.core.X;
import foam.dao.DAO;
import foam.dao.ProxyDAO;
import foam.dao.Sink;
import foam.mlang.order.Comparator;
import foam.mlang.predicate.Predicate;
import foam.nanos.auth.AuthService;
import foam.nanos.auth.User;
import java.security.AccessControlException;
import net.nanopay.model.Account;

import static foam.mlang.MLang.AND;
import static foam.mlang.MLang.EQ;

/**
 * Authenticate UserDAO
 *
 * Features:
 *  - restrict creation/updating of users
 *  - restrict group of users created/updated
 *  - restrict deletion of users
 *  - restrict selection of users
 *    - can only see self by default
 *    - can see users belonging to groups you can create
 *  - grant access to based on SPID
 *  - set SPID to same as user creating new user if they don't have the global
 *    spid property
 **/
public class AuthenticatedUserDAO
    extends ProxyDAO
{
  public final static String GLOBAL_USER_CREATE = "user.create.x";
  public final static String GLOBAL_USER_READ   = "user.read.x";
  public final static String GLOBAL_USER_UPDATE = "user.update.x";
  public final static String GLOBAL_USER_DELETE = "user.delete.x";

  public final static String GLOBAL_SPID_CREATE = "spid.create.x";

  public AuthenticatedUserDAO(X x, DAO delegate) {
    super(x, delegate);
  }

  @Override
  public FObject put_(X x, FObject obj) {
    User        user    = (User) x.get("user");
    AuthService auth    = (AuthService) x.get("auth");

    // if current user doesn't have permissions to create or update, force account's owner to be current user id
    if ( ! auth.check(x, GLOBAL_USER_CREATE) || ! auth.check(x, GLOBAL_USER_UPDATE) ) {
      // account.setId(user.getId());
    }
    return super.put_(x, obj);
  }

  @Override
  public FObject find_(X x, Object id) {
    User        user = (User) x.get("user");
    AuthService auth = (AuthService) x.get("auth");

    // check if logged in
    if ( user == null ) {
      throw new AccessControlException("User is not logged in");
    }

    // find user and check if current user has permission to read
    User result = (User) super.find_(x, id);
    if ( result != null && result.getId() != user.getId() &&
        ! auth.check(x, GLOBAL_USER_READ) &&
        ! auth.check(x, "spid.read." + result.getSpid()) ) {
      return null;
    }

    return result;
  }

  @Override
  public Sink select_(X x, Sink sink, long skip, long limit, Comparator order, Predicate predicate) {
    User        user = (User) x.get("user");
    AuthService auth = (AuthService) x.get("auth");

    // check if logged in
    if ( user == null ) {
      throw new AccessControlException("User is not logged in");
    }

    DAO dao;
    if ( auth.check(x, GLOBAL_USER_READ) ) {
      // get all users in system
      dao = getDelegate();
    } else if ( auth.check(x, "spid.read." + user.getSpid()) ) {
      // get all users under service provider
      dao = getDelegate().where(EQ(User.SPID, user.getSpid()));
    } else {
      // only get authenticated user
      dao = getDelegate().where(EQ(User.ID, user.getId()));
    }
    return dao.select_(x, sink, skip, limit, order, predicate);
  }

  @Override
  public FObject remove_(X x, FObject obj) {
    User        user    = (User) x.get("user");
    AuthService auth    = (AuthService) x.get("auth");

    // check if logged in
    if ( user == null ) {
      throw new AccessControlException("User is not logged in");
    }

    // check if current user has permission to delete
    User remove = (User) obj;
    if ( remove != null && remove.getId() != user.getId() &&
        ! auth.check(x, GLOBAL_USER_DELETE) &&
        ! auth.check(x, "spid.delete." + remove.getSpid()) ) {
      throw new RuntimeException("Unable to delete user");
    }

    return super.remove_(x, obj);
  }

  @Override
  public void removeAll_(X x, long skip, long limit, Comparator order, Predicate predicate) {
    User        user = (User) x.get("user");
    AuthService auth = (AuthService) x.get("auth");

    // check if logged in
    if ( user == null ) {
      throw new AccessControlException("User is not logged in");
    }

    DAO dao;
    if ( auth.check(x, GLOBAL_USER_DELETE) ) {
      // delete all users in system
      dao = getDelegate();
    } else if ( auth.check(x, "spid.delete." + user.getSpid()) ) {
      // delete users under service provider
      dao = getDelegate().where(EQ(User.SPID, user.getSpid()));
    } else {
      // only delete authenticated user
      dao = getDelegate().where(EQ(User.ID, user.getId()));
    }

    dao.removeAll_(x, skip, limit, order, predicate);
  }
}

@@ -1497,125 +1497,99 @@
     
-// if current user doesn't have permissions to create or update, force account's owner to be current user id
+User toPut = (User) obj;%0A    if ( toPut != null && toPut.getId() != user.getId() &&
 %0A    
-if (
+   
  ! a
@@ -1625,10 +1625,18 @@
 TE) 
-%7C%7C
+&&%0A       
  ! a
@@ -1659,46 +1659,168 @@
 BAL_
-USER_UPD
+SPID_CRE
 ATE) 
-) %7B
+&&
 %0A      
-// accoun
+  ! auth.check(x, %22spid.create.%22 + toPut.getSpid()) ) %7B%0A      throw new RuntimeException(%22Unable to update user%22);%0A    %7D%0A%0A    toPu
 t.set
-I
+Spi
 d(us
@@ -1829,21 +1829,17 @@
 .get
-I
+Spi
 d());%0A
-    %7D%0A
     
@@ -1855,27 +1855,29 @@
 per.put_(x, 
-obj
+toPut
 );%0A  %7D%0A%0A  @O
@@ -3637,17 +3637,19 @@
    User 
-r
+toR
 emove = 
@@ -3669,17 +3669,19 @@
    if ( 
-r
+toR
 emove !=
@@ -3689,17 +3689,19 @@
 null && 
-r
+toR
 emove.ge
@@ -3813,17 +3813,19 @@
 ete.%22 + 
-r
+toR
 emove.ge

Finished put_ call