package com.atlassian.stash.internal.util;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.SystemUtils;

import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.nio.file.attribute.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Sets.immutableEnumSet;
import static java.nio.file.Files.getFileAttributeView;
import static java.nio.file.Files.setPosixFilePermissions;
import static java.util.EnumSet.complementOf;
import static java.util.EnumSet.of;

public class FilePermissionUtils {

    @VisibleForTesting
    static final Set<AclEntryPermission> ACL_ENTRY_PERMISSIONS = immutableEnumSet(complementOf(of(
            AclEntryPermission.EXECUTE)));

    /**
     * This method sets appropriate file permissions for 'sensitive' files. The behaviour is operating system dependent.
     * <ul>
     *     <li>POSIX compliant operating systems; A file mask of 0640 is set. </li>
     *     <li>Windows The file is readable/writable by the current user and members of the 'Administrators' group </li>
     * </ul>
     *
     * @param file the sensitive file to which permissions will be applied
     * @throws IOException if permissions could not be set on file
     */
    public static void setRestrictedFilePermissions(@Nonnull File file) throws IOException {
        checkNotNull(file, "file");
        checkArgument(file.exists(), "exists");

        if (SystemUtils.IS_OS_WINDOWS) {
            List<UserPrincipal> userPrincipals = new LinkedList<>();
            UserPrincipalLookupService principalService = file.toPath().getFileSystem().getUserPrincipalLookupService();
            userPrincipals.add(principalService.lookupPrincipalByName(System.getProperty("user.name")));
            try {
                userPrincipals.add(principalService.lookupPrincipalByGroupName("Administrators"));
            } catch (UserPrincipalNotFoundException e) {
                // Nothing we can do here 'Administrators' is a default group in Windows/ActiveDirectory so the customer
                // has a non standard installation, in this case at least the file will be accessible to stash andn
                // the admin can apply any other desired file permissions if they want to manually edit the file.
            }
            AclFileAttributeView view = getFileAttributeView(file.toPath(), AclFileAttributeView.class);
            view.setAcl(createAclEntries(userPrincipals));
        } else {
            try {
                setPosixFilePermissions(file.toPath(), Sets.newHashSet(
                        PosixFilePermission.OWNER_WRITE,
                        PosixFilePermission.OWNER_READ,
                        PosixFilePermission.GROUP_READ));
            } catch (UnsupportedOperationException e) {
                // If you're not running windows and you're not POSIX compliant then just throw an IOException warning
                // clients know that file permissions could not be set.
                throw new IOException("Could not set permissions on file " + file.getAbsolutePath(), e);
            }
        }
    }

    private static List<AclEntry> createAclEntries(List<UserPrincipal> principals) throws IOException {
        List<AclEntry> acls = new LinkedList<>();
        for (UserPrincipal principal : principals) {
            acls.add(createAclEntry(principal));
        }
        return acls;
    }

    private static AclEntry createAclEntry(UserPrincipal user) throws IOException {
        AclEntry.Builder aclEntry = AclEntry.newBuilder();
        aclEntry.setPermissions(ACL_ENTRY_PERMISSIONS);
        aclEntry.setPrincipal(user);
        aclEntry.setType(AclEntryType.ALLOW);
        return aclEntry.build();
    }
}