//
// Emits small JSON metadata records for saved Eagle/Shadok battles.
//

#include <flatbuffers/flatbuffers.h>

#include <cstdlib>
#include <fstream>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>
#include <vector>

#include "src/main/flatbuffer/net/eagle0/shardok/storage/game_state.hpp"
#include "src/main/protobuf/net/eagle0/eagle/internal/shardok_battle.pb.h"
#include "src/main/protobuf/net/eagle0/shardok/api/action_result_view.pb.h"
#include "src/main/protobuf/net/eagle0/shardok/storage/action_result.pb.h"

namespace {

using net::eagle0::eagle::internal::ShardokBattle;
using net::eagle0::shardok::api::ActionResultView;
using net::eagle0::shardok::storage::ActionResult;
namespace fb = net::eagle0::shardok::storage::fb;

std::string ReadFile(const std::string &path) {
    std::ifstream input(path, std::ios::binary);
    if (!input.is_open()) { throw std::runtime_error("Failed to open " + path); }
    return std::string(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
}

std::string JsonEscape(const std::string &value) {
    std::string escaped;
    for (const char ch : value) {
        switch (ch) {
            case '"': escaped += "\\\""; break;
            case '\\': escaped += "\\\\"; break;
            case '\n': escaped += "\\n"; break;
            case '\r': escaped += "\\r"; break;
            case '\t': escaped += "\\t"; break;
            default: escaped += ch; break;
        }
    }
    return escaped;
}

std::string JoinInts(const std::vector<int> &values) {
    std::string joined;
    for (size_t i = 0; i < values.size(); ++i) {
        if (i > 0) { joined += ";"; }
        joined += std::to_string(values[i]);
    }
    return joined;
}

std::string SideForPlayerId(const fb::GameState *state, int playerId) {
    if (state == nullptr || state->player_infos() == nullptr) { return "unknown"; }
    for (const auto *playerInfo : *state->player_infos()) {
        if (playerInfo != nullptr && playerInfo->player_id() == playerId) {
            return playerInfo->is_defender() ? "defender" : "attacker";
        }
    }
    return "unknown";
}

bool IsSurvivingUnitStatus(fb::UnitStatus status) {
    return status == fb::UnitStatus_NORMAL_UNIT || status == fb::UnitStatus_RESERVE_UNIT ||
           status == fb::UnitStatus_NEVER_ENTERED_UNIT;
}

double SurvivingTroopsForSide(const fb::GameState *state, const std::string &side) {
    if (state == nullptr || state->units() == nullptr) { return 0.0; }
    double survivingTroops = 0.0;
    for (const auto *unit : *state->units()) {
        if (unit == nullptr || !IsSurvivingUnitStatus(unit->status())) { continue; }
        if (SideForPlayerId(state, unit->player_id()) == side) {
            survivingTroops += unit->battalion().size();
        }
    }
    return survivingTroops;
}

std::string WinnerSide(const fb::GameState *state) {
    if (state == nullptr || state->status() == nullptr ||
        state->status()->winning_shardok_ids() == nullptr ||
        state->status()->winning_shardok_ids()->empty()) {
        return "unknown";
    }

    bool attackerWon = false;
    bool defenderWon = false;
    for (const int8_t playerId : *state->status()->winning_shardok_ids()) {
        const std::string side = SideForPlayerId(state, playerId);
        attackerWon = attackerWon || side == "attacker";
        defenderWon = defenderWon || side == "defender";
    }
    if (attackerWon && !defenderWon) { return "attacker"; }
    if (defenderWon && !attackerWon) { return "defender"; }
    return "unknown";
}

void PrintUsage(const char *program) {
    std::cerr << "Usage: " << program << " --battle=<new_battle_proto>"
              << " [--result=<final_shardok_action_result_proto>]"
              << " [--state-fb=<final_game_state_flatbuffer>]"
              << " [--action-view=<action_result_view_proto>]...\n";
}

}  // namespace

int main(int argc, char *argv[]) {
    try {
        std::string battlePath;
        std::string resultPath;
        std::string stateFbPath;
        std::vector<std::string> actionViewPaths;
        actionViewPaths.reserve(argc - 1);

        for (int i = 1; i < argc; ++i) {
            const std::string arg = argv[i];
            if (arg.starts_with("--battle=")) {
                battlePath = arg.substr(9);
            } else if (arg.starts_with("--result=")) {
                resultPath = arg.substr(9);
            } else if (arg.starts_with("--state-fb=")) {
                stateFbPath = arg.substr(11);
            } else if (arg.starts_with("--action-view=")) {
                actionViewPaths.push_back(arg.substr(14));
            } else {
                PrintUsage(argv[0]);
                return 1;
            }
        }

        if (battlePath.empty()) {
            PrintUsage(argv[0]);
            return 1;
        }

        ShardokBattle battle;
        if (!battle.ParseFromString(ReadFile(battlePath))) {
            throw std::runtime_error("Failed to parse battle proto");
        }

        std::vector<int> attackerFactions;
        std::vector<int> defenderFactions;
        std::vector<std::string> playerSides;
        attackerFactions.reserve(battle.players_size());
        defenderFactions.reserve(battle.players_size());
        playerSides.reserve(battle.players_size());
        bool aiVsAi = true;
        for (const auto &player : battle.players()) {
            if (player.eagle_fid() == 3 || player.eagle_fid() == 4) { aiVsAi = false; }
            if (player.is_defender()) {
                defenderFactions.push_back(player.eagle_fid());
                playerSides.push_back("defender");
            } else {
                attackerFactions.push_back(player.eagle_fid());
                playerSides.push_back("attacker");
            }
        }

        std::string realWinner = "unknown";
        int realEndCondition = 0;
        int realRound = 0;
        double realAttackerTroops = 0.0;
        double realDefenderTroops = 0.0;

        if (!actionViewPaths.empty()) {
            std::map<int, int> troopTotalsByPlayerId;
            bool hasGameStatus = false;
            for (const auto &actionViewPath : actionViewPaths) {
                ActionResultView view;
                if (!view.ParseFromString(ReadFile(actionViewPath))) {
                    throw std::runtime_error("Failed to parse Shardok action result view proto");
                }
                hasGameStatus = hasGameStatus || view.game_state_view_diff().has_game_status();
                for (const auto &totals : view.game_state_view_diff().changed_player_totals()) {
                    troopTotalsByPlayerId[totals.player_id()] = totals.troop_count();
                }
            }
            std::cout << "{";
            std::cout << "\"player_totals\":[";
            int index = 0;
            for (const auto &[playerId, troopCount] : troopTotalsByPlayerId) {
                if (index > 0) { std::cout << ","; }
                std::cout << "{\"player_id\":" << playerId << ",\"troop_count\":" << troopCount
                          << "}";
                ++index;
            }
            std::cout << "],\"has_game_status\":" << (hasGameStatus ? "true" : "false") << "}\n";
            return 0;
        }
        if (!resultPath.empty()) {
            ActionResult result;
            if (!result.ParseFromString(ReadFile(resultPath))) {
                throw std::runtime_error("Failed to parse Shardok action result proto");
            }
            if (result.has_game_status()) {
                bool attackerWon = false;
                bool defenderWon = false;
                for (const int playerId : result.game_status().winning_shardok_ids()) {
                    const std::string side = playerId == 0   ? "attacker"
                                             : playerId == 1 ? "defender"
                                                             : "unknown";
                    attackerWon = attackerWon || side == "attacker";
                    defenderWon = defenderWon || side == "defender";
                }
                if (attackerWon && !defenderWon) { realWinner = "attacker"; }
                if (defenderWon && !attackerWon) { realWinner = "defender"; }
                if (result.game_status().has_end_game_condition() &&
                    result.game_status().end_game_condition().has_victory()) {
                    realEndCondition =
                            static_cast<int>(result.game_status().end_game_condition().victory());
                }
            }
            if (result.has_next_round()) { realRound = result.next_round().value(); }
        } else if (!stateFbPath.empty()) {
            const std::string stateBytes = ReadFile(stateFbPath);
            if (!stateBytes.empty()) {
                flatbuffers::Verifier verifier(
                        reinterpret_cast<const uint8_t *>(stateBytes.data()),
                        stateBytes.size());
                if (!fb::VerifyGameStateBuffer(verifier)) {
                    throw std::runtime_error("Failed to verify Shardok GameState flatbuffer");
                }
                const auto *state =
                        fb::GetGameState(reinterpret_cast<const uint8_t *>(stateBytes.data()));
                realWinner = WinnerSide(state);
                if (state != nullptr) {
                    realRound = state->current_round();
                    if (state->status() != nullptr) {
                        realEndCondition = state->status()->end_game_condition() == nullptr
                                                   ? 0
                                                   : static_cast<int>(state->status()
                                                                              ->end_game_condition()
                                                                              ->victory_details());
                    }
                    realAttackerTroops = SurvivingTroopsForSide(state, "attacker");
                    realDefenderTroops = SurvivingTroopsForSide(state, "defender");
                }
            }
        }

        std::cout << "{"
                  << "\"shardok_game_id\":\"" << JsonEscape(battle.shardok_game_id()) << "\","
                  << "\"ai_vs_ai\":" << (aiVsAi ? "true" : "false") << ","
                  << "\"attacker_factions\":\"" << JsonEscape(JoinInts(attackerFactions)) << "\","
                  << "\"defender_factions\":\"" << JsonEscape(JoinInts(defenderFactions)) << "\","
                  << "\"player_sides\":\"" << JsonEscape([&playerSides]() {
                         std::string joined;
                         for (size_t i = 0; i < playerSides.size(); ++i) {
                             if (i > 0) { joined += ";"; }
                             joined += playerSides[i];
                         }
                         return joined;
                     }())
                  << "\","
                  << "\"real_winner\":\"" << realWinner << "\","
                  << "\"real_end_condition\":" << realEndCondition << ","
                  << "\"real_round\":" << realRound << ","
                  << "\"real_attacker_troops\":" << realAttackerTroops << ","
                  << "\"real_defender_troops\":" << realDefenderTroops << "}\n";
        return 0;
    } catch (const std::exception &e) {
        std::cerr << "Error: " << e.what() << "\n";
        return 1;
    }
}
