package org.rundeck.app.acl;


import com.dtolabs.rundeck.core.authorization.providers.BaseValidator;
import com.dtolabs.rundeck.core.authorization.providers.YamlProvider;
import com.dtolabs.utils.Streams;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * Provides ACLFileManager backed by files in a directory, does NOT implement Listener semantics
 */
@Builder
@RequiredArgsConstructor
public class ACLFSFileManager
        implements ACLFileManager
{
    final File directory;
    @Getter final BaseValidator validator;

    @Override
    public long storePolicyFile(final String fileName, final InputStream input) throws IOException {
        File file = fileForName(fileName);
        try (FileOutputStream out = new FileOutputStream(file)) {
            return Streams.copyStream(input, out);
        }
    }

    private File fileForName(final String fileName) {
        if (fileName.contains("/")) {
            throw new IllegalArgumentException("invalid aclpolicy filename: " + fileName);
        }
        if (!fileName.endsWith(".aclpolicy")) {
            throw new IllegalArgumentException("invalid aclpolicy filename: " + fileName);
        }
        if (fileName.charAt(0) == '.') {
            throw new IllegalArgumentException("invalid aclpolicy filename: " + fileName);
        }

        File file = new File(directory, fileName);
        if (!file.toPath().normalize().startsWith(directory.toPath().normalize())) {
            throw new IllegalArgumentException(String.format("Path is outside of destination directory: %s", file));
        }
        if(!directory.isDirectory()){
            if(!directory.mkdirs()){
                throw new IllegalStateException("Unable to create necessary directory: " + directory.getAbsolutePath());
            }
        }
        return file;
    }

    @Override
    public boolean deletePolicyFile(final String fileName) throws IOException {
        return Files.deleteIfExists(fileForName(fileName).toPath());
    }

    @Override
    public long storePolicyFileContents(final String fileName, final String fileText) throws IOException {
        File file = fileForName(fileName);
        try (FileOutputStream out = new FileOutputStream(file)) {
            byte[] bytes = fileText.getBytes(StandardCharsets.UTF_8);
            out.write(bytes);
            return bytes.length;
        }
    }

    @Override
    public AclPolicyFile getAclPolicy(final String fileName) {
        return new AclPolicyImpl(fileForName(fileName));
    }

    @RequiredArgsConstructor
    static class AclPolicyImpl
            implements AclPolicyFile
    {
        private final File file;

        @Override
        public InputStream getInputStream() {
            try {
                return new FileInputStream(file);
            } catch (FileNotFoundException e) {
                return null;
            }
        }

        public Date getModified() {
            return new Date(file.lastModified());
        }

        public Date getCreated() {
            return new Date(file.lastModified());
        }

        public String getName() {
            return file.getName();
        }
    }

    @Override
    public String getPolicyFileContents(final String fileName) throws IOException {
        File file = fileForName(fileName);
        try (InputStreamReader is = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
            return readString(is);
        }
    }

    private String readString(final Reader is) throws IOException {
        StringWriter sw = new StringWriter();
        Streams.copyWriterCount(is, sw);
        return sw.toString();
    }

    @Override
    public long loadPolicyFileContents(final String fileName, final OutputStream outputStream) throws IOException {
        File file = fileForName(fileName);
        try (FileInputStream fis = new FileInputStream(file)) {
            return Streams.copyStream(fis, outputStream);
        }
    }

    @Override
    public boolean existsPolicyFile(final String file) {
        return fileForName(file).exists();
    }

    @Override
    public List<String> listStoredPolicyFiles() {
        if(!directory.isDirectory()){
            if(!directory.mkdirs()){
                throw new IllegalStateException("Required directory cannot be created: " + directory);
            }
        }
        return Arrays.asList(Objects.requireNonNull(directory.list(YamlProvider.filenameFilter)));
    }
}

@@ -1797,219 +1797,27 @@
     
-if(!directory.isDirectory())%7B%0A            if(!directory.mkdirs())%7B%0A                throw new IllegalStateException(%22Unable to create necessary directory: %22 + directory.getAbsolutePath());%0A            %7D%0A        %7D
+requireDirectory();
 %0A   
@@ -4188,42 +4188,172 @@
     
-if(!directory.isDirectory())%7B
+requireDirectory();%0A        return Arrays.asList(Objects.requireNonNull(directory.list(YamlProvider.filenameFilter)));
 %0A    
+%7D%0A%0A    private void requireDirectory() %7B%0A
     
@@ -4358,16 +4358,17 @@
       if
+ 
 (!direct
@@ -4379,17 +4379,46 @@
 mkdirs()
-)
+ %7C%7C !directory.isDirectory()) 
 %7B%0A      
@@ -4415,36 +4415,32 @@
 ) %7B%0A            
-    
 throw new Illega
@@ -4460,184 +4460,89 @@
 on(%22
-Required directory cannot be created: %22 + directory);%0A            %7D%0A        %7D%0A        return Arrays.asList(Objects.requireNonNull(directory.list(YamlProvider.filenameFilter)));
+Unable to create necessary directory: %22 + directory.getAbsolutePath());%0A        %7D
 %0A   

update mkdirs check