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

import de.steamwar.command.PreviousArguments;
import de.steamwar.command.SWCommand;
import de.steamwar.command.TypeMapper;
import de.steamwar.command.TypeValidator;
import de.steamwar.linkage.Linked;
import de.steamwar.messages.Chatter;
import de.steamwar.messages.Message;
import de.steamwar.messages.PlayerChatter;
import de.steamwar.sql.Event;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.Team;
import de.steamwar.sql.TeamTeilnahme;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.inventory.SWItem;
import de.steamwar.velocitycore.inventory.SWListInv;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;

import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static de.steamwar.persistent.Storage.teamInvitations;

@Linked
public class TeamCommand extends SWCommand {

    public TeamCommand() {
        super("team");
    }

    @Register(noTabComplete = true)
    public void help(Chatter sender, String... args) {
        helpMessages(sender, "TEAM_HELP_HEADER", "TEAM_HELP_LIST", "TEAM_HELP_INFO");

        SteamwarUser user = sender.user();
        if (user.getTeam() == 0) {
            helpMessages(sender, "TEAM_HELP_CREATE", "TEAM_HELP_JOIN");
        } else {
            helpMessages(sender, "TEAM_HELP_CHAT", "TEAM_HELP_EVENT", "TEAM_HELP_LEAVE");

            if (user.isLeader()) {
                helpMessages(sender, "TEAM_HELP_INVITE", "TEAM_HELP_REMOVE", "TEAM_HELP_KUERZEL", "TEAM_HELP_NAME", "TEAM_HELP_COLOR", "TEAM_HELP_LEADER", "TEAM_HELP_STEP_BACK", "TEAM_HELP_SERVER");
            }
        }
    }

    private void helpMessages(Chatter sender, String... messages) {
        for (String message : messages) {
            sender.system(message);
        }
    }

    @Register(value = "create", description = "TEAM_CREATE_USAGE")
    public void create(@Validator("isNotInTeam") Chatter sender, @Length(min = 2, max = 4) @ErrorMessage("TEAM_KUERZEL_LENGTH") String kuerzel, @Length(min = 4, max = 15) @ErrorMessage("TEAM_NAME_LENGTH") String name) {
        SteamwarUser user = sender.user();
        Team team = Team.byId(user.getTeam());

        if (checkTeamKuerzel(sender, team, kuerzel) || checkTeamName(sender, team, name)) {
            return;
        }

        Team.create(kuerzel, name);
        user.setTeam(Team.get(kuerzel).getTeamId());
        user.setLeader(true);
        sender.system("TEAM_CREATE_CREATED", name);
    }

    @Register("join")
    public void join(@Validator("isNotInTeam") Chatter sender, String... args) {
        SteamwarUser user = sender.user();

        if (notDuringEvent(sender)) return;

        if (!teamInvitations.containsKey(user.getId())) {
            sender.system("TEAM_JOIN_NO_INVITE");
            return;
        }

        List<Integer> invs = teamInvitations.get(user.getId());
        Integer t = null;

        if (invs.size() == 1) {
            t = invs.get(0);
        } else {
            if (args.length != 1) {
                sender.system("TEAM_JOIN_USAGE");
                StringBuilder sb = new StringBuilder();
                for (int inv : invs) {
                    Team team = Team.byId(inv);
                    sb.append(team.getTeamName()).append(" ");
                }
                sender.system("TEAM_JOIN_INVITED", sb.toString());
                return;
            }

            for (int inv : invs) {
                Team team = Team.byId(inv);
                if (team.getTeamKuerzel().equalsIgnoreCase(args[0]) || team.getTeamName().equalsIgnoreCase(args[0])) {
                    t = inv;
                    break;
                }
            }

            if (t == null) {
                sender.system("TEAM_JOIN_NOT_BY_TEAM");
                return;
            }
        }

        user.setTeam(t);
        teamInvitations.remove(user.getId());
        sender.system("TEAM_JOIN_JOINED", Team.byId(t).getTeamName());
    }

