package com.atlassian.confluence.rpc.soap.services;

import com.atlassian.confluence.importexport.ImportExportException;
import com.atlassian.confluence.importexport.ImportExportManager;
import com.atlassian.confluence.importexport.DefaultExportContext;
import com.atlassian.confluence.pages.Comment;
import com.atlassian.confluence.rpc.AlreadyExistsException;
import com.atlassian.confluence.rpc.NotPermittedException;
import com.atlassian.confluence.rpc.RemoteException;
import com.atlassian.confluence.rpc.soap.ConfluenceSoapService;
import com.atlassian.confluence.rpc.soap.SoapUtils;
import com.atlassian.confluence.rpc.soap.beans.RemoteSpace;
import com.atlassian.confluence.rpc.soap.beans.RemoteSpaceSummary;
import com.atlassian.confluence.security.*;
import com.atlassian.confluence.setup.BootstrapManager;
import com.atlassian.confluence.spaces.Space;
import com.atlassian.confluence.spaces.SpaceManager;
import com.atlassian.confluence.user.AuthenticatedUserThreadLocal;
import com.atlassian.confluence.user.UserAccessor;
import com.atlassian.confluence.user.PersonalInformationManager;
import com.atlassian.confluence.user.PersonalInformation;
import com.atlassian.confluence.util.GeneralUtil;
import com.atlassian.renderer.WikiStyleRenderer;
import com.atlassian.user.Group;
import com.atlassian.user.User;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * This is the soap service that handles all of the 'space' type methods.
 * <p/>
 * Usually delegated from the ConfluenceSoapServiceDelegator.
 */
public class SpacesSoapService
{
    private class UserOrGroupResolver
    {
        private String userName = null;
        private String groupName = null;

        public UserOrGroupResolver(String remoteEntityName) throws RemoteException
        {
            if (remoteEntityName != null)
            {
                // See if the remoteEntityName we were given is a valid user
                User user = userAccessor.getUser(remoteEntityName);
                if (user == null)
                {
                    // The specified entity name is not a user therefore check if the entity is a group
                    Group group = userAccessor.getGroup(remoteEntityName);
                    if (group == null)
                    {
                        throw new RemoteException("No user or group with the name '" + remoteEntityName + "' exists.");
                    }
                    else
                    {
                        groupName = remoteEntityName;
                    }
                }
                else
                {
                    userName = remoteEntityName;
                }
            }
        }

        public String getUserName()
        {
            return userName;
        }

        public String getGroupName()
        {
            return groupName;
        }
    }

    SpaceManager spaceManager;
    PermissionManager permissionManager;
    SpacePermissionManager spacePermissionManager;
    PersonalInformationManager personalInformationManager;
    WikiStyleRenderer wikiStyleRenderer;
    SoapServiceHelper soapServiceHelper;
    ImportExportManager importExportManager;
    BootstrapManager bootstrapManager;
    private GateKeeper gateKeeper;
    UserAccessor userAccessor;

    public void setUserAccessor(UserAccessor userAccessor)
    {
        this.userAccessor = userAccessor;
    }

    public void setSpaceManager(SpaceManager spaceManager)
    {
        this.spaceManager = spaceManager;
    }

    public void setPermissionManager(PermissionManager permissionManager)
    {
        this.permissionManager = permissionManager;
    }

    public void setWikiStyleRenderer(WikiStyleRenderer wikiStyleRenderer)
    {
        this.wikiStyleRenderer = wikiStyleRenderer;
    }

    public void setSoapServiceHelper(SoapServiceHelper soapServiceHelper)
    {
        this.soapServiceHelper = soapServiceHelper;
    }

    public void setSpacePermissionManager(SpacePermissionManager spacePermissionManager)
    {
        this.spacePermissionManager = spacePermissionManager;
    }

    public void setPersonalInformationManager(PersonalInformationManager personalInformationManager)
    {
        this.personalInformationManager = personalInformationManager;
    }

    public RemoteSpaceSummary[] getSpaces() throws RemoteException
    {
        User user = AuthenticatedUserThreadLocal.getUser();
        List spaces = spaceManager.getPermittedSpaces(user);
        return SoapUtils.getSpaceSummaries(spaces);
    }

