/*
 *   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.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@AllArgsConstructor
@Getter
@Setter
public class EventRelation {

    static {
        SqlTypeMapper.ordinalEnumMapper(FightTeam.class);
        SqlTypeMapper.ordinalEnumMapper(FromType.class);
    }

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

    private static final SelectStatement<EventRelation> get = new SelectStatement<>(table, "SELECT * FROM EventRelation WHERE FromType = ? AND FromId = ?");
    private static final SelectStatement<EventRelation> byId = new SelectStatement<>(table, "SELECT * FROM EventRelation WHERE id = ?");
    private static final SelectStatement<EventRelation> byEvent = new SelectStatement<>(table, "SELECT ER.* FROM EventRelation ER JOIN EventFight EF ON EF.fightId = ER.fightId WHERE EF.EventID = ?");
    private static final Statement insert = table.insertFields(true, "fightId", "fightTeam", "fromType", "fromId", "fromPlace");
    private static final Statement update = table.update(Table.PRIMARY, "fromType", "fromId", "fromPlace");
    private static final Statement updateTeam = table.update(Table.PRIMARY, "fightTeam");
    private static final Statement delete = table.delete(Table.PRIMARY);

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

    public static EventRelation get(int id) {
        return byId.select(id);
    }

    public static List<EventRelation> getFightRelations(EventFight fight) {
        return get.listSelect(FromType.FIGHT, fight.getFightID());
    }

    public static List<EventRelation> getGroupRelations(EventGroup group) {
        return get.listSelect(FromType.GROUP, group.getId());
    }

    public static EventRelation create(EventFight fight, FightTeam fightTeam, FromType fromType, int fromId, int fromPlace) {
        int id = insert.insertGetKey(fight.getFightID(), fightTeam, fromType, fromId, fromPlace);
        return get(id);
    }

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

    @Field
    private int fightId;

    @Field
    private FightTeam fightTeam;

    @Field
    private FromType fromType;

    @Field
    private int fromId;

    @Field
    private int fromPlace;

    public EventFight getFight() {
        return EventFight.get(fightId);
    }

    public Optional<EventFight> getFromFight() {
        if(fromType == FromType.FIGHT) {
            return Optional.of(EventFight.get(fromId));
        } else {
            return Optional.empty();
        }
    }

    public Optional<EventGroup> getFromGroup() {
        if(fromType == FromType.GROUP) {
            return EventGroup.get(fromId);
        } else {
            return Optional.empty();
        }
    }

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

    public void setUpdateTeam(FightTeam team) {
        updateTeam.update(id, team);
        this.fightTeam = team;
    }

    public void setFromFight(EventFight fight, int place) {
        setFrom(fight.getFightID(), place, FromType.FIGHT);
    }

    public void setFromGroup(EventGroup group, int place) {
        setFrom(group.getId(), place, FromType.GROUP);
    }

    private void setFrom(int fightId, int place, FromType type) {
        update.update(type, fightId, place, id);
        this.fromType = type;
        this.fromId = fightId;
        this.fromPlace = place;
    }

    public Optional<Team> getAdvancingTeam() {
        if (fromType == FromType.FIGHT) {
            if (fromPlace == 0) {
                return getFromFight().flatMap(EventFight::getWinner);
            } else {
                return getFromFight().flatMap(EventFight::getLosser);
            }
        } else if (fromType == FromType.GROUP) {
            return getFromGroup().map(EventGroup::calculatePoints)
                    .flatMap(points -> points.entrySet().stream()
                            .sorted(Map.Entry.<Team, Integer>comparingByValue().reversed())
                            .skip(fromPlace)
                            .findFirst()
                            .map(Map.Entry::getKey));
        } else {
            return Optional.empty();
        }
    }

    public boolean apply() {
        Optional<Integer> team = getAdvancingTeam().map(Team::getTeamId);
        if(!team.isPresent())
            return false;

        EventFight fight = getFight();
        if(fightTeam == FightTeam.RED) {
            fight.update(
                    fight.getStartTime(),
                    fight.getSpielmodus(),
                    fight.getMap(),
                    fight.getTeamBlue(),
                    team.get(),
                    fight.getSpectatePort()
            );
        } else {
            fight.update(
                    fight.getStartTime(),
                    fight.getSpielmodus(),
                    fight.getMap(),
                    team.get(),
                    fight.getTeamRed(),
                    fight.getSpectatePort()
            );
        }

        return true;
    }

    public static enum FightTeam {
        BLUE,
        RED
    }

    public static enum FromType {
        FIGHT,
        GROUP
    }
}
