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

import de.steamwar.sql.internal.*;
import lombok.Getter;
import lombok.Setter;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Getter
@Setter
public class EventGroup {
    static {
        SqlTypeMapper.ordinalEnumMapper(EventGroupType.class);
    }

    private static final Table<EventGroup> table = new Table<>(EventGroup.class);

    private static final SelectStatement<EventGroup> get = table.select(Table.PRIMARY);
    private static final SelectStatement<EventGroup> byEvent = new SelectStatement<>(table, "SELECT * FROM EventGroup WHERE EventID = ?");

    private static final Statement insert = table.insertFields(true, "EventID", "Name", "Type");
    private static final Statement update = table.update(Table.PRIMARY, "Name", "Type", "PointsPerWin", "PointsPerLoss", "PointsPerDraw");
    private static final Statement delete = table.delete(Table.PRIMARY);

    public static List<EventGroup> get(Event eventID) {
        return byEvent.listSelect(eventID.getEventID());
    }

    public static EventGroup create(Event event, String name, EventGroupType type) {
        int key = insert.insertGetKey(event.getEventID(), name, type);
        return EventGroup.get(key).get();
    }

    public static Optional<EventGroup> get(int id) {
        return Optional.ofNullable(get.select(id));
    }

    @Field(keys = Table.PRIMARY)
    private final int id;

    @Field(keys = "EVENT_NAME")
    private int eventID;

    @Field(keys = "EVENT_NAME")
    private String name;

    @Field
    private EventGroupType type;

    @Field
    private int pointsPerWin;

    @Field
    private int pointsPerLoss;

    @Field
    private int pointsPerDraw;

    public EventGroup(int id, int eventID, String name, EventGroupType type, int pointsPerWin, int pointsPerLoss, int pointsPerDraw) {
        this.id = id;
        this.eventID = eventID;
        this.name = name;
        this.type = type;
        this.pointsPerWin = pointsPerWin;
        this.pointsPerLoss = pointsPerLoss;
        this.pointsPerDraw = pointsPerDraw;
    }

    private Map<Team, Integer> points;

    public List<EventFight> getFights() {
        return EventFight.get(this);
    }

    public Set<Integer> getTeamsId() {
        return getFights().stream().flatMap(fight -> Stream.of(fight.getTeamBlue(), fight.getTeamRed()))
                .collect(Collectors.toSet());
    }

    public Set<Team> getTeams() {
        return getTeamsId().stream().map(Team::get).collect(Collectors.toSet());
    }

    public Optional<EventFight> getLastFight() {
        return EventFight.getLast(this);
    }

    public List<EventRelation> getDependents() {
        return EventRelation.getGroupRelations(this);
    }

    public Map<Team, Integer> calculatePoints() {
        if (points == null) {
            Map<Integer, Integer> p = getTeamsId().stream().collect(Collectors.toMap(team -> team, team -> 0));

            for (EventFight fight : getFights()) {
                int blueTeamAdd = 0;
                int redTeamAdd = 0;

                if (!fight.hasFinished()) {
                    continue;
                }

                switch (fight.getErgebnis()) {
                    case 1:
                        blueTeamAdd += pointsPerWin;
                        redTeamAdd += pointsPerLoss;
                        break;
                    case 2:
                        blueTeamAdd += pointsPerLoss;
                        redTeamAdd += pointsPerWin;
                        break;
                    case 0:
                        if (fight.getFightID() != 0) {
                            blueTeamAdd += pointsPerDraw;
                            redTeamAdd += pointsPerDraw;
                        }
                        break;
                }

                p.put(fight.getTeamBlue(), p.get(fight.getTeamBlue()) + blueTeamAdd);
                p.put(fight.getTeamRed(), p.get(fight.getTeamRed()) + redTeamAdd);
            }

            points = p.entrySet().stream().collect(Collectors.toMap(integerIntegerEntry -> Team.get(integerIntegerEntry.getKey()), Map.Entry::getValue));
        }

        return points;
    }

    public void update(String name, EventGroupType type, int pointsPerWin, int pointsPerLoss, int pointsPerDraw) {
        update.update(name, type, pointsPerWin, pointsPerLoss, pointsPerDraw, id);
        this.name = name;
        this.type = type;
        this.pointsPerWin = pointsPerWin;
        this.pointsPerLoss = pointsPerLoss;
        this.pointsPerDraw = pointsPerDraw;
    }

    public boolean needsTieBreak() {
        return calculatePoints().values().stream().sorted().limit(2).distinct().count() < 2;
    }

    public void delete() {
        delete.update(id);
    }

    public static enum EventGroupType {
        GROUP_STAGE,
        ELIMINATION_STAGE
    }
}