    public RemoteSpace getSpace(String spaceKey) throws RemoteException
    {
        return new RemoteSpace(soapServiceHelper.retrieveSpace(spaceKey), wikiStyleRenderer);
    }

    /**
     * Returns permissions that the logged in user has on the space with the given key.
     */
    public String[] getPermissions(String spaceKey) throws RemoteException
    {
        User user = AuthenticatedUserThreadLocal.getUser();
        Space space = soapServiceHelper.retrieveSpace(spaceKey);
        return getUserPermissions(space, user);
    }

    /**
     * Returns the permissions that the nominated user has on the space with the given key.
     */
    public String[] getPermissions(String spaceKey, String userName) throws RemoteException
    {
        User currentUser = AuthenticatedUserThreadLocal.getUser();
        Space space = soapServiceHelper.retrieveSpace(spaceKey);
        User user = userAccessor.getUser(userName);

        if (!GeneralUtil.isSuperUser(currentUser) &&
                !permissionManager.hasPermission(currentUser, Permission.ADMINISTER, space) &&
                !currentUser.equals(user))
        {
            throw new NotPermittedException("Only space administrators can view permissions for other " +
                    "users in the space.");
        }

        if (userName == null || userAccessor.getUser(userName) == null)
            throw new RemoteException("No user with the name '" + userName + "' exists.");

        return getUserPermissions(space, user);
    }

    private String[] getUserPermissions(Space space, User user)
    {
        boolean superUser = GeneralUtil.isSuperUser(user);

        boolean viewPerm = superUser || permissionManager.hasPermission(user, Permission.VIEW, space);
        boolean modifyPerm = superUser || permissionManager.hasPermission(user, Permission.EDIT, space);
        boolean commentPerm = superUser || permissionManager.hasCreatePermission(user, space, Comment.class);
        boolean adminPerm = superUser || permissionManager.hasPermission(user, Permission.ADMINISTER, space);

        List permissions = new ArrayList(4);
        if (viewPerm)
            permissions.add(ConfluenceSoapService.VIEW_PERMISSION);

        if (modifyPerm)
            permissions.add(ConfluenceSoapService.MODIFY_PERMISSION);

        if (commentPerm)
            permissions.add(ConfluenceSoapService.COMMENT_PERMISSION);

        if (adminPerm)
            permissions.add(ConfluenceSoapService.ADMIN_SPACE_PERMISSION);

        return (String[]) permissions.toArray(new String[permissions.size()]);
    }

    public RemoteSpace addSpace(RemoteSpace space) throws RemoteException
    {
        User user = AuthenticatedUserThreadLocal.getUser();

        if (!spaceManager.isValidSpaceKey(space.getKey()))
            throw new RemoteException("Invalid space key: " + space.getKey());
        if (!permissionManager.hasCreatePermission(user, PermissionManager.TARGET_APPLICATION, Space.class))
            throw new NotPermittedException("No permission to create spaces.");
        if (spaceManager.getSpace(space.getKey()) != null)
            throw new AlreadyExistsException("A space already exists with key " + space.getKey());
        return new RemoteSpace(spaceManager.createSpace(space.getKey(), space.getName(), space.getDescription(), user), wikiStyleRenderer);

    }

    /**
     * Perform checking that a personal space for a user may be created
     *
     * @param user The owner of the personal space
     * @throws NotPermittedException The current principal may not create a personal space for the user
     * @throws AlreadyExistsException A personal space already exists for the user
     */
    protected void verifyPersonalSpaceCreation(User user) throws NotPermittedException, AlreadyExistsException {
        User currentUser = AuthenticatedUserThreadLocal.getUser();
        boolean superUser = GeneralUtil.isSuperUser(currentUser);

        PersonalInformation pi = personalInformationManager.getPersonalInformation(user.getName());

        if (!permissionManager.hasCreatePermission(currentUser, pi, Space.class))
            throw new NotPermittedException("No permission to create spaces.");
        if (currentUser != null && !user.getName().equals(currentUser.getName()))
        {
            if (!(superUser || permissionManager.hasPermission(user, Permission.ADMINISTER, PermissionManager.TARGET_APPLICATION)))
                throw new NotPermittedException("No permission to create a personal space for user " + user.getName());
        }

        if (spaceManager.getSpace(SpaceManager.PERSONAL_SPACEKEY_IDENTIFIER + user.getName()) != null)
            throw new AlreadyExistsException("A space already exists with key " + SpaceManager.PERSONAL_SPACEKEY_IDENTIFIER + user.getName());
    }

