package ru.kirillius.pf.sdn.External.API;

import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import ru.kirillius.pf.sdn.core.Context;
import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle;
import ru.kirillius.pf.sdn.core.Subscription.RepositoryConfig;
import ru.kirillius.pf.sdn.core.Subscription.SubscriptionProvider;
import ru.kirillius.pf.sdn.core.Util.HashUtil;
import ru.kirillius.utils.logging.SystemLogger;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.StringJoiner;

/**
 * Subscription provider that pulls JSON resource bundles from a Git repository cache.
 */
public class GitSubscription implements SubscriptionProvider {
    private final static String CTX = GitSubscription.class.getSimpleName();
    private final Context context;

    /**
     * Creates the provider using the shared application context.
     */
    public GitSubscription(Context context) {
        this.context = context;
    }

    /**
     * Runs {@code git fetch} and {@code git pull} when remote updates are available.
     */
    private static void checkAndPullUpdates(Git git) {
        try {

            var fetchResult = git.fetch()
                    .setCheckFetchedObjects(true)
                    .call();

            if (fetchResult.getTrackingRefUpdates() != null && !fetchResult.getTrackingRefUpdates().isEmpty()) {
                SystemLogger.message("Downloading updates...", CTX);
                var pullResult = git.pull().call();

                if (pullResult.isSuccessful()) {
                    SystemLogger.message("Git pull is successful", CTX);

                    // Проверяем были ли обновлены файлы
                    if (pullResult.getFetchResult() != null && !pullResult.getFetchResult().getTrackingRefUpdates().isEmpty()) {
                        var updatedFiles = new StringJoiner("\n");
                        pullResult.getFetchResult().getTrackingRefUpdates().forEach(refUpdate -> updatedFiles.add("   - " + refUpdate.getLocalName() +
                                " : " + refUpdate.getOldObjectId().abbreviate(7).name() +
                                " -> " + refUpdate.getNewObjectId().abbreviate(7).name()));
                        SystemLogger.message("Updated files: " + updatedFiles, CTX);
                    }
                } else {
                    SystemLogger.error("Download failed", CTX);
                }
            } else {
                SystemLogger.message("There is no updates", CTX);
            }
        } catch (Exception e) {
            SystemLogger.error("Failed to fetch & pull repository", CTX, e);
        }

    }

    /**
     * Clones the repository into the given path.
     */
    private static Git cloneRepository(String REPO_URL, File path) throws GitAPIException {
        SystemLogger.message("Cloning repository " + REPO_URL, CTX);
        return Git.cloneRepository()
                .setURI(REPO_URL)
                .setDirectory(path)
                .setCloneAllBranches(true)
                .call();
    }

    /**
     * Opens an existing repository located at the given directory.
     */
    private static Git openRepository(File repoDir) throws IOException {
        var builder = new FileRepositoryBuilder();
        var repository = builder.setGitDir(new File(repoDir, ".git"))
                .readEnvironment()
                .findGitDir()
                .build();
        return new Git(repository);
    }

    /**
     * Returns whether the directory contains a Git repository.
     */
    private static boolean isGitRepository(File directory) {
        if (!directory.exists() || !directory.isDirectory()) {
            return false;
        }

        var gitDir = new File(directory, ".git");
        return gitDir.exists() && gitDir.isDirectory();
    }

    /**
     * Clones or updates the configured repository and loads resource bundles from the {@code resources} directory.
     */
    @Override
    public Map<String, NetworkResourceBundle> getResources(RepositoryConfig config) {
        try {
            var repoDir = new File(context.getConfig().getCacheDirectory(), "git/" + HashUtil.md5(config.getName()));

            if (!repoDir.exists()) {
                if (!repoDir.mkdirs()) {
                    throw new IOException("Unable to create directory: " + repoDir.getAbsolutePath());
                }
            }

            var existing = isGitRepository(repoDir);
            var repository = existing ? openRepository(repoDir) : cloneRepository(config.getSource(), repoDir);
            if (existing) {
                SystemLogger.message("Fetching git repository " + config.getName() + " (" + config.getSource() + ")", CTX);
                checkAndPullUpdates(repository);
            }
            repository.close();

            if(!config.getScript().isBlank()){
                try (var shell = new ShellExecutor(ShellExecutor.Config.builder().useSSH(false).build())) {
                    var result = shell.executeCommand(new String[]{
                            config.getScript(),
                            repoDir.getCanonicalPath()
                    });
                    SystemLogger.message("Shell command execution result:"+result, CTX);
                }
            }

            var resourcesDir = new File(repoDir, "resources");
            if (!resourcesDir.exists()) {
                SystemLogger.error(resourcesDir + " is not exist in repo  (" + config.getSource() + ")", CTX);
                return Collections.emptyMap();
            }

            return LocalFilesystemSubscription.scanLocalDirectory(resourcesDir);
        } catch (Exception e) {
            SystemLogger.error("Error while reading git repository", CTX, e);
            throw new RuntimeException(e);
        }
    }
}
