//
// Created by Codex on 7/14/26.
//

#include "StartingPositionRecommender.hpp"

#include <algorithm>
#include <array>
#include <optional>
#include <ranges>

#include "AIConfig.hpp"
#include "ShardokAIClient.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/command_type.pb.h"

namespace shardok {

using net::eagle0::shardok::api::PlacementCommand;
using net::eagle0::shardok::common::CommandType;

namespace {

struct TutorialPlacement {
    UnitId unitId;
    int row;
    int column;
};

auto RecommendTutorialStartingPositions(const ShardokEngine& engine, const PlayerId playerId)
        -> std::optional<std::vector<PlacementCommand>> {
    const auto* units = engine.GetCurrentGameState()->units();
    const auto isUnplacedPlayerUnit = [playerId](const auto* unit) {
        return unit->player_id() == playerId &&
               unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT;
    };
    if (std::ranges::count_if(*units, isUnplacedPlayerUnit) != 3) return std::nullopt;

    const auto longbowmen = std::ranges::find_if(*units, [playerId](const auto* unit) {
        return unit->player_id() == playerId &&
               unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT &&
               unit->battalion().type() ==
                       net::eagle0::shardok::storage::fb::BattalionTypeId_LONGBOWMEN;
    });
    const auto sadar = std::ranges::find_if(*units, [playerId](const auto* unit) {
        return unit->player_id() == playerId &&
               unit->status() == net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT &&
               unit->attached_hero().profession_info().profession() ==
                       net::eagle0::shardok::storage::fb::Profession_CHAMPION;
    });
    const auto marek =
            std::ranges::find_if(*units, [playerId, &units, &longbowmen, &sadar](const auto* unit) {
                return unit->player_id() == playerId &&
                       unit->status() ==
                               net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT &&
                       (longbowmen == units->end() ||
                        unit->unit_id() != (*longbowmen)->unit_id()) &&
                       (sadar == units->end() || unit->unit_id() != (*sadar)->unit_id());
            });
    if (longbowmen == units->end() || sadar == units->end() || marek == units->end()) {
        return std::nullopt;
    }
    if ((*longbowmen)->unit_id() == (*sadar)->unit_id()) return std::nullopt;

    const std::array desiredPlacements{
            TutorialPlacement{(*longbowmen)->unit_id(), 6, 12},
            TutorialPlacement{(*sadar)->unit_id(), 8, 11},
            TutorialPlacement{(*marek)->unit_id(), 10, 11}};
    ShardokEngine simulatedEngine(engine, false);
    std::vector<PlacementCommand> recommendations;
    recommendations.reserve(desiredPlacements.size());

    for (const auto& placement : desiredPlacements) {
        const auto commands = simulatedEngine.GetAvailableCommandsForAIPlayer(playerId);
        const auto command = std::ranges::find_if(*commands, [&placement](const auto& candidate) {
            return candidate->GetActorUnitId() == placement.unitId &&
                   candidate->GetTargetRow() == placement.row &&
                   candidate->GetTargetColumn() == placement.column &&
                   (candidate->GetCommandType() ==
                            net::eagle0::shardok::common::PLACE_UNIT_COMMAND ||
                    candidate->GetCommandType() ==
                            net::eagle0::shardok::common::PLACE_HIDDEN_UNIT_COMMAND);
        });
        if (command == commands->end()) return std::nullopt;

        PlacementCommand recommendation;
        recommendation.set_unit_id(placement.unitId);
        recommendation.set_row(placement.row);
        recommendation.set_column(placement.column);
        recommendations.push_back(recommendation);
        simulatedEngine.PostCommand(playerId, std::distance(commands->begin(), command));
    }

    return recommendations;
}

}  // namespace

auto RecommendStartingPositions(
        const ShardokEngine& engine,
        const PlayerId playerId,
        const std::chrono::milliseconds timePerChoice) -> std::vector<PlacementCommand> {
    if (engine.GetGameStatus()->state() !=
                net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP ||
        engine.GetCurrentPlayerId() != playerId) {
        return {};
    }

    const auto playerInfos = engine.GetPlayerInfos();
    const auto playerInfo = std::ranges::find_if(playerInfos, [playerId](const auto& info) {
        return info.player_id() == playerId;
    });
    if (playerInfo == playerInfos.end()) return {};

    if (engine.IsTutorialBattleEnabled() && playerInfo->is_defender()) {
        if (const auto tutorialRecommendations =
                    RecommendTutorialStartingPositions(engine, playerId)) {
            return *tutorialRecommendations;
        }
    }

    ShardokEngine simulatedEngine(engine, false);

    mcts::MCTSConfig mctsConfig;
    mctsConfig.backpropagationPolicy = mcts::MCTSBackpropagationPolicy::MINIMAX;
    mctsConfig.maxPlayerFlips = 1;
    mctsConfig.maxSimulationFlips = 1;
    mctsConfig.simulationPolicy = mcts::MCTSSimulationPolicy::WEIGHTED_HEURISTIC;

    ShardokAIClient ai(
            playerId,
            playerInfo->is_defender(),
            false,
            simulatedEngine.GetCurrentGameState()->hex_map(),
            simulatedEngine.GetGameSettings()->GetGetter(),
            AIAlgorithmType::ITERATIVE_DEEPENING,
            ScoringCalculatorType::STANDARD,
            mctsConfig);

    std::vector<PlacementCommand> recommendations;
    while (simulatedEngine.GetGameStatus()->state() ==
                   net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP &&
           simulatedEngine.GetCurrentPlayerId() == playerId) {
        const auto commands = simulatedEngine.GetAvailableCommandsForAIPlayer(playerId);
        if (commands->empty()) break;

        const auto gameStateView = simulatedEngine.GetGameStateView(playerId);
        const auto choice = ai.ChooseCommandIndex(
                simulatedEngine.GetGameSettings(),
                gameStateView,
                commands,
                timePerChoice);
        const auto& command = (*commands)[choice.chosenIndex];
        const auto commandType = command->GetCommandType();

        if (commandType == net::eagle0::shardok::common::END_PLAYER_SETUP_COMMAND) break;
        if (commandType != net::eagle0::shardok::common::PLACE_UNIT_COMMAND &&
            commandType != net::eagle0::shardok::common::PLACE_HIDDEN_UNIT_COMMAND) {
            break;
        }

        PlacementCommand recommendation;
        recommendation.set_unit_id(command->GetActorUnitId());
        recommendation.set_row(command->GetTargetRow());
        recommendation.set_column(command->GetTargetColumn());
        recommendations.push_back(recommendation);

        simulatedEngine.PostCommand(playerId, static_cast<int64_t>(choice.chosenIndex));
    }

    return recommendations;
}

}  // namespace shardok
