/*
 *   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.velocitycore.discord;

import it.unimi.dsi.fastutil.Pair;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.ScheduledEvent;
import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
import net.dv8tion.jda.internal.interactions.CommandDataImpl;
import org.jetbrains.annotations.NotNull;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class VacationCommand extends ListenerAdapter {

    private final Guild guild = Objects.requireNonNull(DiscordBot.getInstance().getJda().getGuildById(1241489896909180998L));

    public final CommandDataImpl COMMAND = new CommandDataImpl("vacation", "Verwalte deinen Urlaub");

    public VacationCommand() {
        COMMAND.addSubcommands(new SubcommandData("create", "Erstelle deinen Urlaub")
                .addOptions(new OptionData(OptionType.STRING, "from", "Datum (TT.MM.JJJJ)", true),
                        new OptionData(OptionType.STRING, "to", "Datum (TT.MM.JJJJ)", true),
                        new OptionData(OptionType.STRING, "reason", "Abwesenheitsnotiz", false)));
        COMMAND.addSubcommands(new SubcommandData("delete", "Lösche deinen Urlaub")
                .addOptions(new OptionData(OptionType.STRING, "vacation", "Dein Urlaub", true, true)));
    }

    @Override
    public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) {
        if (!event.getName().equals("vacation")) return;
        switch (event.getSubcommandName()) {
            case "create":
                createVacation(event);
                break;
            case "delete":
                deleteVacation(event);
                break;
            default:
                break;
        }
    }

    public static final DateTimeFormatter PARSER = DateTimeFormatter.ofPattern("dd.MM.uuuu");

    private void createVacation(SlashCommandInteractionEvent event) {
        InteractionHook interactionHook = event.deferReply(true).complete();
        String from = event.getOption("from").getAsString();
        String to = event.getOption("to").getAsString();
        String reason = event.getOption("reason", "", OptionMapping::getAsString);

        LocalDateTime fromDate;
        try {
            fromDate = LocalDate.parse(from, PARSER).atStartOfDay();
        } catch (DateTimeParseException e) {
            interactionHook.editOriginal("Das Datumsformat ist falsch! Bitte verwenden Sie TT.MM.JJJJ für den ersten Tag deines Urlaubs").queue();
            return;
        }
        if (fromDate == null) {
            interactionHook.editOriginal("Das Datumsformat ist falsch! Bitte verwenden Sie TT.MM.JJJJ für den ersten Tag deines Urlaubs").queue();
            return;
        }
        if (!fromDate.isAfter(LocalDateTime.now())) {
            interactionHook.editOriginal("Bitte gib ein Datum in der Zukunft an!").queue();
            return;
        }

        LocalDateTime toDate;
        try {
            toDate = LocalDate.parse(to, PARSER).atTime(23, 59, 59);
        } catch (DateTimeParseException e) {
            interactionHook.editOriginal("Das Datumsformat ist falsch! Bitte verwenden Sie TT.MM.JJJJ für den letzten Tag deines Urlaubs").queue();
            return;
        }
        if (toDate == null) {
            interactionHook.editOriginal("Das Datumsformat ist falsch! Bitte verwenden Sie TT.MM.JJJJ für den letzten Tag deines Urlaubs").queue();
            return;
        }
        if (!toDate.isAfter(LocalDateTime.now())) {
            interactionHook.editOriginal("Bitte gib ein Datum in der Zukunft an!").queue();
            return;
        }
        if (!toDate.isAfter(fromDate)) {
            interactionHook.editOriginal("Bitte gib ein Datum nach dem ersten Urlaubstag an!").queue();
            return;
        }

        if (reason.length() > 1000) {
            interactionHook.editOriginal("Die Abwesenheitsnotiz darf nicht länger als 1000 Zeichen sein!").queue();
            return;
        }

        guild.createScheduledEvent(
                        "Urlaub " + event.getMember().getEffectiveName(),
                        event.getMember().getId(),
                        OffsetDateTime.of(fromDate, ZoneId.of("Europe/Berlin").getRules().getOffset(fromDate)),
                        OffsetDateTime.of(toDate, ZoneId.of("Europe/Berlin").getRules().getOffset(toDate))
                )
                .setDescription(reason)
                .onSuccess(scheduledEvent -> {
                    interactionHook.editOriginal("Urlaub erstellt!").queue();
                })
                .onErrorMap(throwable -> {
                    interactionHook.editOriginal("Urlaub konnte nicht erstellt werden!").queue();
                    return null;
                })
                .queue();
    }

    private void deleteVacation(SlashCommandInteractionEvent event) {
        InteractionHook interactionHook = event.deferReply(true).complete();
        String eventId = event.getOption("vacation").getAsString();
        ScheduledEvent scheduledEvent = guild.getScheduledEventById(eventId);
        if (scheduledEvent == null) {
            interactionHook.editOriginal("Konnte den Urlaub nicht finden!").queue();
            return;
        }
        scheduledEvent.delete()
                .onSuccess(unused -> {
                    interactionHook.editOriginal("Urlaub gelöscht!").queue();
                })
                .onErrorMap(throwable -> {
                    interactionHook.editOriginal("Urlaub konnte nicht gelöscht werden!").queue();
                    return null;
                })
                .queue();
    }

    @Override
    public void onCommandAutoCompleteInteraction(@NotNull CommandAutoCompleteInteractionEvent event) {
        if (!event.getName().equals("vacation")) return;
        switch (event.getFocusedOption().getName()) {
            case "vacation":
                listVacations(event);
                break;
            default:
                break;
        }
    }

    private void listVacations(CommandAutoCompleteInteractionEvent event) {
        String vacation = event.getOption("vacation").getAsString();
        List<Command.Choice> choices = guild.getScheduledEvents()
                .stream()
                .filter(scheduledEvent -> scheduledEvent.getLocation().equals(event.getMember().getId()))
                .map(scheduledEvent -> {
                    StringBuilder st = new StringBuilder();
                    st.append(String.format("%02d", scheduledEvent.getStartTime().getDayOfMonth())).append(".");
                    st.append(String.format("%02d", scheduledEvent.getStartTime().getMonthValue())).append(".");
                    st.append(scheduledEvent.getStartTime().getYear());
                    st.append(" - ");
                    st.append(String.format("%02d", scheduledEvent.getEndTime().getDayOfMonth())).append(".");
                    st.append(String.format("%02d", scheduledEvent.getEndTime().getMonthValue())).append(".");
                    st.append(scheduledEvent.getEndTime().getYear());
                    return Pair.of(scheduledEvent, st.toString());
                })
                .filter(pair -> pair.right().startsWith(vacation))
                .limit(25)
                .map(pair -> {
                    return new Command.Choice(pair.right(), pair.left().getId());
                })
                .collect(Collectors.toList());
        event.replyChoices(choices)
                .queue();
    }
}
