/*-
 * ~~~~~~licensing~~~~~~
 * artifact-loader
 * ==========
 * 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.platform.initializer.artifactloader;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.entaxy.esb.platform.runtime.core.initializer.api.Initialized;
import ru.entaxy.platform.base.support.SimpleFileWatcher;
import ru.entaxy.platform.core.artifact.Artifact;
import ru.entaxy.platform.core.artifact.Artifacts;
import ru.entaxy.platform.core.artifact.DeployedArtifact;
import ru.entaxy.platform.core.artifact.service.ArtifactService;

@Component(service = ArtifactDeployer.class, immediate = true)
public class ArtifactDeployer implements SimpleFileWatcher.WatcherCallback {

    public static final Logger log = LoggerFactory.getLogger(ArtifactDeployer.class);

    public static final String ROOT_PATH = "init/artifacts";

    public static final String LOCAL_PATH = "local";

    public static final String SHARED_PATH = "shared";

    @Reference(cardinality = ReferenceCardinality.MANDATORY, policy = ReferencePolicy.DYNAMIC,
            policyOption = ReferencePolicyOption.GREEDY, target = "(id=repositories)")
    volatile Initialized repositoriesInitializedMarker;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    ArtifactService artifactService;

    protected ExecutorService executor = Executors.newSingleThreadExecutor();

    protected SimpleFileWatcher artifactWatcher = null;

    protected Path rootPath;

    protected static Map<String, Boolean> sources = new HashMap<>();

    protected static Map<String, Boolean> pathPrefixes = new HashMap<>();

    @Activate
    public void activate() {

        sources.put(LOCAL_PATH, false);
        sources.put(SHARED_PATH, true);

    }

    public void start() {

        Path watchPath = Paths.get(System.getProperty("karaf.etc"), ROOT_PATH);
        try {
            artifactWatcher = new SimpleFileWatcher(watchPath);
            artifactWatcher.setWatcherCallback(this);
            executor.submit(artifactWatcher);
        } catch (IOException e) {
            log.error("FAILED creating file watcher for ArtifactDeployer", e);
        }

        // do the initial job

        File rootFolder = new File(System.getProperty("karaf.etc"));
        if (rootFolder.exists()) {
            rootPath = Paths.get(rootFolder.getAbsolutePath(), ROOT_PATH);
            log.debug("\n\t-->> Root path: [{}]", rootPath.toString());

            for (Entry<String, Boolean> entry : sources.entrySet()) {
                Path currentPath = Paths.get(rootPath.toString(), entry.getKey());
                pathPrefixes.put(currentPath.toAbsolutePath().toString(), entry.getValue());
            }

            scan(rootPath);

        }

    }

    protected static String getPrefix(File f) {
        String filePath = f.getAbsolutePath();
        for (String prefix : pathPrefixes.keySet())
            if (filePath.startsWith(prefix))
                return prefix;
        return null;
    }

    protected static class FileData {

        public File file;
        public String prefix;
        public boolean isShared;

        public Artifact artifact;

        public DeployedArtifact deployedArtifact;

        public FileData(File f) {
            file = f;
            prefix = getPrefix(f);
            isShared = pathPrefixes.getOrDefault(prefix, false);
        }

        public FileData setArtifact(Artifact artifact) {
            this.artifact = artifact;
            return this;
        }

        public FileData setDeployedArtifact(DeployedArtifact deployedArtifact) {
            this.deployedArtifact = deployedArtifact;
            return this;
        }



    }

    protected void scan(Path pathToScan) {

        if (!pathToScan.toFile().exists() || !pathToScan.toFile().isDirectory())
            return;

        log.info("SCANNING: [{}]", pathToScan);

        try (Stream<Path> pathStream = Files.walk(pathToScan)) {
            List<FileData> fileDataList = pathStream
                    .map(p -> p.toFile())
                    .filter(f -> f.exists())
                    .filter(f -> !f.isDirectory())
                    .map(f -> new FileData(f))
                    .filter(fd -> fd.prefix != null)
                    .map(fd -> fd.setArtifact(Artifacts.fromFile(fd.file, fd.prefix)))
                    .filter(fd -> fd.artifact != null)
                    .collect(Collectors.toList());
            List<FileData> deployed = fileDataList.stream()
                    .map(fd -> fd.setDeployedArtifact(fd.isShared ? artifactService.deployShared(fd.artifact)
                            : artifactService.deployLocal(fd.artifact)))
                    .filter(fd -> (fd.deployedArtifact != null))
                    .collect(Collectors.toList());
            for (FileData fd : deployed)
                log.info("Deployed [initial] {} artifact: [{}]", fd.isShared ? "shared" : "local",
                        fd.deployedArtifact.getLocation());
        } catch (IOException e) {
            log.error("Error processing files", e);
        }

    }

    @Deactivate
    public void deactivate() {
        executor.shutdown();
        if (artifactWatcher != null) {
            artifactWatcher.close();
        }
    }

    /* -- WatcherCallback implementation */

    @SuppressWarnings("unchecked")
    @Override
    public void fileCreated(WatchEvent<?> event, Path directory) {
        Path name = ((WatchEvent<Path>) event).context();
        Path fileFullPath = directory.resolve(name);

        Path fileRelPath = rootPath.relativize(fileFullPath);
        Path fileRelTargetPath = null;
        Path fileRootPath = null;

        boolean deployShared = true;

        // check if it's shared artifact
        if (fileRelPath != null && fileRelPath.toString().startsWith(SHARED_PATH)) {
            fileRootPath = Paths.get(rootPath.toString(), SHARED_PATH);
            try {
                fileRelPath = fileRootPath.relativize(fileFullPath);
                log.debug("NEW SHARED FILE: [{}] -> [{}]", fileFullPath, fileRelPath);
            } catch (IllegalArgumentException ignore) {
                // not shared artifact
            }
        } else if (fileRelPath != null && fileRelPath.toString().startsWith(LOCAL_PATH)) {
            fileRootPath = Paths.get(rootPath.toString(), LOCAL_PATH);
            try {
                fileRelPath = fileRootPath.relativize(fileFullPath);
                log.debug("NEW SHARED FILE: [{}] -> [{}]", fileFullPath, fileRelPath);
                deployShared = false;
            } catch (IllegalArgumentException ignore) {
                // not shared artifact
            }
        } else {
            log.warn("File NOT LOCAL or SHARED -> ingoring: [{}]", fileFullPath);
            return;
        }

        Artifact artifact = Artifacts.fromFile(fileFullPath.toFile(), fileRootPath.toString());
        DeployedArtifact deployedArtifact =
                deployShared ? artifactService.deployShared(artifact) : artifactService.deployLocal(artifact);
        log.info("Deployed {} artifact: [{}]", deployShared ? "shared" : "local", deployedArtifact.getLocation());
    }

    @Override
    public void directoryCreated(WatchEvent<?> event, Path directory) {

        Path name = ((WatchEvent<Path>) event).context();
        Path dirFullPath = directory.resolve(name);

        log.info("DIRECTORY CREATED: [{}]", dirFullPath.toAbsolutePath().toString());

        scan(dirFullPath);
    }
}
