//
// Self-Play Battle Runner Implementation
//

#include "SelfPlayBattleRunner.hpp"

#include <iostream>

#include "src/main/cpp/net/eagle0/shardok/ai_battle_simulator/AiBattleSimulator.hpp"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
#include "src/main/protobuf/net/eagle0/shardok/common/command_type.pb.h"
#pragma clang diagnostic pop

namespace shardok {
namespace ml {

namespace {
constexpr PlayerId ATTACKER_ID = 0;
constexpr PlayerId DEFENDER_ID = 1;
}  // namespace

SelfPlayBattleRunner::SelfPlayBattleRunner(
        const net::eagle0::shardok::ai_battle_simulator::BattleConfig& config,
        GameSettingsSPtr settings)
    : config_(config),
      gameSettings_(std::move(settings)) {}

SelfPlayResult SelfPlayBattleRunner::RunBattleWithRecording(
        StateRecorder& recorder,
        AIAlgorithmType aiAlgorithm,
        bool verbose) {
    // Silence unused parameter warning (CreateAIClient uses config, not the parameter)
    (void)aiAlgorithm;

    // Use AiBattleSimulator as friend to access its internals
    ai_battle_simulator::AiBattleSimulator simulator(config_, gameSettings_);

    if (verbose) { std::cout << "Creating initial game state...\n"; }

    // Use simulator's private method to create initial state
    auto gameState = simulator.CreateInitialGameState();
    const auto* hexMap = gameState->hex_map();

    if (verbose) { std::cout << "Creating AI clients...\n"; }

    // Create AI clients with the specified algorithm
    auto attackerAI = simulator.CreateAIClient(ATTACKER_ID, hexMap);
    auto defenderAI = simulator.CreateAIClient(DEFENDER_ID, hexMap);

    // Override the AI algorithm type if needed
    // (CreateAIClient uses config, but we want to override it)
    // For now, we'll work with what the config specifies

    // Create the engine
    if (verbose) { std::cout << "Creating engine...\n"; }
    ShardokEngine engine(simulator.gameSettings_, gameState);

    // Run setup phase (without recording - setup moves aren't interesting for training)
    if (verbose) { std::cout << "Running setup phase...\n"; }
    auto setupResult = simulator.RunSetupPhase(engine, *attackerAI, *defenderAI);

    if (setupResult.endReason != ai_battle_simulator::BattleResult::EndReason::DRAW) {
        // Game ended during setup (shouldn't normally happen)
        SelfPlayResult result;
        result.winner = setupResult.winner;
        result.totalRounds = setupResult.totalRounds;
        result.totalCommands = setupResult.totalCommands;
        result.statesRecorded = 0;
        result.description = setupResult.description;
        return result;
    }

    // Run battle phase WITH recording
    if (verbose) { std::cout << "Running battle phase with recording...\n"; }
    return RunBattlePhaseWithRecording(engine, *attackerAI, *defenderAI, recorder, verbose);
}

SelfPlayResult SelfPlayBattleRunner::RunBattlePhaseWithRecording(
        ShardokEngine& engine,
        ShardokAIClient& attackerAI,
        ShardokAIClient& defenderAI,
        StateRecorder& recorder,
        bool verbose) {
    int totalCommands = 0;
    int statesRecorded = 0;
    int currentRound = 1;

    // Main game loop with state recording
    while (!engine.GameIsOver() && currentRound <= config_.max_rounds()) {
        auto currentState = engine.GetCurrentGameState();
        PlayerId currentPlayer = currentState->current_player();

        auto availableCommands = engine.GetAvailableCommandProtos(currentPlayer, false);
        if (availableCommands.empty()) {
            if (verbose) {
                std::cout << "No commands available for player " << static_cast<int>(currentPlayer)
                          << "\n";
            }
            break;
        }

        // Choose which AI to use
        ShardokAIClient& activeAI = (currentPlayer == ATTACKER_ID) ? attackerAI : defenderAI;

        // Get AI decision
        auto choiceResults = activeAI.ChooseCommandIndex(engine);
        const auto& chosenCommand = availableCommands[choiceResults.chosenIndex];

        // Check if this is an END_TURN command
        const bool isEndTurn =
                chosenCommand.type() == net::eagle0::shardok::common::END_TURN_COMMAND;

        // Apply command
        engine.PostCommand(currentPlayer, choiceResults.chosenIndex);
        totalCommands++;

        // Record state AFTER the command (unless it was END_TURN)
        if (!isEndTurn) {
            auto newState = engine.GetCurrentGameState();
            PlayerId stillCurrentPlayer = newState->current_player();

            // Get the player's view (partial information)
            auto playerView = engine.GetGameStateView(stillCurrentPlayer);

            // Record it!
            recorder.RecordStateView(playerView, stillCurrentPlayer);
            statesRecorded++;

            if (verbose && statesRecorded % 50 == 0) {
                std::cout << "  Recorded " << statesRecorded << " states...\n";
            }
        }

        // Check round progression
        auto newState = engine.GetCurrentGameState();
        if (newState->current_round() > currentRound) {
            if (verbose) {
                std::cout << "Round " << currentRound << " completed. States: " << statesRecorded
                          << "\n";
            }
            currentRound = newState->current_round();
        }
    }

    // Determine winner
    auto finalState = engine.GetCurrentGameState();
    const auto* status = finalState->status();
    PlayerId winner = -1;

    if (status->state() == net::eagle0::shardok::storage::fb::GameStatus_::State_VICTORY) {
        const auto* winningIds = status->winning_shardok_ids();
        if (winningIds && winningIds->size() > 0) { winner = winningIds->Get(0); }
    }

    SelfPlayResult result;
    result.winner = winner;
    result.totalRounds = currentRound;
    result.totalCommands = totalCommands;
    result.statesRecorded = statesRecorded;

    if (winner == ATTACKER_ID) {
        result.description = "Attacker won";
    } else if (winner == DEFENDER_ID) {
        result.description = "Defender won";
    } else {
        result.description = "Draw";
    }

    if (verbose) {
        std::cout << "Battle complete: " << result.description
                  << " (States recorded: " << statesRecorded << ")\n";
    }

    return result;
}

}  // namespace ml
}  // namespace shardok
