/*
 *   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 com.google.protobuf.MapEntry;
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.PlayerChatter;
import de.steamwar.persistent.Subserver;
import de.steamwar.sql.*;
import de.steamwar.sql.Event;
import de.steamwar.velocitycore.EventStarter;
import de.steamwar.velocitycore.SubserverSystem;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;

import java.awt.*;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

@Linked
public class EventCommand extends SWCommand {

    public EventCommand() {
        super("event");
    }

    @Validator("noEvent")
    public TypeValidator<Chatter> noEventValidator() {
        return (sender, value, messageSender) -> Event.get() == null;
    }

    @Register
    public void noCurrentEvent(@Validator("noEvent") Chatter sender) {
        sender.system("EVENT_NO_CURRENT");

        List<Event> coming = Event.getComing();
        if (coming.isEmpty()) return;

        sender.system("EVENT_COMING");

        Instant now = Instant.now();
        DateTimeFormatter format = DateTimeFormatter.ofPattern(sender.parseToPlain("EVENT_DATE_FORMAT"));
        for (Event e : coming) {
            sender.prefixless("EVENT_COMING_EVENT", e.getStart().toLocalDateTime().format(format), e.getEnd().toLocalDateTime().format(format), e.getEventName());

            if (now.isBefore(e.getDeadline().toInstant())) {
                sender.prefixless("EVENT_COMING_DEADLINE", e.getDeadline());
            }

            SchematicType schemType = e.getSchematicType();
            if (schemType != null && schemType.getDeadline() != null && now.isBefore(schemType.getDeadline().toInstant())) {
                sender.prefixless("EVENT_COMING_SCHEM_DEADLINE", schemType.getDeadline());
            }

            Set<Team> teams = TeamTeilnahme.getTeams(e.getEventID());
            if (!teams.isEmpty()) {
                sender.prefixless("EVENT_COMING_TEAMS", teams.stream().map(team -> sender.parseToLegacy("EVENT_COMING_TEAM", team.getTeamColor(), team.getTeamKuerzel())).collect(Collectors.joining(" ")));
            }
        }
    }

    @Register
    public void eventOverview(@Validator(value = "noEvent", invert = true) Chatter sender) {
        sender.system("EVENT_TEAM_USAGE");

        Event currentEvent = Event.get();
        eventShowFightsPage(sender, currentEvent, 1);
    }

    @Register(value = "showtable", description = "EVENT_SHOW_TABLE_USAGE")
    public void eventtable(Chatter sender, Event event) {
        if (!Instant.now().isBefore(event.getEnd().toInstant())) return;

        Map<Integer, Integer> Teampoints = new HashMap<>();

        EventFight.getEvent(event.getEventID()).forEach(eventFight -> {
            if (eventFight.hasFinished() && eventFight.getGroup().isPresent()) {
                switch (eventFight.getErgebnis()) {
                    case 1:
                        //win blue
                        Teampoints.put(eventFight.getTeamBlue(), Teampoints.getOrDefault(eventFight.getTeamBlue(), 0) + eventFight.getGroup().get().getPointsPerWin());
                        Teampoints.put(eventFight.getTeamRed(), Teampoints.getOrDefault(eventFight.getTeamRed(),0) + eventFight.getGroup().get().getPointsPerLoss());
                        break;
                    case 2:
                        //win red
                        Teampoints.put(eventFight.getTeamBlue(), Teampoints.getOrDefault(eventFight.getTeamBlue(),0) + eventFight.getGroup().get().getPointsPerLoss());
                        Teampoints.put(eventFight.getTeamRed(), Teampoints.getOrDefault(eventFight.getTeamRed(),0) + eventFight.getGroup().get().getPointsPerWin());
                        break;
                    default:
                        //draw
                        Teampoints.put(eventFight.getTeamBlue(), Teampoints.getOrDefault(eventFight.getTeamBlue(),0) + eventFight.getGroup().get().getPointsPerDraw());
                        Teampoints.put(eventFight.getTeamRed(), Teampoints.getOrDefault(eventFight.getTeamRed(),0) + eventFight.getGroup().get().getPointsPerDraw());
                }
            }
        });
        Map<Integer, Integer> Teampointssortet = Teampoints.entrySet()
                .stream()
                .sorted(Map.Entry.comparingByValue())
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (a , b) -> a,
                        LinkedHashMap::new
                ));

        for (Map.Entry<Integer, Integer> entry : Teampointssortet.entrySet()) {
            Component finalmessage = sender.parse("EVENT_TEAM_TABLE",Team.byId(entry.getKey()).getTeamColor(),  Team.byId(entry.getKey()).getTeamKuerzel(), Team.byId(entry.getKey()).getTeamName(), entry.getValue());
            finalmessage = finalmessage.clickEvent(ClickEvent.runCommand("/team info " + Team.byId(entry.getKey()).getTeamName()));
            sender.sendMessage(finalmessage);
        }
    }

    @Register(value = "showfights", description = "EVENT_SHOW_FIGHTS_USAGE")
    public void eventShowFightsPage(Chatter sender, Event event, @AllowNull @OptionalValue("1") int page) {
        if (!Instant.now().isBefore(event.getEnd().toInstant())) return;
        int pagecount = sendEventPage(sender, event, page);

        Integer nextpage = page + 1;
        Integer prevpage = page - 1;

        Component finalmessage;

        Component pagenumber = sender.parse("UTIL_LIST_PAGE", page, pagecount);

        if (pagecount != 0 && page != 1) {
            finalmessage = sender.parse("UTIL_LIST_BACK_ARROW");
            finalmessage = finalmessage.clickEvent(ClickEvent.runCommand("/event showfights " + event.getEventName() + " " + prevpage));
            finalmessage = finalmessage.append(pagenumber);
        } else {
            finalmessage = pagenumber;
        }

        if (pagecount != 0 && pagecount > page) {
            Component nextbutton = sender.parse("UTIL_LIST_NEXT");
            nextbutton = nextbutton.clickEvent(ClickEvent.runCommand("/event showfights " + event.getEventName() + " " + nextpage));
            finalmessage = finalmessage.append(nextbutton);
        }
        sender.sendMessage(finalmessage);
    }

    @Register(value = "team", description = "EVENT_TEAM_USAGE")
    public void eventWithTeam(@Validator(value = "noEvent", invert = true) PlayerChatter sender, @ErrorMessage("EVENT_NO_TEAM") Team team) {
        Subserver eventArena = EventStarter.getEventServer().get(team.getTeamId());
        if (eventArena == null || !Subserver.getServerList().contains(eventArena)) {
            sender.system("EVENT_NO_FIGHT_TEAM");
            return;
        }
        if (!PunishmentCommand.isPunishedWithMessage(sender, Punishment.PunishmentType.NoFightServer)) {
            SubserverSystem.sendPlayer(eventArena, sender.getPlayer());
        }
    }

    private int sendEventPage(Chatter sender, Event event, int page) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(sender.parseToPlain("EVENT_DATE_FORMAT"));
        Map<String, List<EventFight>> dates = EventFight.getEvent(event.getEventID()).stream().collect(Collectors.groupingBy(p -> p.getStartTime().toLocalDateTime().format(dateTimeFormatter)));
        if (dates.size() >= page) {
            sender.system("EVENT_CURRENT_EVENT", event.getEventName());
            DateTimeFormatter format = DateTimeFormatter.ofPattern(sender.parseToPlain("EVENT_TIME_FORMAT"));
            for (EventFight eventFight : dates.get(dates.keySet().toArray()[page - 1])) {
                Team blue = Team.byId(eventFight.getTeamBlue());
                Team red = Team.byId(eventFight.getTeamRed());
                Component finalfightline = sender.parse("EVENT_CURRENT_FIGHT_1", eventFight.getStartTime().toLocalDateTime().format(format), blue.getTeamColor(), blue.getTeamKuerzel());
                finalfightline = finalfightline.clickEvent(ClickEvent.runCommand("/team info " + blue.getTeamName()));
                Component vs = sender.parse("EVENT_CURRENT_FIGHT_VS");
                finalfightline = finalfightline.append(vs);
                Component redteam = sender.parse("EVENT_CURRENT_FIGHT_2", red.getTeamColor(), red.getTeamKuerzel());
                redteam = redteam.clickEvent(ClickEvent.runCommand("/team info " + red.getTeamName()));
                finalfightline = finalfightline.append(redteam);

                if (eventFight.hasFinished()) {
                    Component ergebnis;
                    switch (eventFight.getErgebnis()) {
                        case 1:
                            ergebnis = sender.parse("EVENT_CURRENT_FIGHT_WIN", blue.getTeamColor(), blue.getTeamKuerzel());
                            break;
                        case 2:
                            ergebnis = sender.parse("EVENT_CURRENT_FIGHT_WIN", red.getTeamColor(), red.getTeamKuerzel());
                            break;
                        default:
                            ergebnis = sender.parse("EVENT_CURRENT_FIGHT_DRAW");
                    }
                    finalfightline = finalfightline.append(ergebnis);
                }
                sender.sendMessage(finalfightline);
            }
        }
        return dates.size();
    }

    protected static TypeMapper<Event> eventTypeMapper() {
        return new TypeMapper<>() {
            @Override
            public Event map(Chatter sender, PreviousArguments previousArguments, String s) {
                return Event.get(s);
            }

            @Override
            public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
                Set<String> events = new HashSet<>();
                List<Event> allevents = Event.getAll();
                for (Event event : allevents) {
                    if (!Instant.now().isAfter(event.getEnd().toInstant())) {
                        events.add(event.getEventName());
                    }
                }
                return events;
            }
        };
    }

    @ClassMapper(value = Event.class, local = true)
    public TypeMapper<Event> eventMapper() {
        return eventTypeMapper();
    }

    protected static TypeMapper<Team> eventTeam(Function<EventFight, List<Integer>> teamMapper) {
        return new TypeMapper<>() {
            @Override
            public Team map(Chatter sender, PreviousArguments previousArguments, String s) {
                if ("PUB".equalsIgnoreCase(s) || "public".equalsIgnoreCase(s)) {
                    return Team.byId(0);
                }
                return Team.get(s);
            }

            @Override
            public Collection<String> tabCompletes(Chatter sender, PreviousArguments previousArguments, String s) {
                Set<String> teams = new HashSet<>();
                Event currentEvent = Event.get();
                for (EventFight fight : EventFight.getEvent(currentEvent.getEventID())) {
                    if (fight.hasFinished()) continue;
                    teamMapper.apply(fight).forEach(integer -> {
                        Team team = Team.byId(integer);
                        teams.add(team.getTeamName());
                        teams.add(team.getTeamKuerzel());
                    });
                }
                return teams;
            }
        };
    }

    @ClassMapper(value = Team.class, local = true)
    public TypeMapper<Team> eventTeams() {
        return eventTeam(eventFight -> {
            if (eventFight.getStartTime().after(new Timestamp(System.currentTimeMillis()))) {
                return Collections.emptyList();
            }
            return List.of(eventFight.getTeamBlue(), eventFight.getTeamRed());
        });
    }
}