/*
 *   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.features.region;

import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.config.BauServer;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.RegionUtils;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.utils.RegionExtensionType;
import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.linkage.Linked;
import de.steamwar.linkage.LinkedInstance;
import de.steamwar.sql.BauweltMember;
import de.steamwar.sql.Punishment;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;

@Linked
public class TestblockCommand extends SWCommand {

    @LinkedInstance
    public BauServer bauServer;

    public TestblockCommand() {
        super("testblock", "tb", "dummy");
    }

    @Register
    public void schematicTestblockCommand(@Validator Player p, TestblockParameterType... testblockParameterTypes) {
        schematicTestblockCommand(p, null, testblockParameterTypes);
    }

    @Register
    public void schematicTestblockCommand(@Validator Player p, @Mapper("withPublic") SchematicNode node, TestblockParameterType... testblockParameterTypes) {
        Set<TestblockParameterType> testblockParameterTypesSet = new HashSet<>(Arrays.asList(testblockParameterTypes));
        boolean isExtension = testblockParameterTypesSet.contains(TestblockParameterType.EXTENSION);
        boolean isIgnoreAir = testblockParameterTypesSet.contains(TestblockParameterType.IGNORE_AIR);
        boolean isOnlyColor = testblockParameterTypesSet.contains(TestblockParameterType.ONLY_COLOR);
        boolean replaceTNT = testblockParameterTypesSet.contains(TestblockParameterType.TNT);
        boolean replaceWater = testblockParameterTypesSet.contains(TestblockParameterType.WATER);

        resetRegion(p, node, isExtension ? RegionExtensionType.EXTENSION : RegionExtensionType.NORMAL, isIgnoreAir, isOnlyColor, replaceTNT, replaceWater);
    }

    private void resetRegion(Player p, SchematicNode node, RegionExtensionType regionExtensionType, boolean ignoreAir, boolean onlyColors, boolean removeTNT, boolean removeWater) {
        Region region = regionCheck(p);
        if (region == null) return;

        if (node != null) {
            if (node.isDir()) {
                BauSystem.MESSAGE.send("ONLY_SCHEMS", p);
                return;
            }
        }

        if (!p.getUniqueId().equals(bauServer.getOwner())) {
            if (Punishment.isPunished(SteamwarUser.get(bauServer.getOwner()), Punishment.PunishmentType.NoSchemReceiving, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMRECEIVING", p, punishment.getEndTime()))) {
                return;
            }
            if (Punishment.isPunished(SteamwarUser.get(p.getUniqueId()), Punishment.PunishmentType.NoSchemSharing, punishment -> BauSystem.MESSAGE.send("REGION_TB_NO_SCHEMSHARING", p, punishment.getEndTime()))) {
                return;
            }
        }

        // Beta Tester enabled
        switch (BauServer.getInstance().getOwnerID()) {
            case 245:
            case 403:
            case 1898:
            case 3320:
            case 4603:
            case 5262:
            case 5399:
            case 6032:
            case 7862:
            case 11077:
            case 11888:
            case 12258:
            case 16009:
                if (node == null) {
                    node = region.getRegionData().getTestblockSchematic();
                } else {
                    region.getRegionData().setTestblockSchematic(node);
                }
                break;
            default:
                break;
        }

        PasteBuilder.ClipboardProvider clipboardProvider;
        if (node == null) {
            clipboardProvider = new PasteBuilder.FileProvider(region.getTestblockArea().getResetFile());
        } else {
            clipboardProvider = new PasteBuilder.SchematicProvider(node);
        }

        try {
            PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider)
                    .ignoreAir(ignoreAir)
                    .onlyColors(onlyColors)
                    .removeTNT(removeTNT)
                    .removeWater(removeWater)
                    .color(region.getFlags().get(Flag.COLOR).getWithDefault());
            region.getTestblockArea().reset(pasteBuilder, regionExtensionType == RegionExtensionType.EXTENSION);
            RegionUtils.message(region, "REGION_TB_DONE");
        } catch (SecurityException e) {
            BauSystem.MESSAGE.send("REGION_TB_ERROR", p);
            Bukkit.getLogger().log(Level.WARNING, "Failed testblock", e);
        }
    }

    @ClassMapper(value = TestblockParameterType.class, local = true)
    public TypeMapper<TestblockParameterType> testblockParameterTypeMapper() {
        Map<TestblockParameterType, List<String>> testblockParameterTypeListMap = new EnumMap<>(TestblockParameterType.class);
        for (TestblockParameterType value : TestblockParameterType.values()) {
            testblockParameterTypeListMap.put(value, value.getTabCompletes());
        }

        Map<String, TestblockParameterType> testblockParameterTypesMap = new HashMap<>();
        testblockParameterTypeListMap.forEach((k, v) -> v.forEach(s -> testblockParameterTypesMap.put(s, k)));

        return new TypeMapper<TestblockParameterType>() {
            @Override
            public TestblockParameterType map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
                return testblockParameterTypesMap.get(s);
            }

            @Override
            public List<String> tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) {
                Set<TestblockParameterType> testblockParameterTypeSet = new HashSet<>();
                previousArguments.getAll(TestblockParameterType.class).forEach(showModeType -> {
                    testblockParameterTypeSet.addAll(Arrays.asList(showModeType.getRemoved().get()));
                });
                Arrays.stream(previousArguments.userArgs).map(testblockParameterTypesMap::get).forEach(testblockParameterTypeSet::add);
                testblockParameterTypeSet.remove(null);

                Set<TestblockParameterType> removed = testblockParameterTypeSet.stream()
                        .map(TestblockParameterType::getRemoved)
                        .map(Supplier::get)
                        .flatMap(Arrays::stream)
                        .collect(Collectors.toSet());

                List<String> tabCompletes = new ArrayList<>();
                for (Map.Entry<TestblockParameterType, List<String>> entry : testblockParameterTypeListMap.entrySet()) {
                    if (removed.contains(entry.getKey()) || testblockParameterTypeSet.contains(entry.getKey())) {
                        continue;
                    }
                    tabCompletes.addAll(entry.getValue());
                }
                return tabCompletes;
            }
        };
    }

    private Region regionCheck(Player player) {
        Region region = Region.getRegion(player.getLocation());
        if (region.getTestblockArea().isEmpty()) {
            BauSystem.MESSAGE.send("REGION_TB_NO_REGION", player);
            return null;
        }
        return region;
    }

    @Mapper("withPublic")
    public TypeMapper<SchematicNode> nodeWithPublic() {
        return new TypeMapper<SchematicNode>() {
            @Override
            public List<String> tabCompletes(CommandSender commandSender, PreviousArguments previousArguments, String s) {
                List<String> stringList = new ArrayList<>(SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s));
                stringList.addAll(SchematicNode.getNodeTabcomplete(SteamwarUser.get(0), s));
                return stringList;
            }

            @Override
            public SchematicNode map(CommandSender commandSender, PreviousArguments previousArguments, String s) {
                SchematicNode node = SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s);
                if (node == null) {
                    node = SchematicNode.getNodeFromPath(SteamwarUser.get(0), s);
                }
                return node;
            }
        };
    }

    public enum TestblockParameterType {
        EXTENSION(Arrays.asList("-e", "-extension")),
        TNT(Arrays.asList("-t", "-tnt")),
        WATER(Arrays.asList("-w", "-water")),
        IGNORE_AIR(Arrays.asList("-ig", "-ignore_air")),
        ONLY_COLOR(Arrays.asList("-o", "-color", "-only_color"));

        @Getter
        private List<String> tabCompletes;

        @Getter
        private final Supplier<TestblockParameterType[]> removed;
        private AtomicReference<TestblockParameterType[]> cached = new AtomicReference<>();

        TestblockParameterType(List<String> tabCompletes, String... removed) {
            this.tabCompletes = tabCompletes;
            this.removed = () -> {
                if (cached.get() == null) {
                    TestblockParameterType[] showModeParameterTypes = new TestblockParameterType[removed.length];
                    for (int i = 0; i < removed.length; i++) {
                        showModeParameterTypes[i] = TestblockParameterType.valueOf(removed[i]);
                    }
                    cached.set(showModeParameterTypes);
                    return showModeParameterTypes;
                }
                return cached.get();
            };
        }
    }
}
