#include "GameOverResponsePopulator.hpp"

#include <algorithm>
#include <iostream>
#include <optional>
#include <ranges>
#include <string>
#include <vector>

#include "src/main/cpp/net/eagle0/shardok/library/ShardokCTypes.h"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokException.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
#include "src/main/protobuf/net/eagle0/common/victory_condition.pb.h"

namespace shardok {

using net::eagle0::common::VictoryCondition;

namespace {
auto EagleFactionIdForPlayer(
        const std::vector<net::eagle0::shardok::common::PlayerInfo> &resolvedPlayerInfos,
        const PlayerId playerId) -> std::optional<int32_t> {
    const auto it = std::ranges::find_if(
            resolvedPlayerInfos,
            [playerId](const net::eagle0::shardok::common::PlayerInfo &playerInfo) {
                return playerInfo.player_id() == playerId;
            });
    if (it == resolvedPlayerInfos.end()) return std::nullopt;
    return it->eagle_faction_id();
}
}  // namespace

auto PopulateGameOverResponse(
        const std::string &gameId,
        const net::eagle0::shardok::common::GameStatus &gameStatus,
        const std::vector<net::eagle0::shardok::common::PlayerInfo> &resolvedPlayerInfos,
        const std::vector<net::eagle0::shardok::storage::ResolvedUnit> &resolvedUnits,
        net::eagle0::common::GameOverResponse *gameOverResponse) -> bool {
    const auto &endCondition = gameStatus.end_game_condition();

    int32_t winningFactionId = -1;

    for (const auto &rpi : resolvedPlayerInfos) {
        auto shardokPid = rpi.player_id();
        const int32_t eagleFactionId = rpi.eagle_faction_id();

        auto *resolutionInfo = gameOverResponse->add_user_infos();

        // Set the endgame condition (win/lose) appropriately
        resolutionInfo->set_eagle_faction_id(eagleFactionId);
        if (std::ranges::contains(gameStatus.winning_shardok_ids(), shardokPid)) {
            // The winning player gets the victory set
            resolutionInfo->mutable_end_game_condition()->set_victory(
                    net::eagle0::common::VictoryCondition(endCondition.victory()));
            winningFactionId = eagleFactionId;
        } else if (std::ranges::any_of(
                           gameStatus.winning_shardok_ids(),
                           [&rpi](const PlayerId pid) {
                               return std::ranges::any_of(
                                       rpi.allies(),
                                       [pid](const net::eagle0::shardok::common::AlliedPlayer &ap) {
                                           return ap.player_id() == pid;
                                       });
                           })) {
            // Allied to a winner — their side won, so they get victory too
            resolutionInfo->mutable_end_game_condition()->set_victory(
                    net::eagle0::common::VictoryCondition(endCondition.victory()));
        } else {
            resolutionInfo->mutable_end_game_condition()->set_loss(
                    net::eagle0::common::VictoryCondition(endCondition.victory()));
        }

        // copy in all the resolvedUnits that correspond to this player
        for (const auto &internalRu : resolvedUnits) {
            const auto unit = UnitFromBytes(internalRu.unit_bytes());
            if (!unit.has_attached_hero()) continue;
            if (!IncludeUnitProtoInReturn(internalRu.status())) continue;
            if (unit.player_id() != shardokPid) continue;

            auto *ru = resolutionInfo->add_units();
            *ru->mutable_unit() = FromUnitFb(unit);
            ru->set_status(FromInternalStatus(internalRu.status()));
            if (internalRu.status() ==
                net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_SECURED_UNIT) {
                if (!internalRu.has_secured_by_player()) {
                    throw ShardokInternalErrorException("Secured unit is missing securing player");
                }
                const auto securedByEagleFactionId = EagleFactionIdForPlayer(
                        resolvedPlayerInfos,
                        internalRu.secured_by_player().value());
                if (!securedByEagleFactionId.has_value()) {
                    throw ShardokInternalErrorException("Secured unit is missing securing player");
                }
                ru->mutable_secured_by_eagle_faction_id()->set_value(
                        securedByEagleFactionId.value());
            }
        }
    }

    std::cout << "Finishing game " << gameId << ": " << winningFactionId << " wins" << '\n';

    return true;
}

auto IncludeUnitProtoInReturn(
        const net::eagle0::shardok::storage::ResolvedUnit::UnitStatus &internalStatus) -> bool {
    switch (internalStatus) {
        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_CAPTURED_UNIT:
        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_NORMAL_UNIT:
        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_FLED_UNIT:
        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_RETREATED_UNIT:
        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_NEVER_ENTERED_UNIT:
        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_OUTLAWED_UNIT:
        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_EVACUATED_UNIT:
        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_SECURED_UNIT: return true;

        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_DESTROYED_SUMMONED_UNIT:
            return false;

        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_UNKNOWN_UNIT:
        case net::eagle0::shardok::storage::
                ResolvedUnit_UnitStatus_ResolvedUnit_UnitStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
        case net::eagle0::shardok::storage::
                ResolvedUnit_UnitStatus_ResolvedUnit_UnitStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
            throw ShardokInternalErrorException("Bad unit status on resolved unit");
    }

    return true;
}

auto FromUnitFb(const Unit &unit) -> net::eagle0::common::CommonUnit {
    net::eagle0::common::CommonUnit commonUnit{};
    commonUnit.set_eagle_player_id(unit.eagle_player_id());
    commonUnit.mutable_hero()->set_eagle_hero_id(unit.attached_hero().eagle_hero_id());
    commonUnit.mutable_hero()->set_profession(static_cast<net::eagle0::common::CommonProfession>(
            unit.attached_hero().profession_info().profession()));

    commonUnit.mutable_hero()->set_strength(unit.attached_hero().strength());
    commonUnit.mutable_hero()->set_strength_xp(unit.attached_hero().strength_xp());

    commonUnit.mutable_hero()->set_agility(unit.attached_hero().agility());
    commonUnit.mutable_hero()->set_agility_xp(unit.attached_hero().agility_xp());

    commonUnit.mutable_hero()->set_constitution(unit.attached_hero().constitution());
    commonUnit.mutable_hero()->set_constitution_xp(unit.attached_hero().constitution_xp());

    commonUnit.mutable_hero()->set_charisma(unit.attached_hero().charisma());
    commonUnit.mutable_hero()->set_charisma_xp(unit.attached_hero().charisma_xp());

    commonUnit.mutable_hero()->set_wisdom(unit.attached_hero().wisdom());
    commonUnit.mutable_hero()->set_wisdom_xp(unit.attached_hero().wisdom_xp());

    commonUnit.mutable_hero()->set_integrity(unit.attached_hero().integrity());
    commonUnit.mutable_hero()->set_ambition(unit.attached_hero().ambition());
    commonUnit.mutable_hero()->set_gregariousness(unit.attached_hero().gregariousness());
    commonUnit.mutable_hero()->set_bravery(unit.attached_hero().bravery());

    commonUnit.mutable_hero()->set_vigor(unit.attached_hero().vigor());
    commonUnit.mutable_hero()->set_spent_vigor(unit.attached_hero().spent_vigor());

    commonUnit.mutable_battalion()->set_eagle_battalion_id(unit.battalion().eagle_battalion_id());
    commonUnit.mutable_battalion()->set_size(unit.battalion().size());
    commonUnit.mutable_battalion()->set_type(
            static_cast<net::eagle0::common::CommonBattalionTypeId>(unit.battalion().type()));
    commonUnit.mutable_battalion()->set_armament(unit.battalion().armament());
    commonUnit.mutable_battalion()->set_training(unit.battalion().training());

    return commonUnit;
}

auto FromInternalStatus(
        const net::eagle0::shardok::storage::ResolvedUnit::UnitStatus internalStatus)
        -> net::eagle0::common::UnitStatus {
    switch (internalStatus) {
        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_CAPTURED_UNIT:
            return net::eagle0::common::UnitStatus::CAPTURED_UNIT;

        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_NORMAL_UNIT:
            return net::eagle0::common::UnitStatus::NORMAL_UNIT;

        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_FLED_UNIT:
            return net::eagle0::common::UnitStatus::FLED_UNIT;

        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_RETREATED_UNIT:
            return net::eagle0::common::UnitStatus::RETREATED_UNIT;

        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_NEVER_ENTERED_UNIT:
            return net::eagle0::common::UnitStatus::NEVER_ENTERED_UNIT;

        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_OUTLAWED_UNIT:
            return net::eagle0::common::UnitStatus::OUTLAWED_UNIT;

        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_EVACUATED_UNIT:
            return net::eagle0::common::UnitStatus::EVACUATED_UNIT;

        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_SECURED_UNIT:
            return net::eagle0::common::UnitStatus::SECURED_UNIT;

        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_DESTROYED_SUMMONED_UNIT:
        case net::eagle0::shardok::storage::ResolvedUnit_UnitStatus_UNKNOWN_UNIT:
        case net::eagle0::shardok::storage::
                ResolvedUnit_UnitStatus_ResolvedUnit_UnitStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
        case net::eagle0::shardok::storage::
                ResolvedUnit_UnitStatus_ResolvedUnit_UnitStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
            throw ShardokInternalErrorException("Bad unit status on resolved unit");
    }

    throw ShardokInternalErrorException("Bad unit status on resolved unit");
}

}  // namespace shardok
