package org.limine.snapper.processes;

import org.limine.snapper.formats.json1.SnapperEntry;
import org.limine.snapper.formats.snapper.SnapperProperty;
import org.limine.snapper.objects.Config;
import org.limine.snapper.objects.Output;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class SnapperReader {

    private final HashMap<String, SnapperEntry> snapperEntries;
    private String lastTimeID, preLastTimeID;

    public SnapperReader() {
        this.snapperEntries = new HashMap<>();
        this.lastTimeID = null;
        this.preLastTimeID = null;
        this.process();
    }

    private void process() {
        readList();
        // If no Snapper entry is found, verify whether "/.snapshots" is set up correctly.
        // Important: This prevents accidental deletion of all snapshot entries due to user error.
        if (snapperEntries.isEmpty()) {
            // Check if the "/.snapshots" directory exists.
            if (Utility.isDirectoryPresent("/.snapshots")) {
                // If it exists, verify that it is a valid Btrfs subvolume.
                if (Utility.runCommand("btrfs subvolume show /.snapshots", false, false)) {
                    if (countNumericFoldersWithSnapshot() > 0) {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException ignored) {
                        }
                        readList();
                        if (snapperEntries.isEmpty() && countNumericFoldersWithSnapshot() > 0) {
                            String errMessage = "Snapper list is empty, but \"/.snapshots\" is not empty. Snapper is too slow or has a bug.";
                            Utility.exitWithError(errMessage);
                            return;
                        }
                    }
                    Utility.warnMessage("You do not have a snapshot.");
                } else {
                    String errMessage = "\"/.snapshots\" is not Btrfs subvolume.";
                    Utility.exitWithError(errMessage);
                }
            } else {
                String errMessage = "\"/.snapshots\" is either not a directory or is missing.";
                Utility.exitWithError(errMessage);
            }
        }

        if (Config.USE_OPENSUSE_LAYOUT) {
            // If the last snapper ID contains the description "writable copy of #<ID>", then remove it
            SnapperEntry lastSnapperEntry = this.getLastSnapperEntry();
            if (lastSnapperEntry != null) {
                String description = lastSnapperEntry.properties().get(SnapperProperty.DESCRIPTION.replace);
                if (description != null && description.startsWith("writable copy of #")) {
                    this.snapperEntries.remove(lastTimeID);
                    this.lastTimeID = preLastTimeID;
                }
            }
        }
    }

    private void readList() {
        snapperEntries.clear();
        lastTimeID = null;
        Output output = Utility.getTextFromCommand(Config.SNAPPER_LIST_CMDLINE, true, null);
        if (output.isSuccess()) {
            for (String line : output.text()) {
                // Check if snapshot ID is number.
                if (!line.isEmpty() && Character.isDigit(line.charAt(0))) {
                    SnapperEntry entry = this.createSnapperEntry(line);
                    if (entry != null) {
                        preLastTimeID = lastTimeID;
                        snapperEntries.put(entry.timeId(), entry);
                        lastTimeID = entry.timeId();
                    }
                }
            }
        }
    }

    private SnapperEntry createSnapperEntry(String line) {
        String[] columns = line.split(",");
        if (columns.length >= 2) {
            // Trim each part to remove any leading/trailing whitespace
            int snapshotID = Integer.parseInt(columns[0].trim());
            String utcTime = columns[1].trim();
            if (snapshotID == 0) {
                return null;

            } else if (Utility.isTimeFormatNotValid(utcTime)) {
                Utility.exitWithError("Stopping the sync:", "because the Snapper time format has changed: " + utcTime);
                return null;
            }

            String description = "";
            if (columns.length >= 4) {
                description = columns[3];
            }
            String type = "";
            if (columns.length >= 3) {
                type = columns[2];
            }
            Map<String, String> properties = new HashMap<>();
            properties.put(SnapperProperty.DESCRIPTION.replace, description);
            properties.put(SnapperProperty.TYPE.replace, type);
            String localTime = Utility.convertUTCtoLocal(utcTime);
            return new SnapperEntry(snapshotID, utcTime, localTime, properties);

        } else {
            String errMessage = "Mismatch Snapper format output!";
            Utility.exitWithError(errMessage);
            return null;
        }
    }

    private int countNumericFoldersWithSnapshot() {
        Path path = Paths.get("/.snapshots");
        int count = 0;
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(path, Files::isDirectory)) {
            for (Path entry : stream) {
                String name = entry.getFileName().toString();
                if (name.isEmpty()) continue;
                boolean numeric = name.chars().allMatch(Character::isDigit);
                if (!numeric) continue;
                if (Files.isDirectory(entry.resolve("snapshot"))) {
                    count++;
                }
            }
        } catch (IOException ignored) {
        }
        return count;
    }


    public HashMap<String, SnapperEntry> getSnapperEntries() {
        return snapperEntries;
    }

    public SnapperEntry getLastSnapperEntry() {
        if (snapperEntries.isEmpty()) {
            return null;
        } else {
            return snapperEntries.get(lastTimeID);
        }
    }
}
