/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed 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.uberfire.backend.server;

import java.util.Set;
import javax.servlet.http.HttpSession;

import org.jboss.errai.bus.client.api.QueueSession;
import org.jboss.errai.bus.client.api.base.MessageBuilder;
import org.jboss.errai.bus.client.api.messaging.Message;
import org.jboss.errai.bus.server.api.RpcContext;
import org.jboss.errai.security.shared.api.identity.User;
import org.jboss.errai.security.shared.api.identity.UserImpl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.uberfire.backend.vfs.Path;
import org.uberfire.backend.vfs.PathFactory;
import org.uberfire.backend.vfs.impl.LockInfo;
import org.uberfire.backend.vfs.impl.LockResult;
import org.uberfire.io.IOService;
import org.uberfire.java.nio.file.FileSystem;
import org.uberfire.java.nio.file.NoSuchFileException;
import org.uberfire.rpc.SessionInfo;

import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class VFSLockServiceTest {

    @InjectMocks
    private VFSLockServiceImpl lockService;

    @Mock
    private IOService ioService;

    @Mock
    private FileSystem fileSystem;

    @Mock
    private SessionInfo sessionInfo;

    @Mock
    private QueueSession queueSession;

    @Mock
    private HttpSession httpSession;

    private Path path = PathFactory.newPath("file-to-lock.txt",
                                            "default://file-to-lock.txt");

    @Before
    public void setup() {
        setupRpcContext();

        User testUser = new UserImpl("testUser");
        when(sessionInfo.getIdentity()).thenReturn(testUser);
        when(queueSession.getAttribute(HttpSession.class,
                                       HttpSession.class.getName())).thenReturn(httpSession);
    }

    @Test
    public void acquireLockSucceedsIfFileUnlocked() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(false);

        final LockResult result = lockService.acquireLock(path);
        assertTrue(result.isSuccess());
        assertEquals(path,
                     result.getLockInfo().getFile());
        assertEquals("testUser",
                     result.getLockInfo().lockedBy());
        assertTrue(result.getLockInfo().isLocked());
    }

    @Test
    public void acquireLockSucceedsIfLockOwned() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(true);
        when(ioService.readAllString(any(org.uberfire.java.nio.file.Path.class))).thenReturn("testUser");

        final LockResult result = lockService.acquireLock(path);
        assertTrue(result.isSuccess());
        assertEquals(path,
                     result.getLockInfo().getFile());
        assertEquals("testUser",
                     result.getLockInfo().lockedBy());
        assertTrue(result.getLockInfo().isLocked());
    }

    @Test
    public void acquireLockFailsIfFileLocked() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(true);
        when(ioService.readAllString(any(org.uberfire.java.nio.file.Path.class))).thenReturn("some-other-user");

        final LockResult result = lockService.acquireLock(path);
        assertFalse(result.isSuccess());
        assertEquals(path,
                     result.getLockInfo().getFile());
        assertEquals("some-other-user",
                     result.getLockInfo().lockedBy());
        assertTrue(result.getLockInfo().isLocked());
    }

    @Test
    public void acquireLockUpdatesSession() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(false);

        lockService.acquireLock(path);
        verify(httpSession).setAttribute(eq(VFSLockServiceImpl.LOCK_SESSION_ATTRIBUTE_NAME),
                                         any(Set.class));
    }

    @Test
    // Unfortunately, batching is required for ensuring writes are properly 
    // replicated in the cluster. This needs to addressed in a future version 
    // of UF: https://issues.jboss.org/browse/UF-242
    public void acquireLockUsesBatch() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(false);

        lockService.acquireLock(path);

        final InOrder inOrder = inOrder(ioService);
        inOrder.verify(ioService).startBatch(fileSystem);
        inOrder.verify(ioService).exists(any(org.uberfire.java.nio.file.Path.class));
        inOrder.verify(ioService).write(any(org.uberfire.java.nio.file.Path.class),
                                        any(String.class));
        inOrder.verify(ioService).endBatch();
    }

    @Test
    public void releaseLockSucceedsIfLockOwned() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(true);
        when(ioService.readAllString(any(org.uberfire.java.nio.file.Path.class))).thenReturn("testUser");

        final LockResult result = lockService.releaseLock(path);
        assertTrue(result.isSuccess());
        assertEquals(path,
                     result.getLockInfo().getFile());
        assertEquals(null,
                     result.getLockInfo().lockedBy());
        assertFalse(result.getLockInfo().isLocked());
    }

    @Test
    public void releaseLockFailsIfLockNotOwned() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(true);
        when(ioService.readAllString(any(org.uberfire.java.nio.file.Path.class))).thenReturn("some-other-user");

        try {
            lockService.releaseLock(path);
            fail("Expected exception on attempt to release lock not owned by user");
        } catch (Exception ioe) {
            // expected    
        }
    }

    @Test
    public void forceReleaseLockSucceedsIfLockNotOwned() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(true);
        when(ioService.readAllString(any(org.uberfire.java.nio.file.Path.class))).thenReturn("some-other-user");

        final LockResult result = lockService.forceReleaseLock(path);
        assertTrue(result.isSuccess());
        assertEquals(path,
                     result.getLockInfo().getFile());
        assertEquals(null,
                     result.getLockInfo().lockedBy());
        assertFalse(result.getLockInfo().isLocked());
    }

    @Test
    public void releaseLockFailsIfFileUnlocked() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(false);

        final LockResult result = lockService.releaseLock(path);
        assertFalse(result.isSuccess());
        assertEquals(path,
                     result.getLockInfo().getFile());
        assertEquals(null,
                     result.getLockInfo().lockedBy());
        assertFalse(result.getLockInfo().isLocked());
    }

    @Test
    public void releaseLockUpdatesSession() {
        lockService.acquireLock(path);

        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(true);
        when(ioService.readAllString(any(org.uberfire.java.nio.file.Path.class))).thenReturn("testUser");

        lockService.releaseLock(path);
        verify(httpSession).setAttribute(eq(VFSLockServiceImpl.LOCK_SESSION_ATTRIBUTE_NAME),
                                         any(Set.class));
    }

    @Test
    // Unfortunately, batching is required for ensuring writes are properly 
    // replicated in the cluster. This needs to addressed in a future version 
    // of UF: https://issues.jboss.org/browse/UF-242
    public void releaseLockUsesBatch() {
        lockService.acquireLock(path);

        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(true);
        when(ioService.readAllString(any(org.uberfire.java.nio.file.Path.class))).thenReturn("testUser");

        lockService.releaseLock(path);

        final InOrder inOrder = inOrder(ioService);
        inOrder.verify(ioService).startBatch(fileSystem);
        inOrder.verify(ioService).exists(any(org.uberfire.java.nio.file.Path.class));
        inOrder.verify(ioService).readAllString(any(org.uberfire.java.nio.file.Path.class));
        inOrder.verify(ioService).delete(any(org.uberfire.java.nio.file.Path.class));
        inOrder.verify(ioService).endBatch();
    }

    @Test
    public void retrieveLockInfoForLockedFile() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(true);
        when(ioService.readAllString(any(org.uberfire.java.nio.file.Path.class))).thenReturn("some-user");

        final LockInfo info = lockService.retrieveLockInfo(path);
        assertTrue(info.isLocked());
        assertEquals("some-user",
                     info.lockedBy());
    }

    @Test
    public void retrieveLockInfoForUnlockedFile() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(false);
        when(ioService.readAllString(any(org.uberfire.java.nio.file.Path.class))).thenReturn(null);

        final LockInfo info = lockService.retrieveLockInfo(path);
        assertFalse(info.isLocked());
        assertNull(info.lockedBy());
    }

    @Test
    public void retrieveLockInfoNoSuchFileException() {
        when(ioService.exists(any(org.uberfire.java.nio.file.Path.class))).thenReturn(true);
        when(ioService.readAllString(any(org.uberfire.java.nio.file.Path.class))).thenThrow(new NoSuchFileException());

        final LockInfo info = lockService.retrieveLockInfo(path);
        assertFalse(info.isLocked());
        assertNull(info.lockedBy());
    }

    private void setupRpcContext() {
        final Message message = MessageBuilder.createMessage("for testing").signalling().done().getMessage();
        message.setResource("Session",
                            queueSession);
        RpcContext.set(message);
    }
}