    @Register("stepback")
    public void stepBack(@Validator("isLeader") Chatter sender) {
        SteamwarUser user = sender.user();
        Team team = Team.byId(user.getTeam());

        if (noRemainingLeaders(team, user)) {
            sender.system("TEAM_OTHER_LEADER_REQUIRED");
            return;
        }

        user.setLeader(false);
        sender.system("TEAM_STEP_BACK");
    }

    @Register("leave")
    public void leave(@Validator("isInTeam") Chatter sender) {
        SteamwarUser user = sender.user();
        Team team = Team.byId(user.getTeam());

        int teamSize = team.size();
        if (teamSize > 1 && user.isLeader() && noRemainingLeaders(team, user)) {
            sender.system("TEAM_OTHER_LEADER_REQUIRED");
            return;
        }

        user.setTeam(0);

        if (teamSize == 1) team.disband(user);

        sender.system("TEAM_LEAVE_LEFT");
    }

    private boolean noRemainingLeaders(Team team, SteamwarUser except) {
        return SteamwarUser.getTeam(team.getTeamId()).stream().filter(member -> except.getId() != member.getId()).noneMatch(SteamwarUser::isLeader);
    }


    @Register(value = "invite", description = "TEAM_INVITE_USAGE")
    public void invite(@Validator("isLeader") Chatter sender, @ErrorMessage("TEAM_INVITE_NO_PLAYER") SteamwarUser target) {
        Team team = Team.byId(sender.user().getTeam());

        if (notDuringEvent(sender)) return;

        if (target.getTeam() != 0) {
            sender.system("TEAM_INVITE_IN_TEAM");
            return;
        }

        List<Integer> invitations = teamInvitations.computeIfAbsent(target.getId(), id -> new ArrayList<>());
        if (invitations.contains(team.getTeamId())) {
            sender.system("TEAM_INVITE_ALREADY_INVITED");
            return;
        }

        invitations.add(team.getTeamId());
        sender.system("TEAM_INVITE_INVITED", target.getUserName());
        Chatter.of(target).system("TEAM_INVITE_INVITED_TARGET", team.getTeamColor(), team.getTeamName());
    }

    @Register(value = "remove", description = "TEAM_REMOVE_USAGE")
    public void remove(@Validator("isLeader") Chatter sender, @ErrorMessage("TEAM_REMOVE_NOT_PLAYER") @Mapper("memberList") SteamwarUser target) {
        Team team = Team.byId(sender.user().getTeam());

        if (target.isLeader()) {
            sender.system("TEAM_REMOVE_NOT_LEADER");
            return;
        }

        List<Integer> invitations = teamInvitations.get(target.getId());
        if (invitations != null) {
            sender.system(invitations.remove((Integer) team.getTeamId()) ? "TEAM_REMOVE_INVITE" : "TEAM_REMOVE_NO_INVITE");
            return;
        }

        if (target.getTeam() != team.getTeamId()) {
            sender.system("TEAM_REMOVE_NOT_IN_TEAM");
            return;
        }

        target.setTeam(0);

        sender.system("TEAM_REMOVE_REMOVED");
        Chatter.of(target).system("TEAM_REMOVE_REMOVED_TARGET");
    }

