var assert = require('assert');
var loopback = require('../index');
var acl = require('../lib/models/acl');
var Scope = acl.Scope;
var ACL = acl.ACL;
var role = require('../lib/models/role');
var Role = role.Role;
var RoleMapping = role.RoleMapping;
var User = loopback.User;
var testModel;

function checkResult(err, result) {
  // console.log(err, result);
  assert(!err);
}

describe('security scopes', function () {

  beforeEach(function() {
    var ds = this.ds = loopback.createDataSource({connector: loopback.Memory});
    testModel = loopback.Model.extend('testModel');
    ACL.attachTo(ds);
    Role.attachTo(ds);
    RoleMapping.attachTo(ds);
    User.attachTo(ds);
    Scope.attachTo(ds);
    testModel.attachTo(ds);
  });

  it("should allow access to models for the given scope by wildcard", function () {
    Scope.create({name: 'userScope', description: 'access user information'}, function (err, scope) {
      ACL.create({principalType: ACL.SCOPE, principalId: scope.id, model: 'User', property: ACL.ALL,
          accessType: ACL.ALL, permission: ACL.ALLOW},
        function (err, resource) {
        Scope.checkPermission('userScope', 'User', ACL.ALL, ACL.ALL, checkResult);
        Scope.checkPermission('userScope', 'User', 'name', ACL.ALL, checkResult);
        Scope.checkPermission('userScope', 'User', 'name', ACL.READ, checkResult);
      });
    });

  });

  it("should allow access to models for the given scope", function () {
    Scope.create({name: 'testModelScope', description: 'access testModel information'}, function (err, scope) {
      ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
          model: 'testModel', property: 'name', accessType: ACL.READ, permission: ACL.ALLOW},
        function (err, resource) {
          ACL.create({principalType: ACL.SCOPE, principalId: scope.id,
              model: 'testModel', property: 'name', accessType: ACL.WRITE, permission: ACL.DENY},
            function (err, resource) {
              // console.log(resource);
              Scope.checkPermission('testModelScope', 'testModel', ACL.ALL, ACL.ALL, function (err, perm) {
                assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
              });
              Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.ALL, function (err, perm) {
                assert(perm.permission === ACL.DENY); // because name.WRITE == DENY
              });
              Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.READ, function (err, perm) {
                assert(perm.permission === ACL.ALLOW);
              });
              Scope.checkPermission('testModelScope', 'testModel', 'name', ACL.WRITE, function (err, perm) {
                assert(perm.permission === ACL.DENY);
              });
            });
        });
    });

  });

});

