//
// Shardok-specific game state adapter implementation
//

#include "ShardokGameState.hpp"

#include <sstream>
#include <string>
#include <utility>

#include "src/main/cpp/net/eagle0/shardok/ai/score/AIScoreCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"

namespace shardok::mcts {

ShardokGameState::ShardokGameState(
        GameStateW state,
        const AIScoreCalculator* calculator,
        const GameSettings* settings,
        const bool isDefender,
        AIStrategy strategy,
        const CoordsSet& castleCoords,
        const APDCache& apdCache,
        const ALCache& alCache,
        const CoordsSet& criticalTileCoords)
    : state_(std::move(state)),
      scoreCalculator_(calculator),
      settings_(settings),
      isDefender_(isDefender),
      strategy_(std::move(strategy)),
      castleCoords_(castleCoords),
      apdCache_(apdCache),
      alCache_(alCache),
      criticalTileCoords_(criticalTileCoords) {}

uint64_t ShardokGameState::hash() const {
    if (!hashCached_) {
        cachedHash_ = state_.ComputeFNV1aHash();
        hashCached_ = true;
    }
    return cachedHash_;
}

double ShardokGameState::score(MCTSPlayerId playerId) const {
    // Honor the interface contract: score() should return evaluation from playerId's perspective.
    // Map the requested playerId to defender/attacker role to determine scoring perspective.

    // Look up which player ID is the defender from game state
    bool foundDefender = false;
    bool requestedPlayerIsDefender = false;

    if (state_->player_infos()) {
        for (const auto* pi : *state_->player_infos()) {
            if (pi && pi->is_defender()) {
                foundDefender = true;
                requestedPlayerIsDefender = (static_cast<PlayerId>(playerId) == pi->player_id());
                break;
            }
        }
    }

    // Fallback: if we can't determine from game state, use isDefender_ which represents
    // the root player's role (and playerId is always the root player in practice)
    const bool scoreFromDefenderPerspective =
            foundDefender ? requestedPlayerIsDefender : isDefender_;

    // Score the current state directly
    return scoreCalculator_
            ->GuessedStateScore(scoreFromDefenderPerspective, state_, strategy_, castleCoords_);
}

MCTSPlayerId ShardokGameState::currentPlayerId() const { return state_->current_player(); }

bool ShardokGameState::isTerminal() const {
    // Check if game status indicates the game is over
    if (state_->status()) {
        const auto gameStatus = state_->status()->state();
        if (gameStatus == net::eagle0::shardok::storage::fb::GameStatus_::State_VICTORY ||
            gameStatus == net::eagle0::shardok::storage::fb::GameStatus_::State_DRAW) {
            return true;
        }
    }

    // Check max rounds
    if (state_->current_round() >= settings_->GetGetter().Backing().max_rounds()) { return true; }

    return false;
}

std::unique_ptr<MCTSGameState> ShardokGameState::clone() const {
    auto cloned = std::make_unique<ShardokGameState>(
            state_,
            scoreCalculator_,
            settings_,
            isDefender_,
            strategy_,
            castleCoords_,
            apdCache_,
            alCache_,
            criticalTileCoords_);
    return cloned;
}

bool ShardokGameState::equals(const MCTSGameState& other) const {
    const auto* shardokOther = dynamic_cast<const ShardokGameState*>(&other);
    if (!shardokOther) { return false; }

    return hash() == shardokOther->hash();
}

MCTSPlayerId ShardokGameState::getWinner() const {
    if (!state_->status() ||
        state_->status()->state() !=
                net::eagle0::shardok::storage::fb::GameStatus_::State_VICTORY) {
        return -1;
    }

    const auto* winningIds = state_->status()->winning_shardok_ids();
    if (!winningIds || winningIds->empty()) { return -1; }

    for (const PlayerId winningPid : *winningIds) {
        if (winningPid >= 0) { return winningPid; }
    }

    return -1;  // No winner
}

std::string ShardokGameState::toString() const {
    std::stringstream ss;
    ss << "ShardokGameState[Round:" << static_cast<int>(state_->current_round())
       << " Player:" << currentPlayerId() << " Hash:" << hash() << "]";
    return ss.str();
}

}  // namespace shardok::mcts
