/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.oak.security.authorization.permission;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import javax.jcr.security.AccessControlList;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.Privilege;

import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
import org.apache.jackrabbit.oak.AbstractSecurityTest;
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.plugins.tree.TreeUtil;
import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
import org.apache.jackrabbit.oak.spi.mount.Mounts;
import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration;
import org.apache.jackrabbit.oak.spi.security.authorization.permission.PermissionProvider;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
import org.apache.jackrabbit.oak.spi.security.principal.EveryonePrincipal;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

public class MountPermissionStoreTest extends AbstractSecurityTest {

    private static final String TEST_NAME = "MultiplexingProviderTest";
    private static final String TEST_PATH = "/" + TEST_NAME;
    private static final String CONTENT_NAME = "content";
    private static final String CONTENT_PATH = TEST_PATH + "/" + CONTENT_NAME;

    private MountInfoProvider mountInfoProvider = Mounts.newBuilder().mount("testMount", TEST_PATH).build();

    private AuthorizationConfiguration config;
    private PermissionStore permissionStore;

    @Override
    @Before
    public void before() throws Exception {
        super.before();

        Tree rootNode = root.getTree("/");
        Tree test = TreeUtil.addChild(rootNode, TEST_NAME, JcrConstants.NT_UNSTRUCTURED);
        Tree content = TreeUtil.addChild(test, CONTENT_NAME, JcrConstants.NT_UNSTRUCTURED);
        Tree child = TreeUtil.addChild(content, "child", JcrConstants.NT_UNSTRUCTURED);

        AccessControlManager acMgr = getAccessControlManager(root);
        Privilege[] privileges = AccessControlUtils.privilegesFromNames(acMgr, PrivilegeConstants.JCR_READ);

        AccessControlList acl = AccessControlUtils.getAccessControlList(acMgr, content.getPath());
        assertNotNull(acl);
        acl.addAccessControlEntry(EveryonePrincipal.getInstance(), privileges);
        acMgr.setPolicy(content.getPath(), acl);

        AccessControlList acl2 = AccessControlUtils.getAccessControlList(acMgr, child.getPath());
        assertNotNull(acl2);
        acl2.addAccessControlEntry(EveryonePrincipal.getInstance(), privileges);
        acMgr.setPolicy(child.getPath(), acl2);
        root.commit();

        String wspName = adminSession.getWorkspaceName();
        PermissionProvider pp = config.getPermissionProvider(root, wspName, Set.of(EveryonePrincipal.getInstance()));
        assertTrue(pp instanceof MountPermissionProvider);
        permissionStore = ((MountPermissionProvider) pp).getPermissionStore(root, wspName, RestrictionProvider.EMPTY);
    }

    @Override
    @After
    public void after() throws Exception {
        try {
            root.refresh();
            Tree test = root.getTree(TEST_PATH);
            if (test.exists()) {
                test.remove();
            }
            root.commit();
        } finally {
            super.after();
        }
    }

    @Override
    protected SecurityProvider initSecurityProvider() {
        SecurityProvider sp = super.initSecurityProvider();
        config = MountUtils.bindMountInfoProvider(sp, mountInfoProvider);
        return sp;
    }

    @Test
    public void testLoadByAccessControlledPath() {
        Collection<PermissionEntry> entries = permissionStore.load(EveryonePrincipal.NAME, CONTENT_PATH);
        assertNotNull(entries);
        assertEquals(1, entries.size());
    }

    @Test
    public void testLoadByNonAccessControlledPath() {
        Collection<PermissionEntry> entries = permissionStore.load(EveryonePrincipal.NAME, TEST_PATH);
        assertNull(entries);
    }

    @Test
    public void testLoadByPrincipalNameWithEntries() {
        PrincipalPermissionEntries ppe = permissionStore.load(EveryonePrincipal.NAME);
        assertNotNull(ppe);
        assertTrue(ppe.isFullyLoaded());
        assertEquals(2, ppe.getSize());
    }

    @Test
    public void testLoadByUnknownPrincipalName() {
        PrincipalPermissionEntries ppe = permissionStore.load("unknown");
        assertNotNull(ppe);
        assertTrue(ppe.isFullyLoaded());
        assertEquals(0, ppe.getSize());
    }

    @Test
    public void testGetNumEntries() {
        assertEquals(2, permissionStore.getNumEntries(EveryonePrincipal.NAME, 10).size);
    }

    @Test
    public void testGetNumEntriesMaxReachedExact() throws Exception {
        PermissionStoreImpl mock = insertMockStore();
        when(mock.getNumEntries(anyString(), anyLong())).thenReturn(NumEntries.valueOf(2, true));

        NumEntries ne = permissionStore.getNumEntries(EveryonePrincipal.NAME, 10);
        assertEquals(NumEntries.valueOf(4, true), ne);

        ne = permissionStore.getNumEntries(EveryonePrincipal.NAME, 2);
        assertEquals(NumEntries.valueOf(4, true), ne);
    }

    @Test
    public void testGetNumEntriesMaxReachedNotExact() throws Exception {
        PermissionStoreImpl mock = insertMockStore();
        when(mock.getNumEntries(anyString(), anyLong())).thenReturn(NumEntries.valueOf(2, false));

        NumEntries ne = permissionStore.getNumEntries(EveryonePrincipal.NAME, 10);
        assertEquals(NumEntries.valueOf(4, false), ne);

        ne = permissionStore.getNumEntries(EveryonePrincipal.NAME, 2);
        assertEquals(NumEntries.valueOf(2, false), ne);
    }

    @Test
    public void testGetNumEntriesUnknownPrincipalName() {
        assertEquals(0, permissionStore.getNumEntries("unknown", 10).size);
    }

    @Test
    public void testFlush() throws Exception {
        PermissionStoreImpl mock = insertMockStore();

        permissionStore.flush(root);

        Mockito.verify(mock, Mockito.times(1)).flush(root);
    }

    private PermissionStoreImpl insertMockStore() throws Exception {
        Field f = Class.forName("org.apache.jackrabbit.oak.security.authorization.permission.MountPermissionProvider$MountPermissionStore").getDeclaredField("stores");
        f.setAccessible(true);

        PermissionStoreImpl mock = Mockito.mock(PermissionStoreImpl.class);
        List<PermissionStoreImpl> stores = (List<PermissionStoreImpl>) f.get(permissionStore);
        stores.add(0, mock);
        return mock;
    }
}