    public RemoteSpace addPersonalSpace(RemoteSpace space, String username) throws RemoteException
    {
        User user = userAccessor.getUser(username);
        if (user == null)
            throw new RemoteException("No user found with name " + username);

        verifyPersonalSpaceCreation(user);

        return new RemoteSpace(spaceManager.createPersonalSpace(space.getName(), space.getDescription(), user), wikiStyleRenderer);
    }

    /**
     * Convert a space to a personal space
     *
     * @param userName User name of user to create a personal space for
     * @param spaceKey The key of the space to convert
     * @param newName The new name of the space once it has been converted
     * @param updateLinks Update links in the space after conversion
     * @return true if successful
     * @throws RemoteException
     */
     public boolean convertToPersonalSpace(String userName, String spaceKey, String newName, boolean updateLinks) throws RemoteException {

        User targetUser = userAccessor.getUser(userName);
         if (targetUser == null)
            throw new RemoteException("No user found with name " + userName);

        verifyPersonalSpaceCreation(targetUser);

        Space targetSpace = spaceManager.getSpace(spaceKey);
         if (targetSpace == null)
            throw new RemoteException("Space '" + spaceKey + "' does not exist");

        User currentUser = AuthenticatedUserThreadLocal.getUser();
        boolean superUser = GeneralUtil.isSuperUser(currentUser);

        if (!(superUser || permissionManager.hasPermission(currentUser, Permission.ADMINISTER, targetSpace)))
            throw new NotPermittedException("You are not permitted to convert the space'" + spaceKey + "' to a personal space");

        spaceManager.convertToPersonalSpace(targetSpace, targetUser, updateLinks);

        return true;
    }

    /**
     * Get all fine grained Space Level Permissions so that they can be added remotely.
     * RETURNS a string array containing the following elements.
     * VIEWSPACE_PERMISSION
     * COMMENT_PERMISSION
     * EDITSPACE
     * SETSPACEPERMISSIONS
     * REMOVEPAGE
     * REMOVECOMMENT
     * REMOVEBLOG
     * CREATEATTACHMENT
     * REMOVEATTACHMENT
     * EDITBLOG
     * EXPORTPAGE
     * EXPORTSPACE
     * REMOVEMAIL
     */

    public String[] getSpaceLevelPermissions() throws RemoteException
    {
        List genericPermissions = SpacePermission.getGenericSpacePermissions();
        String[] permissions = new String[genericPermissions.size() + 1];
        Iterator it = genericPermissions.iterator();
        for (int i = 0; it.hasNext(); i++)
        {
            permissions[i] = (String) it.next();
        }
        permissions[permissions.length - 1] = SpacePermission.VIEWSPACE_PERMISSION;
        return permissions;
    }

    /**
     * Add a given permission for a particular group/user to the given space.
     * Assumption : Users and groups cannot have the same name.
     *
     * @param permission       is the Permission that is to be added to the space
     * @param remoteEntityName is either a group or a user name or null for anonymous permissions
     * @param spaceKey         is the key of the space for which the given permission is to be added to the given given group/user
     * @throws RemoteException if space is null or authenticated user does not not have A ADMINISTRATE SPACE
     *                         permission or if null entity is specified or if no valid entity i.e group/user exist with the given name
     */
    public boolean addPermissionToSpace(String permission, String remoteEntityName, String spaceKey) throws RemoteException
    {
        if (permission == null)
            throw new RemoteException("Space Permission must be non-null");

        // Create an array of the single permission and pass it off
        String[] permissionsArray = {permission};

        return addPermissionsToSpace(permissionsArray, remoteEntityName, spaceKey);
    }

    private Space validatePermissionsOperation(String spaceKey) throws RemoteException
    {
        Space space = spaceManager.getSpace(spaceKey);

        // Check if Space exists
        if (space == null)
        {
            throw new NotPermittedException("Not permitted to add space permissions");
        }

        soapServiceHelper.assertCanAdminister(space);
        return space;
    }

