/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.bausystem.region.fixed;

import com.sk89q.worldedit.EditSession;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.region.*;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.TestblockMode;
import de.steamwar.bausystem.utils.FlatteningWrapper;
import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.bausystem.worlddata.WorldData;
import de.steamwar.core.Core;
import de.steamwar.sql.SchematicType;
import lombok.NonNull;
import org.bukkit.Bukkit;
import yapion.hierarchy.types.YAPIONObject;

import javax.annotation.Nullable;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

public class FixedRegion implements Region {

    private static final File backupFolder = new File(Bukkit.getWorlds().get(0).getWorldFolder(), "backup");
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd' 'HH:mm:ss");

    private final String name;
    private final UUID uuid;
    private final FixedFlagStorage flagStorage;
    private final Prototype prototype;
    private final String skin;

    private final Area area;
    private final Area build;
    private final Area testblock;
    private final int floorLevel;
    private final int waterLevel;
    private final GameModeConfig gameModeConfig;
    private final RegionData regionData;
    private final RegionHistory regionHistory = new RegionHistory.Impl(20);

    private final RegionBackups regionBackups = new RegionBackups() {
        @Override
        public Optional<Backup> create(BackupType backupType) {
            final File definedBackupFolder = new File(new File(backupFolder, prototype.getName()), name);
            //noinspection ResultOfMethodCallIgnored
            definedBackupFolder.mkdirs();

            File[] currentBackups = definedBackupFolder.listFiles();
            if (currentBackups != null && currentBackups.length >= 20) {
                List<File> files = new ArrayList<>(Arrays.asList(currentBackups));
                files.sort(Comparator.comparingLong(File::lastModified));
                while (files.size() >= 20) files.remove(0).delete();
            }

            final File backupFile = new File(definedBackupFolder, LocalDateTime.now().format(formatter) + ".schem");
            Point minPoint = area.getMinPoint(false);
            Point maxPoint = area.getMaxPoint(false);
            if (!FlatteningWrapper.impl.backup(minPoint, maxPoint, backupFile)) {
                return Optional.empty();
            }
            return Optional.of(new BackupImpl(backupFile));
        }

        @Override
        public @NonNull List<Backup> list() {
            final File definedBackupFolder = new File(new File(backupFolder, prototype.getName()), name);
            //noinspection ResultOfMethodCallIgnored
            definedBackupFolder.mkdirs();

            File[] currentBackups = definedBackupFolder.listFiles();
            if (currentBackups == null || currentBackups.length == 0) {
                return Collections.emptyList();
            }
            List<File> files = new ArrayList<>(Arrays.asList(currentBackups));
            files.sort(Comparator.comparingLong(File::lastModified));
            return files.stream().map(BackupImpl::new).collect(Collectors.toList());
        }

        @Nullable
        @Override
        public Backup get(String name) {
            final File definedBackupFolder = new File(new File(backupFolder, prototype.getName()), FixedRegion.this.name);
            //noinspection ResultOfMethodCallIgnored
            definedBackupFolder.mkdirs();
            File[] files = definedBackupFolder.listFiles((dir, s) -> s.equals(name.replace('_', ' ') + ".schem"));
            if (files == null || files.length == 0) return null;
            return new BackupImpl(files[0]);
        }
    };

    private class BackupImpl extends RegionBackups.Backup {

        private final File file;

        public BackupImpl(File file) {
            super(RegionBackups.BackupType.AUTOMATIC, file.getName().replace(' ', '_').replace(".schem", ""), flagStorage);
            this.file = file;
        }

        @Override
        public boolean load() {
            if (!file.exists()) return false;
            EditSession editSession = new PasteBuilder(new PasteBuilder.FileProvider(file))
                    .pastePoint(area.getMinPoint(false).add(prototype.getSizeX() / 2, 0, prototype.getSizeZ() / 2))
                    .minPoint(area.getMinPoint(false))
                    .maxPoint(area.getMaxPoint(false))
                    .waterLevel(waterLevel)
                    .run();
            regionHistory.remember(editSession);
            return true;
        }