describe('security ACLs', function () {

  it("should allow access to models for the given principal by wildcard", function () {
    ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
      accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) {

      ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'User', property: ACL.ALL,
        accessType: ACL.READ, permission: ACL.DENY}, function (err, acl) {

        ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.READ, function (err, perm) {
          assert(perm.permission === ACL.DENY);
        });

        ACL.checkPermission(ACL.USER, 'u001', 'User', 'name', ACL.ALL, function (err, perm) {
          assert(perm.permission === ACL.DENY);
        });

      });

    });

  });

  it("should allow access to models by exception", function () {
    ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
      accessType: ACL.ALL, permission: ACL.DENY}, function (err, acl) {

      ACL.create({principalType: ACL.USER, principalId: 'u001', model: 'testModel', property: ACL.ALL,
        accessType: ACL.READ, permission: ACL.ALLOW}, function (err, acl) {

        ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.READ, function (err, perm) {
          assert(perm.permission === ACL.ALLOW);
        });

        ACL.checkPermission(ACL.USER, 'u001', 'testModel', ACL.ALL, ACL.READ, function (err, perm) {
          assert(perm.permission === ACL.ALLOW);
        });

        ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.WRITE, function (err, perm) {
          assert(perm.permission === ACL.DENY);
        });

        ACL.checkPermission(ACL.USER, 'u001', 'testModel', 'name', ACL.ALL, function (err, perm) {
          assert(perm.permission === ACL.DENY);
        });

      });

    });

  });

  it("should honor defaultPermission from the model", function () {
    var ds = this.ds;
    var Customer = ds.createModel('Customer', {
      name: {
        type: String,
        acls: [
          {principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
          {principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
        ]
      }
    }, {
      acls: [
        {principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
      ]
    });

    Customer.settings.defaultPermission = ACL.DENY;

    ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, function (err, perm) {
      assert(perm.permission === ACL.DENY);
    });

    ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, function (err, perm) {
      assert(perm.permission === ACL.ALLOW);
    });

    ACL.checkPermission(ACL.USER, 'u002', 'Customer', 'name', ACL.WRITE, function (err, perm) {
      assert(perm.permission === ACL.DENY);
    });

  });

  it("should honor static ACLs from the model", function () {
    var ds = this.ds;
    var Customer = ds.createModel('Customer', {
      name: {
        type: String,
        acls: [
          {principalType: ACL.USER, principalId: 'u001', accessType: ACL.WRITE, permission: ACL.DENY},
          {principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
        ]
      }
    }, {
      acls: [
        {principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
      ]
    });

    /*
     Customer.settings.acls = [
     {principalType: ACL.USER, principalId: 'u001', accessType: ACL.ALL, permission: ACL.ALLOW}
     ];
     */

    ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.WRITE, function (err, perm) {
      assert(perm.permission === ACL.DENY);
    });

    ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.READ, function (err, perm) {
      assert(perm.permission === ACL.ALLOW);
    });

    ACL.checkPermission(ACL.USER, 'u001', 'Customer', 'name', ACL.ALL, function (err, perm) {
      assert(perm.permission === ACL.ALLOW);
    });

  });

  it("should check access against LDL, ACL, and Role", function () {
    // var log = console.log;
    var log = function() {};
    var ds = this.ds;

    // Create
    User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) {

      log('User: ', user.toObject());

      var userId = user.id;

      // Define a model with static ACLs
      var Customer = ds.createModel('Customer', {
        name: {
          type: String,
          acls: [
            {principalType: ACL.USER, principalId: userId, accessType: ACL.WRITE, permission: ACL.DENY},
            {principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW}
          ]
        }
      }, {
        acls: [
          {principalType: ACL.USER, principalId: userId, accessType: ACL.ALL, permission: ACL.ALLOW}
        ],
        defaultPermission: 'DENY'
      });

      ACL.create({principalType: ACL.USER, principalId: userId, model: 'Customer', property: ACL.ALL,
        accessType: ACL.ALL, permission: ACL.ALLOW}, function (err, acl) {

        log('ACL 1: ', acl.toObject());

        Role.create({name: 'MyRole'}, function (err, myRole) {
          log('Role: ', myRole.toObject());

          myRole.principals.create({principalType: RoleMapping.USER, principalId: userId}, function (err, p) {

            log('Principal added to role: ', p.toObject());

            ACL.create({principalType: ACL.ROLE, principalId: 'MyRole', model: 'Customer', property: ACL.ALL,
              accessType: ACL.READ, permission: ACL.DENY}, function (err, acl) {

              log('ACL 2: ', acl.toObject());

              ACL.checkAccess({
                principals: [
                  {type: ACL.USER, id: userId}
                ],
                model: 'Customer',
                property: 'name',
                accessType: ACL.READ
              }, function(err, access) {
                assert(!err && access.permission === ACL.ALLOW);
              });

              ACL.checkAccess({
                principals: [
                  {type: ACL.ROLE, id: Role.EVERYONE}
                ],
                model: 'Customer',
                property: 'name',
                accessType: ACL.READ
              }, function(err, access) {
                assert(!err && access.permission === ACL.DENY);
              });

            });
          });
        });
      });
    });
  });

});