/*-
 * ~~~~~~licensing~~~~~~
 * resources-service
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.esb.resources.provider;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import ru.entaxy.esb.resources.EntaxyResource;
import ru.entaxy.esb.resources.EntaxyResourceMetadata;
import ru.entaxy.esb.resources.impl.AbstractResource;
import ru.entaxy.esb.resources.impl.AbstractResourceProvider;
import ru.entaxy.platform.base.support.FileUtils;
import ru.entaxy.platform.base.support.JSONUtils;

public class FileResourceProvider extends AbstractResourceProvider {

    private static final Logger log = LoggerFactory.getLogger(FileResourceProvider.class);

    public static final String METADATA_FILE_EXTENSION = "e_r_meta";

    private String rootDirectory;
    private File root;

    public String getRootDirectory() {
        return rootDirectory;
    }

    public void setRootDirectory(String rootDirectory) {
        this.rootDirectory = rootDirectory;
        root = new File(this.rootDirectory);
        if (!root.exists())
            root.mkdirs();
    }

    @Override
    protected EntaxyResource doGetResource(String location) {
        Path rootPath = root.toPath();
        Path targetPath = Paths.get(rootPath.toString(), location);
        log.debug("TARGET PATH :: " + targetPath.toAbsolutePath().toString());
        File targetFile = new File(targetPath.toAbsolutePath().toString());
        log.debug("TARGET FILE :: " + targetFile.getAbsolutePath());
        return new FileResource(targetFile);
    }

    @Override
    protected List<EntaxyResource> doGetResources(String location) {
        Path rootPath = root.toPath();
        Path targetPath = Paths.get(rootPath.toString(), location);
        log.debug("TARGET PATH :: " + targetPath.toAbsolutePath().toString());
        File targetFile = new File(targetPath.toAbsolutePath().toString());
        if (!targetFile.exists())
            return null;
        if (!targetFile.isDirectory())
            return null;
        return Stream.of(targetFile.listFiles())
                .filter(f -> isFiltered(f))
                .map(f -> new FileResource(f))
                .collect(Collectors.toList());

    }

    protected boolean isFiltered(File f) {
        return f.isDirectory() || !f.getName().endsWith(METADATA_FILE_EXTENSION);
    }

    public static class FileResourceMetadata implements EntaxyResourceMetadata {

        public static class JsonMetadataSection implements EntaxyResourceMetadata.MetadataSection {

            String name;
            JsonObject content;

            public JsonMetadataSection(String name, JsonObject content) {
                super();
                this.name = name;
                this.content = content;
            }

            @Override
            public String getName() {
                return this.name;
            }

            @Override
            public JsonObject getContent() {
                return content;
            }

        }

        private static final Logger log = LoggerFactory.getLogger(FileResourceProvider.FileResourceMetadata.class);

        protected static Gson GSON = (new GsonBuilder())
                .setPrettyPrinting()
                .create();

        protected File metadataFile;

        protected JsonObject root;

        protected Map<String, JsonMetadataSection> sections = null;

        public FileResourceMetadata(File metadataFile) {
            super();
            this.metadataFile = metadataFile;
            if (this.metadataFile.exists()) {
                try {
                    this.root = JSONUtils.getJsonRootObject(metadataFile.toURI().toURL());
                } catch (IOException e) {
                    log.error("Error reading metadata from [" + this.metadataFile.getAbsolutePath() + "]", e);
                    this.root = new JsonObject();
                }
            } else {
                this.root = new JsonObject();
            }

        }

        public boolean exists() {
            return metadataFile.exists();
        }

        protected void initSections() {
            if (this.sections == null) {
                this.sections = new HashMap<>();
                for (Entry<String, JsonElement> entry : root.entrySet()) {
                    if (entry.getValue().isJsonObject()) {
                        sections.put(entry.getKey(),
                                new JsonMetadataSection(entry.getKey(), entry.getValue().getAsJsonObject()));
                    }
                }
            }
        }

        @Override
        public List<MetadataSection> getSections() {
            initSections();
            return this.sections.values().stream()
                    .map(m -> (MetadataSection) m)
                    .collect(Collectors.toList());
        }

        @Override
        public boolean hasSection(String sectionName) {
            initSections();
            return this.sections.containsKey(sectionName);
        }

        @Override
        public MetadataSection getSection(String sectionName) {
            if (!hasSection(sectionName)) {
                this.sections.put(sectionName, new JsonMetadataSection(sectionName, new JsonObject()));
            }
            return this.sections.get(sectionName);
        }

        @Override
        public void update() {
            for (JsonMetadataSection section : sections.values()) {
                JSONUtils.replaceValue(root, section.getName(), section.content);
            }
            try {
                FileUtils.string2file(root.toString(), metadataFile);
            } catch (IOException e) {
                log.error("Error saving file [" + metadataFile.getAbsolutePath() + "]", e);
            }
        }

        @Override
        public JsonObject getAsJson() {
            JsonObject result = root.deepCopy();
            if (sections == null)
                initSections();
            for (JsonMetadataSection section : sections.values()) {
                JSONUtils.replaceValue(result, section.getName(), section.content);
            }
            return result;
        }

    }

    public static class FileResource extends AbstractResource<FileResourceMetadata> {

        private File file;

        private File metadataFile;

        public FileResource(File file) {
            this.file = file;
        }

        @Override
        protected void deleteMatadata() {
            if (!metadataInited)
                initMetadata();
            deleteFile(metadataFile);
        }

        @Override
        protected void deleteResource() {
            deleteFile(file);
        }

        protected void deleteFile(File f) {
            if (f == null)
                return;
            if (!f.exists())
                return;
            if (!f.isDirectory())
                f.delete();
            else
                try {
                    org.apache.commons.io.FileUtils.deleteDirectory(f);
                } catch (IOException e) {
                    log.error("Error deleting directory [" + f.toPath().toString() + "]", e);
                }
        }

        @Override
        public InputStream getInputStream() {
            if (!exists())
                return null;
            if (file.isDirectory())
                return null;
            try {
                return file.toURI().toURL().openStream();
            } catch (IOException e) {
                FileResourceProvider.log.error("Error getting resource", e);
                return null;
            }
        }

        @Override
        public String getAsString() {
            if (!exists())
                return null;
            if (file.isDirectory())
                return "";
            String result = null;
            try (InputStream is = getInputStream()) {
                result = IOUtils.toString(is);
            } catch (IOException e) {
                FileResourceProvider.log.error("Error getting resource as string", e);
            }
            return result;
        }

        @Override
        public void save(InputStream inputStream) {
            if (!file.exists())
                try {
                    log.debug("\nMAKE DIRS", file.getParentFile().getAbsolutePath());
                    file.getParentFile().mkdirs();
                    // file.createNewFile();
                } catch (Exception e1) {
                    log.error("Error creating resource", e1);
                }
            if (file.isDirectory())
                return;
            try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
                FileChannel channel = fileOutputStream.getChannel();
                FileLock lock = channel.lock();
                byte[] bytes = IOUtils.toByteArray(inputStream);
                channel.write(ByteBuffer.wrap(bytes));

                lock.release();
                channel.close();
                IOUtils.closeQuietly(fileOutputStream);

            } catch (Exception e) {
                log.error("Error saving resource", e);
            }
        }

        @Override
        public boolean exists() {
            return file.exists();
        }

        @Override
        public URL getLocation() {
            try {
                return this.file.toURI().toURL();
            } catch (MalformedURLException e) {
                return null;
            }
        }

        @Override
        public boolean isFolder() {
            return file.isDirectory();
        }

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

        @Override
        public long getSize() {
            try {
                if (file.isFile())
                    return Files.size(file.toPath());
                return 0;
            } catch (IOException e) {
                log.warn("Error getting file size for [" + file.getAbsolutePath() + "]", e);
                return 0;
            }
        }

        @Override
        protected void doInitMetadata() {
            String metadataFileName = (file.isDirectory() ? "" : file.getName())
                    + "." + FileResourceProvider.METADATA_FILE_EXTENSION;
            Path metadataFilePath = Path.of(
                    (file.isDirectory()
                            ? file
                            : file.getParentFile()).toPath().toString(),
                    metadataFileName);
            metadataFile = metadataFilePath.toFile();
            if (metadataFile.exists())
                this.metadata = new FileResourceMetadata(metadataFile);
        }

        @Override
        protected FileResourceMetadata createMetadata() {
            if (!metadataFile.exists())
                this.metadata = new FileResourceMetadata(metadataFile);
            return this.metadata;
        }

        @Override
        public String getURL() {
            return null;
        }

    }

}