    /**
     * @param permissions
     * @param remoteEntityName is either a group or a user name or null for anonymous permissions
     * @param spaceKey
     * @return
     * @throws RemoteException
     * @throws NotPermittedException
     */
    public boolean addPermissionsToSpace(String[] permissions, String remoteEntityName, String spaceKey) throws RemoteException, NotPermittedException
    {
        if (permissions == null)
            throw new RemoteException("Space Permissions must be non-null");

        Space space = validatePermissionsOperation(spaceKey);

        UserOrGroupResolver resolver = new UserOrGroupResolver(remoteEntityName);

        List spacePermissions = space.getPermissions();

        // Iterate through the given array of permissions, and add as necessary
        for (int i = 0; i < permissions.length; i++)
        {
            String permission = permissions[i];

            // Add permission to the user for the given space provided the permission has not yet been added
            SpacePermission userPermission = new SpacePermission(permission, space, resolver.getGroupName(), resolver.getUserName());

            if (!spacePermissions.contains(userPermission))
            {
                space.addPermission(userPermission);
                spacePermissionManager.savePermission(userPermission);
            }
        }
        return true;
    }

    public boolean removePermissionFromSpace(String permission, String remoteEntityName, String spaceKey) throws NotPermittedException, RemoteException
    {
        Space space = validatePermissionsOperation(spaceKey);

        UserOrGroupResolver resolver = new UserOrGroupResolver(remoteEntityName);

        // retrieve space permissions
        List spacePermissions = space.getPermissions();

        // remove permission from the user for the given space provided the permission exists
        SpacePermission userPermission = new SpacePermission(permission, space, resolver.getGroupName(), resolver.getUserName());

        for (int i = 0; i < spacePermissions.size(); i++)
        {
            SpacePermission spacePermission = (SpacePermission) spacePermissions.get(i);

            if (userPermission.equals(spacePermission))
                spacePermissionManager.removePermission(spacePermission);
        }

        return true;
    }

    public Boolean removeSpace(String spaceKey) throws RemoteException
    {
        Space space = spaceManager.getSpace(spaceKey);

        if (space == null)
        {
            throw new RemoteException("No space found for space key: " + spaceKey);
        }

        soapServiceHelper.assertCanView(space);
        soapServiceHelper.assertCanAdminister(space);
        spaceManager.removeSpace(space);

        return Boolean.TRUE;
    }

    public String exportSpace(String spaceKey, String exportType) throws RemoteException
    {
        if (exportType.equals("all")) //a gesture of convenience, clients might not have a ImportExportManager to access constants
            exportType = ImportExportManager.TYPE_XML;

        if (!importExportManager.getImportExportTypeSpecifications().contains(exportType))
        {
            throw new RemoteException("Invalid export type: [" + exportType + "]");
        }

        Space space = spaceManager.getSpace(spaceKey);

        if (space == null)
        {
            throw new RemoteException("Invalid spaceKey: [" + spaceKey + "]");
        }

        // Check permissions
        soapServiceHelper.assertCanExport(space);

        String downloadPath;

        User user = AuthenticatedUserThreadLocal.getUser();

        try
        {
            DefaultExportContext context = new DefaultExportContext(ImportExportManager.EXPORT_XML_TYPE_SPACE);
            context.setExportComments(true);
            context.setExportAttachments(true);
            context.addWorkingEntity(space);
            context.setContentTree(importExportManager.getContentTree(user, space));

            String archivePath = importExportManager.exportAs(exportType, context);
            downloadPath = importExportManager.prepareDownloadPath(archivePath);
            gateKeeper.addKey(downloadPath, user);
        }
        catch (ImportExportException e)
        {
            return "Could not export space: " + e;
        }
        catch (IOException e)
        {
            return "Could not export space: " + e;
        }

        return bootstrapManager.getBaseUrl() + downloadPath;
    }

    public void setImportExportManager(ImportExportManager importExportManager)
    {
        this.importExportManager = importExportManager;
    }

    public void setBootstrapManager(BootstrapManager bootstrapManager)
    {
        this.bootstrapManager = bootstrapManager;
    }

    public void setGateKeeper(GateKeeper gateKeeper)
    {
        this.gateKeeper = gateKeeper;
    }
}