/*
 * Created by IntelliJ IDEA.
 * User: Mike
 * Date: Feb 12, 2004
 * Time: 4:10:53 PM
 */
package com.atlassian.confluence.rpc.soap.services;

import bucket.container.ContainerManager;
import bucket.test.container.SpringTestContainerContext;
import com.atlassian.confluence.pages.Attachment;
import com.atlassian.confluence.pages.Comment;
import com.atlassian.confluence.pages.Page;
import com.atlassian.confluence.pages.PageManager;
import com.atlassian.confluence.renderer.PageContext;
import com.atlassian.confluence.rpc.RemoteException;
import com.atlassian.confluence.rpc.soap.AbstractSoapTestCase;
import com.atlassian.confluence.rpc.soap.beans.*;
import com.atlassian.confluence.security.PermissionManager;
import com.atlassian.confluence.security.SpacePermission;
import com.atlassian.confluence.security.Permission;
import com.atlassian.confluence.setup.BootstrapManager;
import com.atlassian.confluence.setup.BootstrapUtils;
import com.atlassian.confluence.spaces.Space;
import com.atlassian.confluence.spaces.SpaceManager;
import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
import com.atlassian.renderer.WikiStyleRenderer;
import com.atlassian.user.User;
import com.atlassian.user.UserManager;
import com.atlassian.user.security.password.PasswordEncryptor;
import com.atlassian.user.repository.DefaultRepository;
import com.atlassian.user.impl.memory.provider.MemoryProvider;
import com.atlassian.user.impl.memory.MemoryUserManager;
import com.atlassian.user.impl.osuser.security.password.OSUPasswordEncryptor;
import com.mockobjects.dynamic.C;
import com.mockobjects.dynamic.Mock;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class TestPagesSoapService extends AbstractSoapTestCase
{
    private User user;
    private PagesSoapService service;
    private Mock mockPageManager;
    private Mock mockRenderer;
    private Mock mockServiceHelper;
    private Mock mockSpaceManager;

    public void setUp() throws Exception
    {
        super.setUp();
        service = new PagesSoapService();
        PasswordEncryptor encryptor = new OSUPasswordEncryptor();
        UserManager um = new MemoryUserManager(new DefaultRepository(),new MemoryProvider(), encryptor);
        user = um.createUser("bob");
        AuthenticatedUserThreadLocal.setUser(user);

        // setup mock objects
        mockPageManager = new Mock(PageManager.class);
        service.setPageManager((PageManager) mockPageManager.proxy());

        service.setPermissionManager((PermissionManager) mockPermissionManager.proxy());

        mockRenderer = new Mock(WikiStyleRenderer.class);
        service.setWikiStyleRenderer((WikiStyleRenderer) mockRenderer.proxy());

        mockServiceHelper = new Mock(SoapServiceHelper.class);
        service.setSoapServiceHelper((SoapServiceHelper) mockServiceHelper.proxy());

        mockSpaceManager = new Mock(SpaceManager.class);
        service.setSpaceManager((SpaceManager) mockSpaceManager.proxy());

        BootstrapUtils.getBootstrapManager().setProperty(BootstrapManager.ATTACHMENTS_DIR_PROP, System.getProperty("java.io.tmpdir"));

        ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"/testColourSchemeContext.xml", "/testBootstrapContext.xml"});
        ContainerManager.getInstance().setContainerContext(new SpringTestContainerContext(context));

        service.setBootstrapManager(BootstrapUtils.getBootstrapManager());
    }

    public void tearDown() throws Exception
    {
        ContainerManager.resetInstance();
        super.tearDown();

        AuthenticatedUserThreadLocal.setUser(null);
    }

    private void verifyMocks()
    {
        mockPageManager.verify();
        mockRenderer.verify();
    }

    public void testRetrieveSpaceAndPages() throws Exception
    {
        // retrieve the space
        Space space = makeSpace("FRUIT");
        Page apples = makePage(space, 1, "Apples");
        Page bananas = makePage(space, 2, "Bananas");

        ArrayList content = new ArrayList();
        content.add(apples);
        content.add(bananas);

        ArrayList permissions = new ArrayList();
        permissions.add(SpacePermission.VIEWSPACE_PERMISSION);

        mockSpaceManager.matchAndReturn("getPages", C.args(C.eq(space), C.IS_TRUE), content);
        mockPermissionManager.expectAndReturn("getPermittedEntities", C.args(C.eq(user), C.eq(Permission.VIEW), C.eq(content) ), content);

        service.setPermissionManager((PermissionManager) mockPermissionManager.proxy());

        mockServiceHelper.expectAndReturn("retrieveSpace", C.args(C.eq("FRUIT")), space);
        List summaries = Arrays.asList(service.getPages("FRUIT"));
        assertEquals(2, summaries.size());
        assertTrue(summaries.contains(new RemotePageSummary(bananas)));
        assertTrue(summaries.contains(new RemotePageSummary(apples)));
        verifyMocks();
    }

    public void testRetrievePageHistoryCommentsAndAttachments() throws Exception
    {
        // first check we can retrieve the page
        Space space = makeSpace("FRUIT");
        Page page = makePage(space, 1, "Quinces");
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), page);
        assertEquals(new RemotePage(page), service.getPage(1));

        // now check we can retrieve the page history
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), page);
        Page historicalPage = makePage(space, 2, "Quinces");
        page.setVersion(2);
        historicalPage.setVersion(1);
        historicalPage.setLastModificationDate(new Date());
        historicalPage.setLastModifierName("fred");
        page.addPreviousVersion(historicalPage);
        RemotePageHistory[] history = service.getPageHistory(1);
        assertTrue(Arrays.equals(new RemotePageHistory[]{new RemotePageHistory(historicalPage)}, history));

        // now check we can retrieve the comments
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), page);
        Comment c = new Comment();
        c.setId(4);
        page.addComment(c);
        RemoteComment[] comments = service.getComments(1);
        assertTrue(Arrays.equals(new RemoteComment[]{new RemoteComment(c)}, comments));

        // now check we can retrieve the attachments
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), page);
        Attachment a = new Attachment();
        a.setId(4);
        a.setFileName("bob.txt");
        page.addAttachment(a);
        RemoteAttachment[] attachments = service.getAttachments(1);
        assertTrue(Arrays.equals(new RemoteAttachment[]{new RemoteAttachment(a)}, attachments));
        verifyMocks();
    }


    public void testDeleteNonExistantOrNonBrowseableOrNonModifiablePage() throws Exception
    {
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), null);
        try
        {
            service.removePage(1);
            fail("Should have thrown exception.");
        }
        catch (Exception e)
        {
        }

        // no perms to view page
        Space space = makeSpace("FRUIT");
        Page page = makePage(space, 1, "Quinces");
        mockServiceHelper.expectAndThrow("retrieveAbstractPage", C.args(C.eq(1L)), new RemoteException());
        try
        {
            service.removePage(1);
            fail("Should have thrown exception.");
        }
        catch (Exception e)
        {
        }

        // perms to view page but no perms to modify page
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), page);
        mockServiceHelper.matchAndThrow("assertCanModify", C.args(C.eq(space)), new RemoteException());
        mockServiceHelper.matchAndThrow("assertCanRemove", C.args(C.eq(page)), new RemoteException());
        try
        {
            service.removePage(1);
            fail("Should have thrown exception.");
        }
        catch (Exception e)
        {
        }
        verifyMocks();
    }

    public void testDeleteOldVersionFails() throws Exception
    {
        Space space = makeSpace("FRUIT");
        Page page = makePage(space, 1, "Quinces");
        page.setOriginalVersion(makePage(null, 2, "Quinces"));
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), page);
        mockServiceHelper.expect("assertCanRemove", C.args(C.eq(page)));
        try
        {
            service.removePage(1);
            fail("Should have thrown exception.");
        }
        catch (Exception e)
        {
            assertEquals("You can't remove an old version of the page - remove the current version.", e.getMessage());
        }
        verifyMocks();
    }

    public void testDeletePageSuccessfully() throws Exception
    {
        Space space = makeSpace("FRUIT");
        // this is a neat little hack to check whether or not removed is called
        // note: IDEA put in the array automatically when converting removed to final - neat!
        final boolean[] removed = new boolean[]{false};
        Page page = new Page()
        {
            public void remove(PageManager pageManager)
            {
                removed[0] = true;
            }
        };
        page.setId(1);
        page.setSpace(space);
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), page);
        mockServiceHelper.expect("assertCanRemove", C.args(C.eq(page)));
        mockPageManager.expect("trashPage", page);
        assertTrue(service.removePage(1).booleanValue());
        verifyMocks();
    }

    public void testRenderContentNoPageOrNoBrowsePermission() throws Exception
    {
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), null);
        try
        {
            service.renderContent(null, 1, null);
            fail("Should have thrown exception.");
        }
        catch (Exception e)
        {
        }

        mockServiceHelper.expectAndThrow("retrieveAbstractPage", C.args(C.eq(1L)), new RemoteException());
        try
        {
            service.renderContent(null, 1, "foo");
            fail("Should have thrown exception.");
        }
        catch (Exception e)
        {
        }
        verifyMocks();
    }

    public void testRenderExistingPageContent() throws Exception
    {
        Space space = makeSpace("FRUIT");
        Page page = makePage(space, 1, "Quinces");
        page.setSpace(space);
        page.setContent("the content");
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), page);
        mockRenderer.expectAndReturn("convertWikiToXHtml", C.args(C.eq(page.toPageContext()), C.eq("the content")), "worked");
        String renderedContent = service.renderContent("FRUIT", 1, null);
        assertTrue(renderedContent.indexOf("<html>") >= 0);
        assertTrue(renderedContent.indexOf("<head>") >= 0);
        assertTrue(renderedContent.indexOf("<title>Quinces</title>") >= 0);
        assertTrue(renderedContent.indexOf("<body>") >= 0);
        assertTrue(renderedContent.indexOf("worked") > 0);
        assertTrue(renderedContent.indexOf("</body>") > 0);
        assertTrue(renderedContent.indexOf("</html>") > 0);
        verifyMocks();
    }

    public void testRenderUpdatedPageContent() throws Exception
    {
        Space space = makeSpace("FRUIT");
        Page page = makePage(space, 1, "Quinces");
        page.setSpace(space);
        page.setContent("the content");
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(1L)), page);
        mockRenderer.expectAndReturn("convertWikiToXHtml", C.args(C.eq(page.toPageContext()), C.eq("updated content")), "worked");
        String renderedContent = service.renderContent("FRUIT", 1, "updated content");
        assertTrue(renderedContent.indexOf("<html>") >= 0);
        assertTrue(renderedContent.indexOf("<head>") >= 0);
        assertTrue(renderedContent.indexOf("<title>Quinces</title>") >= 0);
        assertTrue(renderedContent.indexOf("<body>") >= 0);
        assertTrue(renderedContent.indexOf("worked") > 0);
        assertTrue(renderedContent.indexOf("</body>") > 0);
        assertTrue(renderedContent.indexOf("</html>") > 0);
        verifyMocks();
    }

    public void testRenderContentNoPageNoSpaceKey() throws Exception
    {
        try
        {
            service.renderContent(null, -1, "the content");
            fail("Should throw exception.");
        }
        catch (Exception e)
        {
        }
        verifyMocks();
    }

    public void testRenderContentNoPage() throws Exception
    {
        mockRenderer.expectAndReturn("convertWikiToXHtml", C.args(C.eq(new PageContext("FRUIT")), C.eq("the content")), "worked");
        String renderedContent = service.renderContent("FRUIT", -1, "the content");
        assertTrue(renderedContent.indexOf("<html>") >= 0);
        assertTrue(renderedContent.indexOf("<head>") >= 0);
        assertTrue(renderedContent.indexOf("<title>Untitled</title>") >= 0);
        assertTrue(renderedContent.indexOf("<body>") >= 0);
        assertTrue(renderedContent.indexOf("worked") > 0);
        assertTrue(renderedContent.indexOf("</body>") > 0);
        assertTrue(renderedContent.indexOf("</html>") > 0);
        verifyMocks();
    }

    public void testStorePageWithBadSpaceOrInvalidSpacePermissionsFails() throws Throwable
    {
        // first test with a null space
        RemotePage rp = new RemotePage();
        assertBarfsStoringPage(rp);

        // now check without space browse permissions
        rp.setSpace("FRUIT");
        Space space = makeSpace("FRUIT");
        mockServiceHelper.expectAndThrow("retrieveSpace", C.args(C.eq("FRUIT")), new RemoteException());
        assertBarfsStoringPage(rp);

        // now check with space view but without space modify permissions
        mockServiceHelper.expectAndReturn("retrieveSpace", C.args(C.eq("FRUIT")), space);
        mockServiceHelper.expectAndThrow("assertCanView", C.args(C.eq(space)), new RemoteException());
        assertBarfsStoringPage(rp);
        verifyMocks();
    }

    public void testStoreNewPageSuccessfully() throws Exception
    {
        Space space = makeSpace("FRUIT");
        Page expectedPage = new Page();
        RemotePage rp = new RemotePage();
        setupStoreNewPage(space, expectedPage, rp);
        mockPageManager.expect("saveContentEntity", C.args(C.eq(expectedPage), C.IS_NULL));

        RemotePage result = service.storePage(rp);
        assertEquals("apples", result.getTitle());
        assertEquals("i like apples", result.getContent());
        assertEquals("FRUIT", result.getSpace());
        assertEquals(1, result.getVersion());
        verifyMocks();
    }

    public void testStoreNewPageWithParentSuccessfully() throws Exception
    {
        Space space = makeSpace("FRUIT");
        Page expectedPage = new Page();
        RemotePage rp = new RemotePage();
        setupStoreNewPage(space, expectedPage, rp);
        mockPageManager.expect("saveContentEntity", C.args(C.eq(expectedPage), C.IS_NULL));
        rp.setParentId(10);

        final boolean[] childAdded = new boolean[] { false };
        Page parentPage = new Page() {
            public void addChild(Page child)
            {
                child.setParentPage(this);
                childAdded[0] = true;
            }
        };
        parentPage.setId(10);
        parentPage.setSpace(space);
        mockPageManager.expectAndReturn("getPage", C.args(C.eq(10L)), parentPage);

        RemotePage result = service.storePage(rp);
        assertTrue(childAdded[0]);
        assertEquals(10, result.getParentId());
        verifyMocks();
    }

    public void testUpdatePageWithChangedParentSuccessfully() throws Exception
    {
        Space space = makeSpace("FRUIT");
        Page expectedPage = new Page();
        Page oldParent = new Page();
        oldParent.setId(1010);
        oldParent.addChild(expectedPage);
        RemotePage rp = new RemotePage();
        setupUpdatePage(space, expectedPage, rp);
        mockPageManager.expect("saveContentEntity", C.args(C.eq(expectedPage), C.eq(expectedPage), C.IS_NULL));
        rp.setParentId(10);

        final boolean[] childAdded = new boolean[] { false };
        Page parentPage = new Page() {
            public void addChild(Page child)
            {
                super.addChild(child);
                childAdded[0] = true;
            }
        };

        parentPage.setId(10);
        parentPage.setSpace(space);
        mockPageManager.expectAndReturn("getPage", C.args(C.eq(10L)), parentPage);

        RemotePage result = service.storePage(rp);
        assertTrue(childAdded[0]);
        assertEquals(10, result.getParentId());
        assertEquals(0, oldParent.getChildren().size());
        verifyMocks();
    }

    public void testUpdatePageSettingNullParentSuccessfully() throws Exception
    {
        Space space = makeSpace("FRUIT");
        Page expectedPage = new Page();
        Page oldParent = new Page();
        oldParent.setId(1010);
        oldParent.addChild(expectedPage);
        RemotePage rp = new RemotePage();
        setupUpdatePage(space, expectedPage, rp);

        // update the page, setting parent to 0. (equivalent to null).
        rp.setParentId(0);
        mockPageManager.expectAndReturn("getPage", C.args(C.eq(0L)), null);
        mockPageManager.expect("saveContentEntity", C.args(C.eq(expectedPage), C.eq(expectedPage), C.IS_NULL));

        RemotePage result = service.storePage(rp);
        assertEquals(0, result.getParentId());
        assertEquals(0, oldParent.getChildren().size());
        verifyMocks();
    }

    public void testUpdatePageWithChangedNameSuccessfully() throws Exception
    {
        Space space = makeSpace("FRUIT");
        Page expectedPage = new Page();
        RemotePage rp = new RemotePage();
        setupUpdatePage(space, expectedPage, rp);
        expectedPage.setVersion(1);
        rp.setVersion(1);
        rp.setId(100);
        rp.setTitle("New Title");
        mockServiceHelper.expect("renamePage", C.args(C.eq(expectedPage), C.eq("New Title")));
        mockPageManager.expectAndReturn("getPage", C.args(C.eq(0L)), null);

        RemotePage result = service.storePage(rp);
        verifyMocks();
    }


    private void setupUpdatePage(Space space, Page expectedPage, RemotePage rp)
    {
        mockServiceHelper.expectAndReturn("retrieveSpace", C.args(C.eq("FRUIT")), space);
        mockServiceHelper.expect("assertCanModify", C.args(C.eq(expectedPage)));
        mockServiceHelper.expectAndReturn("retrieveAbstractPage", C.args(C.eq(100L)), expectedPage);

        expectedPage.setVersion(1);
        expectedPage.setId(100);
        expectedPage.setSpace(space);
        expectedPage.setTitle("apples");
        expectedPage.setContent("i like apples");

        rp.setVersion(1);
        rp.setId(100);
        rp.setTitle("apples");
        rp.setContent("i like apples");
        rp.setSpace("FRUIT");
    }

    private void setupStoreNewPage(Space space, Page expectedPage, RemotePage rp)
    {
        mockServiceHelper.expectAndReturn("retrieveSpace", C.args(C.eq("FRUIT")), space);
        mockServiceHelper.expect("assertCanView", C.args(C.eq(space)));
        mockServiceHelper.expect("assertCanCreatePage", C.args(C.eq(space)));
        mockPageManager.expectAndReturn("getPage", C.args(C.eq("FRUIT"), C.eq("apples")), null);

        expectedPage.setSpace(space);
        expectedPage.setTitle("apples");
        expectedPage.setContent("i like apples");

        rp.setTitle("apples");
        rp.setContent("i like apples");
        rp.setSpace("FRUIT");
    }

    public void testStoreNewPageWithExistingTitle()
    {
        Space space = makeSpace("FRUIT");
        RemotePage rp = new RemotePage();
        rp.setTitle("apples");
        rp.setSpace("FRUIT");
        mockServiceHelper.expectAndReturn("retrieveSpace", C.args(C.eq("FRUIT")), space);
        mockServiceHelper.expect("assertCanView", C.args(C.eq(space)));
        mockServiceHelper.expect("assertCanCreatePage", C.args(C.eq(space)));
        mockPageManager.expectAndReturn("getPage", C.args(C.eq("FRUIT"), C.eq("apples")), new Page());

        assertBarfsStoringPage(rp);
        verifyMocks();
    }

    public void testStoreNewPageWithBadParentId() throws Exception
    {
        Space space = makeSpace("FRUIT");
        Page expectedPage = new Page();
        RemotePage rp = new RemotePage();
        setupStoreNewPage(space, expectedPage, rp);
        rp.setParentId(10);

        mockPageManager.expectAndReturn("getPage", C.args(C.eq(10L)), null);
        assertBarfsStoringPage(rp);
        verifyMocks();
    }

    /*
    // test store with modified space key fails
    // test store with modified title fails
    // test storing an outdated version fails
    // error - editing outdate version
    // test updating parent without content still updates
    // test updating content without parent still updates
    */

    private void assertBarfsStoringPage(RemotePage rp)
    {
        try
        {
            service.storePage(rp);
            fail("Should have barfed.");
        }
        catch (Exception e)
        {
        }
    }
}