    @Mapper(value = "memberList", local = true)
    public TypeMapper<SteamwarUser> memberList() {
        return new TypeMapper<SteamwarUser>() {
            @Override
            public SteamwarUser map(Chatter sender, PreviousArguments previousArguments, String s) {
                return SteamwarUser.get(s);
            }

            @Override
            public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
                return Team.byId(sender.user().getTeam()).getMembers().stream()
                        .map(SteamwarUser::byId)
                        .map(SteamwarUser::getUserName)
                        .toList();
            }
        };
    }

    @Register(value = "changekurzel", description = "TEAM_KUERZEL_USAGE")
    public void changekuerzel(@Validator("isLeader") Chatter sender, @Length(min = 2, max = 4) @ErrorMessage("TEAM_KUERZEL_LENGTH") String kuerzel) {
        Team team = Team.byId(sender.user().getTeam());

        if (notDuringEvent(sender)) return;

        if (checkTeamKuerzel(sender, team, kuerzel)) return;

        team.setTeamKuerzel(kuerzel);
        sender.system("TEAM_KUERZEL_CHANGED");
    }

    @Register(value = "changename", description = "TEAM_NAME_USAGE")
    public void changename(@Validator("isLeader") Chatter sender, @Length(min = 4, max = 15) @ErrorMessage("TEAM_NAME_LENGTH") String name) {
        Team team = Team.byId(sender.user().getTeam());

        if (notDuringEvent(sender)) return;

        if (checkTeamName(sender, team, name)) return;

        team.setTeamName(name);
        sender.system("TEAM_NAME_CHANGED");
    }

    @Register(value = "promote", description = "TEAM_LEADER_USAGE")
    public void promote(@Validator("isLeader") Chatter sender, @ErrorMessage("TEAM_LEADER_NOT_USER") @Mapper("memberList") SteamwarUser target) {
        if (notDuringEvent(sender)) return;

        if (target.getTeam() != sender.user().getTeam()) {
            sender.system("TEAM_LEADER_NOT_MEMBER");
            return;
        }

        target.setLeader(true);
        sender.system("TEAM_LEADER_PROMOTED", target);
    }

    @Register("info")
    public void info(@Validator("isInTeam") Chatter sender) {
        info(sender, Team.byId(sender.user().getTeam()));
    }

    @Register(value = "info", description = "TEAM_INFO_USAGE")
    public void info(Chatter sender, @ErrorMessage("UNKNOWN_TEAM") Team team) {
        List<SteamwarUser> users = team.getMembers().stream().map(SteamwarUser::byId).toList();

        sender.system("TEAM_INFO_TEAM", team.getTeamName(), team.getTeamColor(), team.getTeamKuerzel());
        sender.prefixless("TEAM_INFO_LEADER", users.stream().filter(SteamwarUser::isLeader).count(), getMemberList(users, true));

        String members = getMemberList(users, false);
        if (!members.isEmpty()) {
            sender.prefixless("TEAM_INFO_MEMBER", users.stream().filter(u -> !u.isLeader()).count(), members);
        }

        Set<Event> events = TeamTeilnahme.getEvents(team.getTeamId());
        if (!events.isEmpty()) {
            sender.prefixless("TEAM_INFO_EVENTS", events.size(), events.stream().map(event -> {
                Integer placement = TeamTeilnahme.getPlacement(team, event);
                if (placement != null) {
                    String prefix;
                    switch (placement) {
                        case 1:
                            prefix = "§x§F§F§D§7§0§e\uD83C\uDFC6 ";
                            break;
                        case 2:
                            prefix = "§x§C§0§C§0§C§7\uD83C\uDFC6 ";
                            break;
                        case 3:
                            prefix = "§x§8§C§7§8§5§6\uD83C\uDFC6 ";
                            break;
                        default:
                            prefix = "§f" + placement + ". ";
                            break;
                    }
                    return prefix + event.getEventName();
                } else {
                    return event.getEventName();
                }
            }).collect(Collectors.joining("§8,§e ")));
        }
    }

    private String getMemberList(List<SteamwarUser> users, boolean leaders) {
        return users.stream()
                .filter(user -> user.isLeader() == leaders)
                .map(user -> {
                    StringBuilder st = new StringBuilder();
                    if (VelocityCore.getProxy().getPlayer(user.getUUID()).isPresent()) {
                        if (!StreamingCommand.isNotStreaming(user)) {
                            st.append("§5");
                        } else {
                            st.append("§a");
                        }
                    } else {
                        st.append("§e");
                    }
                    st.append(user.getUserName());
                    return st.toString();
                })
                .collect(Collectors.joining("§8,§r "));
    }

    @Register("list")
    public void list(Chatter sender, @Min(intValue = 1) @OptionalValue("1") @ErrorMessage("TEAM_LIST_NOT_PAGE") int page) {
        final int TEAMS_PER_PAGE = 10;

        List<Team> all = Team.getAll();
        final int lastPage = ((all.size() - 1) / 10) + 1;
        if (page < 1 || page > lastPage) {
            sender.system("TEAM_LIST_UNKNOWN_PAGE");
            return;
        }

        sender.system("TEAM_LIST_HEADER", page, lastPage);

        for (int i = (page - 1) * TEAMS_PER_PAGE; i < (page - 1) * TEAMS_PER_PAGE + TEAMS_PER_PAGE && i < all.size(); i++) {
            Team tm = all.get(i);

            sender.prefixless("TEAM_LIST_TEAM", new Message("TEAM_LIST_TEAM_HOVER"),
                    ClickEvent.runCommand("/team info " + tm.getTeamName()), tm.getTeamColor(), tm.getTeamKuerzel(), tm.getTeamName());
        }

        Component beforePage = Component
                .text("«« ")
                .color(page > 1 ? NamedTextColor.YELLOW : NamedTextColor.DARK_GRAY);
        if (page > 1) {
            beforePage = beforePage
                    .hoverEvent(HoverEvent.showText(sender.parse("TEAM_LIST_PREV")))
                    .clickEvent(ClickEvent.runCommand("/team list " + (page - 1)));
        }

        Component nextPage = sender.parse("TEAM_LIST_PAGE")
                .color(page > 1 ? NamedTextColor.YELLOW : NamedTextColor.DARK_GRAY);
        if (page < lastPage) {
            nextPage = nextPage
                    .hoverEvent(HoverEvent.showText(sender.parse("TEAM_LIST_NEXT")))
                    .clickEvent(ClickEvent.runCommand("/team list " + (page + 1)));
        }

        sender.sendMessage(beforePage.append(nextPage));
    }

    @Register("event")
    public void event(@Validator("isInTeam") Chatter sender) {
        Team team = Team.byId(sender.user().getTeam());

        sender.system("TEAM_EVENT_USAGE");
        Set<Event> events = TeamTeilnahme.getEvents(team.getTeamId());
        if (!events.isEmpty()) {
            sender.system("TEAM_EVENT_HEADER");
            DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern(sender.parseToPlain("EVENT_DATE_FORMAT"));
            for (Event e : events) {
                sender.prefixless("TEAM_EVENT_EVENT", e.getStart().toLocalDateTime().format(dateFormat), e.getEventName());
            }
        }
    }

    @Register("event")
    public void event(@Validator("isLeader") Chatter sender, Event event) {
        Team team = Team.byId(sender.user().getTeam());

        if (notDuringEvent(sender)) return;

        if (Instant.now().isAfter(event.getDeadline().toInstant())) {
            sender.system("TEAM_EVENT_OVER");
            return;
        }

        if (TeamTeilnahme.nimmtTeil(team.getTeamId(), event.getEventID())) {
            TeamTeilnahme.notTeilnehmen(team.getTeamId(), event.getEventID());
            sender.system("TEAM_EVENT_LEFT");
        } else {
            TeamTeilnahme.teilnehmen(team.getTeamId(), event.getEventID());
            sender.system("TEAM_EVENT_JOINED", event.getEventName());
            sender.prefixless("TEAM_EVENT_HOW_TO_LEAVE");
        }

        DiscordBot.withBot(bot -> bot.getEventChannel().update());
    }

    private static final Map<String, String> COLOR_CODES = new HashMap<>();

    static {
        COLOR_CODES.put("4", "RED_DYE");
        COLOR_CODES.put("c", "WHITE_DYE");
        COLOR_CODES.put("6", "ORANGE_DYE");
        COLOR_CODES.put("e", "YELLOW_DYE");
        COLOR_CODES.put("2", "GREEN_DYE");
        COLOR_CODES.put("a", "LIME_DYE");
        COLOR_CODES.put("b", "LIGHT_BLUE_DYE");
        COLOR_CODES.put("3", "CYAN_DYE");
        COLOR_CODES.put("1", "LAPIS_LAZULI");
        COLOR_CODES.put("9", "CYAN_DYE");
        COLOR_CODES.put("d", "PINK_DYE");
        COLOR_CODES.put("5", "PURPLE_DYE");
        COLOR_CODES.put("f", "WHITE_DYE");
        COLOR_CODES.put("7", "LIGHT_GRAY_DYE");
        COLOR_CODES.put("8", "GRAY_DYE");
        COLOR_CODES.put("0", "BLACK_DYE");
    }

    @Register("color")
    @Register("changecolor")
    public void changeColor(@Validator("isLeader") PlayerChatter sender) {
        Team team = Team.byId(sender.user().getTeam());

        if (notDuringEvent(sender)) return;

        SWListInv<String> inv = new SWListInv<>(sender, new Message("TEAM_COLOR_TITLE"), COLOR_CODES.entrySet().stream().map(entry -> new SWListInv.SWListEntry<>(new SWItem(entry.getValue(), new Message("PLAIN_STRING", "§" + entry.getKey() + team.getTeamKuerzel())), entry.getKey())).toList(), (click, element) -> {
        });
        inv.setCallback((click, element) -> {
            inv.close();
            team.setTeamColor(element);
        });
        inv.open();
    }

    @ClassMapper(Event.class)
    public TypeMapper<Event> eventTypeMapper() {
        return new TypeMapper<>() {
            @Override
            public Event map(Chatter sender, PreviousArguments previousArguments, String s) {
                return Event.getComing()
                        .stream()
                        .filter(event -> event.getEventName().replace(" ", "").equalsIgnoreCase(s))
                        .findFirst()
                        .orElse(null);
            }

            @Override
            public boolean validate(Chatter sender, Event value, MessageSender messageSender) {
                if (value == null) {
                    sender.system("TEAM_EVENT_NO_EVENT");
                    return false;
                } else {
                    return true;
                }
            }

            @Override
            public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
                return Event.getComing().stream().map(Event::getEventName).map(e -> e.replace(" ", "")).toList();
            }
        };
    }

    @Validator(value = "isNotInTeam", local = true)
    public TypeValidator<Chatter> isNotInTeamValidator() {
        return (sender, value, messageSender) -> {
            if (sender.user().getTeam() != 0) {
                messageSender.send("TEAM_IN_TEAM");
                return false;
            }
            return true;
        };
    }

    @Validator(value = "isInTeam", local = true)
    public TypeValidator<Chatter> isInTeamValidator() {
        return (sender, value, messageSender) -> {
            if (sender.user().getTeam() == 0) {
                messageSender.send("TEAM_NOT_IN_TEAM");
                return false;
            }
            return true;
        };
    }

    @Validator(value = "isLeader", local = true)
    public TypeValidator<Chatter> isLeaderValidator() {
        return (sender, value, messageSender) -> {
            SteamwarUser user = sender.user();
            if (user.getTeam() == 0) {
                messageSender.send("TEAM_NOT_IN_TEAM");
                return false;
            }
            if (!user.isLeader()) {
                messageSender.send("TEAM_NOT_LEADER");
                return false;
            }
            return true;
        };
    }

    @ClassMapper(Team.class)
    @Cached(global = true, cacheDuration = 60)
    public TypeMapper<Team> team() {
        return new TypeMapper<Team>() {
            @Override
            public Team map(Chatter sender, PreviousArguments previousArguments, String s) {
                return Team.get(s);
            }

            @Override
            public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
                return Team.getAll().stream()
                        .flatMap(team -> Stream.of(team.getTeamName(), team.getTeamKuerzel()))
                        .toList();
            }
        };
    }

    private boolean checkTeamName(Chatter sender, Team team, String arg) {
        Team t = Team.get(arg);
        if (t != null && t.getTeamId() != team.getTeamId()) {
            sender.system("TEAM_NAME_TAKEN");
            return true;
        }
        return false;
    }

    private boolean checkTeamKuerzel(Chatter sender, Team team, String arg) {
        Team t = Team.get(arg);
        if (t != null && (team == null || t.getTeamId() != team.getTeamId())) {
            sender.system("TEAM_KUERZEL_TAKEN");
            return true;
        }
        return false;
    }

    private boolean notDuringEvent(Chatter sender) {
        if (Event.get() != null) {
            sender.system("TEAM_NOT_IN_EVENT");
            return true;
        }
        return false;
    }
}
