//
// Created by Dan Crosby on 2025-01-15.
//

#include "PerformanceTestGameStateBuilder.hpp"

#include <filesystem>

#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/common/TsvParser.hpp"
#include "src/main/cpp/net/eagle0/common/byte_vector.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/GameStateHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/BattalionTypeRegistrar.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
#include "src/main/flatbuffer/net/eagle0/shardok/storage/unit.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/player_info.pb.h"

namespace shardok {

namespace {

// Profession enum values
constexpr int NO_PROFESSION = 0;

// Player IDs
constexpr PlayerId AI_PLAYER_ID = 0;
constexpr PlayerId HUMAN_PLAYER_ID = 1;

}  // namespace

auto PerformanceTestGameStateBuilder::InitializeGameSettings() -> GameSettingsSPtr {
    auto settings = std::make_shared<GameSettings>();
    auto setter = settings->GetSetter();

    // Load battalion types
    BattalionTypeRegistrar::RegisterBattalionTypes(setter);

    // Load complete settings from settings.tsv file
    TsvParser parser;
    const string settingsPath = FilesystemUtils::StaticShardokFilesDirectory() + "settings.tsv";
    const string settingsTsv = string(byte_vector::FromPath(settingsPath));
    const auto valuesAndTypes = parser.ParseColumnEntryTsv(settingsTsv);
    setter.SetFromTypesAndValues(valuesAndTypes[1], valuesAndTypes[0]);

    return settings;
}

auto PerformanceTestGameStateBuilder::CreatePerfTestGameState(
        const GameSettingsSPtr& settings,
        bool defenderToggle) -> GameStateW {
    return CreateCustomTestGameState(
            settings,
            "Alah",
            6,  // 6 AI units (full test configuration)
            6,  // 6 human units (full test configuration)
            defenderToggle);
}

auto PerformanceTestGameStateBuilder::CreateCustomTestGameState(
        const GameSettingsSPtr& settings,
        const std::string& mapName,
        int aiUnitCount,
        int humanUnitCount,
        bool defenderToggle) -> GameStateW {
    // Load the map using existing utilities
    auto hexMapProto = LoadMap(mapName);

    // Create player info protos
    std::vector<net::eagle0::shardok::common::PlayerInfo> playerInfoProtos;

    // AI player
    net::eagle0::shardok::common::PlayerInfo aiPlayerInfo;
    aiPlayerInfo.set_player_id(AI_PLAYER_ID);
    aiPlayerInfo.set_is_defender(defenderToggle);
    aiPlayerInfo.set_starting_food(1000);
    aiPlayerInfo.add_victory_conditions(
            net::eagle0::shardok::common::VICTORY_CONDITION_LAST_PLAYER_STANDING);
    if (defenderToggle) {
        aiPlayerInfo.add_victory_conditions(
                net::eagle0::shardok::common::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS);
    } else {
        aiPlayerInfo.add_victory_conditions(
                net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
    }
    playerInfoProtos.push_back(aiPlayerInfo);

    // Human player
    net::eagle0::shardok::common::PlayerInfo humanPlayerInfo;
    humanPlayerInfo.set_player_id(HUMAN_PLAYER_ID);
    humanPlayerInfo.set_is_defender(!defenderToggle);
    humanPlayerInfo.set_starting_food(1000);
    humanPlayerInfo.add_victory_conditions(
            net::eagle0::shardok::common::VICTORY_CONDITION_LAST_PLAYER_STANDING);
    if (!defenderToggle) {
        humanPlayerInfo.add_victory_conditions(
                net::eagle0::shardok::common::VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS);
    } else {
        humanPlayerInfo.add_victory_conditions(
                net::eagle0::shardok::common::VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
    }
    playerInfoProtos.push_back(humanPlayerInfo);

    // Create units
    std::vector<net::eagle0::shardok::storage::fb::Unit> units;

    // Create AI units in reserve (location -1, -1)
    for (int i = 0; i < aiUnitCount && i < 6; ++i) {
        units.push_back(AddGenericUnit(
                AI_PLAYER_ID,
                i,                                                  // Unit ID
                net::eagle0::shardok::storage::fb::Coords(-1, -1),  // Reserve location
                i + 1,  // Profession: 1-6 (Mage through Strategist)
                HEAVY_INFANTRY_BATTALION_TYPE,
                defenderToggle ? -1 : 0));  // Defender: -1, Attacker: 0
    }

    // Create human units in reserve (location -1, -1)
    for (int i = 0; i < humanUnitCount && i < 6; ++i) {
        units.push_back(AddGenericUnit(
                HUMAN_PLAYER_ID,
                aiUnitCount + i,                                    // Unit ID starting aiUnitCount
                net::eagle0::shardok::storage::fb::Coords(-1, -1),  // Reserve location
                NO_PROFESSION,
                HEAVY_INFANTRY_BATTALION_TYPE,
                defenderToggle ? 0 : -1));  // Defender: -1, Attacker: 0
    }

    // Use the proper SetupInitialGameState helper (setup phase will be handled by AI)
    return shardok::fb::SetupInitialGameState(
            "performance_test_game",  // gameId
            hexMapProto,
            playerInfoProtos,
            units,
            4,      // month
            false,  // isWinter
            settings->GetGetter());
}

auto PerformanceTestGameStateBuilder::AddPlayerInfo(
        flatbuffers::FlatBufferBuilder& fbb,
        int playerId,
        bool isDefender,
        int food) -> flatbuffers::Offset<net::eagle0::shardok::storage::fb::PlayerInfo> {
    std::vector<int8_t> victoryConditions{
            net::eagle0::shardok::storage::fb::
                    VictoryCondition_VICTORY_CONDITION_LAST_PLAYER_STANDING};

    if (isDefender) {
        victoryConditions.push_back(
                net::eagle0::shardok::storage::fb::
                        VictoryCondition_VICTORY_CONDITION_WIN_AFTER_MAX_ROUNDS);
    } else {
        victoryConditions.push_back(
                net::eagle0::shardok::storage::fb::
                        VictoryCondition_VICTORY_CONDITION_HOLDS_CRITICAL_TILES);
    }

    auto victoryConditionsOffset = fbb.CreateVector(victoryConditions);

    net::eagle0::shardok::storage::fb::PlayerInfoBuilder pib(fbb);
    pib.add_player_id(playerId);
    pib.add_starting_food(food);
    pib.add_is_defender(isDefender);
    pib.add_victory_conditions(victoryConditionsOffset);

    return pib.Finish();
}

auto PerformanceTestGameStateBuilder::AddGenericUnit(
        PlayerId playerId,
        UnitId unitId,
        const net::eagle0::shardok::storage::fb::Coords& location,
        int profession,
        int battalionType,
        int startingPositionIndex) -> net::eagle0::shardok::storage::fb::Unit {
    net::eagle0::shardok::storage::fb::Unit unit{};  // Initialize to zero

    // Basic unit properties (following UnitConversions.cpp pattern)
    unit.mutate_player_id(playerId);
    unit.mutate_unit_id(unitId);
    unit.mutate_eagle_player_id(playerId);  // Set eagle player ID
    unit.mutable_location() = location;
    unit.mutate_status(net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT);
    unit.mutate_remaining_action_points(12);
    unit.mutate_hidden(false);
    unit.mutate_fortified(false);
    unit.mutate_can_flee(true);
    unit.mutate_can_start_fire(false);
    unit.mutate_can_archery(false);
    unit.mutate_stun_rounds_remaining(0);
    unit.mutate_commanding_unit_id(-1);
    unit.mutate_targeted_unit(-1);
    unit.mutate_starting_position_index(startingPositionIndex);
    unit.mutate_has_moved_in_zoc(false);
    unit.mutate_volleys_remaining(0);
    unit.mutate_food_remaining(1000.0f);  // Set food remaining

    // Battalion
    net::eagle0::shardok::storage::fb::Battalion battalion;
    battalion.mutate_type(
            static_cast<net::eagle0::shardok::storage::fb::BattalionTypeId>(battalionType));
    battalion.mutate_size(1000.0);
    battalion.mutate_armament(100.0f);
    battalion.mutate_training(100.0f);
    battalion.mutate_morale(50.0f);
    unit.mutable_battalion() = battalion;

    // Hero (if profession is specified)
    if (profession != NO_PROFESSION) {
        unit.mutate_has_attached_hero(true);

        net::eagle0::shardok::storage::fb::Hero hero;
        hero.mutate_strength(50);
        hero.mutate_strength_xp(0);
        hero.mutate_agility(50);
        hero.mutate_agility_xp(0);
        hero.mutate_wisdom(50);
        hero.mutate_wisdom_xp(0);
        hero.mutate_charisma(50);
        hero.mutate_charisma_xp(0);
        hero.mutate_constitution(80);
        hero.mutate_constitution_xp(0);
        hero.mutate_vigor(50);
        hero.mutate_starting_vigor(50);
        hero.mutate_spent_vigor(0);
        hero.mutate_bravery(50);
        hero.mutate_integrity(50);
        hero.mutate_ambition(50);
        hero.mutate_eagle_hero_id(unitId + 1);
        hero.mutate_is_vip(false);

        hero.mutable_profession_info().mutate_profession(
                static_cast<net::eagle0::shardok::storage::fb::Profession>(profession));
        hero.mutable_profession_info().mutate_meteor_cast_state(
                net::eagle0::shardok::storage::fb::MultiroundMagicState_NONE);

        hero.mutable_control_info().mutate_controlled_unit_id(-1);
        hero.mutable_control_info().mutate_controlled_this_round(false);

        unit.mutable_attached_hero() = hero;
    } else {
        unit.mutate_has_attached_hero(false);
    }

    // Initialize opponent knowledge for both players (player IDs 0 and 1)
    unit.mutable_opponent_knowledge()->Mutate(0, 0);  // Player 0 knowledge
    unit.mutable_opponent_knowledge()->Mutate(1, 0);  // Player 1 knowledge

    return unit;
}

}  // namespace shardok