        @Override
        public void delete() {
            file.delete();
        }
    }

    public FixedRegion(String name, FixedFlagStorage flagStorage, Prototype prototype, YAPIONObject regionConfig, YAPIONObject regionData) {
        this.name = name;
        uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));
        this.flagStorage = flagStorage;
        this.prototype = prototype;
        this.skin = prototype.getDefaultSkin();

        Point minPoint;
        if (regionConfig.containsKey("minX", Integer.class) && regionConfig.containsKey("minY", Integer.class) && regionConfig.containsKey("minZ", Integer.class)) {
            minPoint = new Point(regionConfig.getInt("minX"), regionConfig.getInt("minY"), regionConfig.getInt("minZ"));
        } else {
            throw new IllegalArgumentException("minX and minY and minZ are required");
        }
        Point maxPoint = minPoint.add(prototype.getSizeX() - 1, prototype.getSizeY() - 1, prototype.getSizeZ() - 1);

        area = new Area() {
            @Override
            public @NonNull Point getMinPoint(boolean extension) {
                return minPoint;
            }

            @Override
            public @NonNull Point getMaxPoint(boolean extension) {
                return maxPoint;
            }

            @Override
            public @NonNull Point getCopyPoint() {
                return minPoint;
            }

            @Nullable
            @Override
            public File getResetFile() {
                return prototype.getSkinMap().get(skin).getSchematicFile();
            }

            @Override
            public void reset(PasteBuilder pasteBuilder, boolean extension) {
                pasteBuilder.reset(extension)
                        .minPoint(getMinPoint(extension))
                        .maxPoint(getMaxPoint(extension))
                        .waterLevel(waterLevel);
                if (pasteBuilder.getClipboardProvider().is(PasteBuilder.SchematicProvider.class)) {
                    SchematicType schematicType = pasteBuilder.getClipboardProvider().as(PasteBuilder.SchematicProvider.class).getSchematic().getSchemtype();
                    pasteBuilder.rotate(schematicType.fightType() || schematicType.check());
                }
                pasteBuilder.pastePoint(minPoint.add(prototype.getSizeX() / 2, 0, prototype.getSizeZ() / 2));
                regionHistory.remember(pasteBuilder.run());
            }
        };

        if (prototype.getBuild() != null) {
            Point minPointBuild = minPoint.add(prototype.getBuild().getOffsetX(), prototype.getBuild().getOffsetY(), prototype.getBuild().getOffsetZ());
            Point maxPointBuild = minPointBuild.add(prototype.getBuild().getSizeX() - 1, prototype.getBuild().getSizeY() - 1, prototype.getBuild().getSizeZ() - 1);

            Point minPointBuildExtension = minPointBuild.subtract(prototype.getBuild().getExtensionNegativeX(), prototype.getBuild().getExtensionNegativeY(), prototype.getBuild().getExtensionNegativeZ());
            Point maxPointBuildExtension = maxPointBuild.add(prototype.getBuild().getExtensionPositiveX(), prototype.getBuild().getExtensionPositiveY(), prototype.getBuild().getExtensionPositiveZ());

            Point copyPoint;
            if (!prototype.getBuild().isHasCopyPoint() && (prototype.getCopyPointOffsetX() != 0 || prototype.getCopyPointOffsetY() != 0 || prototype.getCopyPointOffsetZ() != 0)) {
                copyPoint = minPoint.add(prototype.getCopyPointOffsetX(), prototype.getCopyPointOffsetY(), prototype.getCopyPointOffsetZ());
            } else if (prototype.getBuild().getCopyOffsetX() != 0 || prototype.getBuild().getCopyOffsetY() != 0 || prototype.getBuild().getCopyOffsetZ() != 0) {
                copyPoint = minPointBuild.add(prototype.getBuild().getCopyOffsetX(), prototype.getBuild().getCopyOffsetY(), prototype.getBuild().getCopyOffsetZ());
            } else {
                copyPoint = minPointBuild.add(prototype.getBuild().getSizeX() / 2, 0, prototype.getBuild().getSizeZ());
            }

            build = new Area() {
                @Override
                public @NonNull Point getMinPoint(boolean extension) {
                    return extension ? minPointBuildExtension : minPointBuild;
                }

                @Override
                public @NonNull Point getMaxPoint(boolean extension) {
                    return extension ? maxPointBuildExtension : maxPointBuild;
                }

                @Override
                public @NonNull Point getCopyPoint() {
                    return copyPoint;
                }

                @Nullable
                @Override
                public File getResetFile() {
                    return prototype.getSkinMap().get(skin).getBuildSchematicFile();
                }

                @Override
                public void reset(PasteBuilder pasteBuilder, boolean extension) {
                    pasteBuilder.reset(extension)
                            .minPoint(getMinPoint(extension))
                            .maxPoint(getMaxPoint(extension))
                            .waterLevel(waterLevel);
                    if (pasteBuilder.getClipboardProvider().is(PasteBuilder.SchematicProvider.class)) {
                        SchematicType schematicType = pasteBuilder.getClipboardProvider().as(PasteBuilder.SchematicProvider.class).getSchematic().getSchemtype();
                        pasteBuilder.rotate(schematicType.fightType() || schematicType.check());
                    }
                    pasteBuilder.pastePoint(minPointBuild.add(prototype.getBuild().getSizeX() / 2, 0, prototype.getBuild().getSizeZ() / 2));
                    regionHistory.remember(pasteBuilder.run());
                }
            };
        } else {
            build = Area.EMPTY;
        }

        if (prototype.getTestblock() != null) {
            Point minPointTestblock = minPoint.add(prototype.getTestblock().getOffsetX(), prototype.getTestblock().getOffsetY(), prototype.getTestblock().getOffsetZ());
            Point maxPointTestblock = minPointTestblock.add(prototype.getTestblock().getSizeX() - 1, prototype.getTestblock().getSizeY() - 1, prototype.getTestblock().getSizeZ() - 1);

            Point minPointTestblockExtension = minPointTestblock.subtract(prototype.getTestblock().getExtensionNegativeX(), prototype.getTestblock().getExtensionNegativeY(), prototype.getTestblock().getExtensionNegativeZ());
            Point maxPointTestblockExtension = maxPointTestblock.add(prototype.getTestblock().getExtensionPositiveX(), prototype.getTestblock().getExtensionPositiveY(), prototype.getTestblock().getExtensionPositiveZ());

            Point copyPoint;
            if (prototype.getTestblock().getCopyOffsetX() != 0 || prototype.getTestblock().getCopyOffsetY() != 0 || prototype.getTestblock().getCopyOffsetZ() != 0) {
                copyPoint = minPointTestblock.add(prototype.getTestblock().getCopyOffsetX(), prototype.getTestblock().getCopyOffsetY(), prototype.getTestblock().getCopyOffsetZ());
            } else {
                copyPoint = minPointTestblock.add(prototype.getTestblock().getSizeX() / 2, 0, -1);
            }

            testblock = new Area() {
                @Override
                public @NonNull Point getMinPoint(boolean extension) {
                    return extension ? minPointTestblockExtension : minPointTestblock;
                }

                @Override
                public @NonNull Point getMaxPoint(boolean extension) {
                    return extension ? maxPointTestblockExtension : maxPointTestblock;
                }

                @Override
                public @NonNull Point getCopyPoint() {
                    return copyPoint;
                }

                @Nullable
                @Override
                public File getResetFile() {
                    return prototype.getSkinMap().get(skin).getTestblockSchematicFile();
                }

                @Override
                public void reset(PasteBuilder pasteBuilder, boolean extension) {
                    pasteBuilder.reset(extension)
                            .minPoint(getMinPoint(extension))
                            .maxPoint(getMaxPoint(extension))
                            .waterLevel(waterLevel);
                    if (pasteBuilder.getClipboardProvider().is(PasteBuilder.SchematicProvider.class)) {
                        SchematicType schematicType = pasteBuilder.getClipboardProvider().as(PasteBuilder.SchematicProvider.class).getSchematic().getSchemtype();
                        pasteBuilder.rotate(schematicType.fightType() || schematicType.check());
                    }
                    Point pastePoint = minPointTestblock.add(prototype.getTestblock().getSizeX() / 2, 0, prototype.getTestblock().getSizeZ() / 2);
                    if (pasteBuilder.getClipboardProvider().is(PasteBuilder.SchematicProvider.class)) {
                        SchematicType schematicType = pasteBuilder.getClipboardProvider().as(PasteBuilder.SchematicProvider.class).getSchematic().getSchemtype();
                        if (schematicType.getKuerzel().equalsIgnoreCase("wg")) {
                            pastePoint = pastePoint.add(0, 0, 1);
                        }
                        if (schematicType.getKuerzel().equalsIgnoreCase("ws")) {
                            pastePoint = pastePoint.add(-1, 0, 1);
                        }
                        if (schematicType.getKuerzel().equalsIgnoreCase("as")) {
                            pastePoint = pastePoint.add(-1, 0, 1);
                        }
                    }
                    pasteBuilder.pastePoint(pastePoint);
                    regionHistory.remember(pasteBuilder.run());
                }
            };
        } else {
            testblock = Area.EMPTY;
        }

        if (!testblock.isEmpty() && !build.isEmpty()) {
            if (testblock.getMinPoint(false).getZ() > build.getMinPoint(false).getZ()) {
                flagStorage.getBackedMap().put(Flag.TESTBLOCK, TestblockMode.SOUTH);
            } else {
                flagStorage.getBackedMap().put(Flag.TESTBLOCK, TestblockMode.NORTH);
            }
        } else {
            flagStorage.getBackedMap().put(Flag.TESTBLOCK, TestblockMode.NO_VALUE);
        }

        floorLevel = prototype.getFloorOffset() != 0 ? minPoint.getY() + prototype.getFloorOffset() : 0;
        waterLevel = prototype.getWaterOffset() != 0 ? minPoint.getX() + prototype.getWaterOffset() : 0;

        File found = null;
        File baseFile = new File(BauSystem.getInstance().getDataFolder().getParentFile(), "FightSystem");
        for (int version = Core.getVersion(); version >= 15; version--) {
            File specific = new File(baseFile, prototype.getDisplayName() + version + ".yml");
            if (specific.exists()) {
                found = specific;
                break;
            }
        }
        this.gameModeConfig = new GameModeConfig(found);
        this.regionData = new RegionData.RegionDataImpl(regionData, WorldData::write);
    }

    @Override
    public @NonNull UUID getID() {
        return uuid;
    }

    @Override
    public @NonNull RegionType getType() {
        return RegionType.NORMAL;
    }

    @Override
    public @NonNull FlagStorage getFlags() {
        return flagStorage;
    }

    @Override
    public @NonNull Area getArea() {
        return area;
    }

    @Override
    public @NonNull Area getBuildArea() {
        return build;
    }

    @Override
    public @NonNull Area getTestblockArea() {
        return testblock;
    }

    @Override
    public @NonNull GameModeConfig getGameModeConfig() {
        return gameModeConfig;
    }

    @Override
    public @NonNull RegionHistory getHistory() {
        return regionHistory;
    }

    @Override
    public @NonNull RegionBackups getBackups() {
        return regionBackups;
    }

    @Override
    public @NonNull RegionData getRegionData() {
        return regionData;
    }
}
