//
// MCTS-Optimized implementation of AIScoreCalculator
// Uses bounded linear scoring tuned for MCTS exploration/exploitation balance
//

#include "MCTSOptimizedAIScoreCalculator.hpp"

#include <algorithm>
#include <unordered_map>

#include "private/AIScoreCalculatorSharedUtilities.hpp"
#include "private/AbstractAIScoreCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIScoreUtilities.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIStrategy.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIUnitScoreCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/score/AIScoreCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistancesCache.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"

namespace shardok {

using net::eagle0::shardok::storage::fb::BattalionTypeId;

// Bring shared utilities into scope
using score_calculator_internal::FleeStrategyScoreForState;
using score_calculator_internal::UnitsScoreComponents;

// Scoring constants tuned for MCTS
namespace {

// Scale constants designed to produce score differences in the range that works well for MCTS
// With C=1.41 and typical parent visits ~10000, exploration term ≈ 0.42
// We want:
// - Early game tactical moves: 0.3-0.5 difference (ratio 0.7-1.2x exploration)
// - Mid game advantages (10-30%): 4.0-8.0 difference (ratio 9-19x exploration)
// - Late game crushing advantages: 10-40 difference (ratio 24-95x exploration)

// Maximum contribution from proportional unit advantage (applies to both battle sizes)
// A 100% unit advantage (all attacker, no defender) produces ±80 score
constexpr double UNITS_SCORE_SCALE = 80.0;

// Minimum reference value to avoid division by zero in edge cases
constexpr double MIN_REFERENCE_VALUE = 1000.0;

// IMPORTANT: Victory condition scores are NOT normalized by army size
// They represent absolute strategic goals (castle control, etc.) that should not
// diminish as more units are placed. Typical range: -3000 to +3000 (raw).
// Scaling factor of 0.01 brings them to -30 to +30 range.

}  // anonymous namespace

/// MCTS-Optimized implementation of AIScoreCalculator.
/// Produces bounded linear scores that balance MCTS exploration and exploitation.
/// Inherits from AbstractAIScoreCalculator to share common functionality.
class MCTSOptimizedAIScoreCalculator : public AbstractAIScoreCalculator {
public:
    MCTSOptimizedAIScoreCalculator(
            int maxRounds,
            ActionPoints braveWaterCost,
            ActionPoints archeryActionPointCost,
            int meteorRange,
            double meteorCastVigorCost,
            int minimumFleeOddsThreshold,
            int desperateFleeThreshold,
            std::vector<BattalionTypeSPtr> battalionTypes,
            const APDCache &apdCache,
            const ALCache &alCache)
        : AbstractAIScoreCalculator(
                  maxRounds,
                  braveWaterCost,
                  archeryActionPointCost,
                  meteorRange,
                  meteorCastVigorCost,
                  minimumFleeOddsThreshold,
                  desperateFleeThreshold,
                  std::move(battalionTypes),
                  apdCache,
                  alCache) {}

    [[nodiscard]] auto GuessedStateScore(
            bool isDefender,
            const GameStateW &state,
            const AIStrategy &aiStrategy,
            const CoordsSet &allCastleCoords) const -> ScoreValue override;

    // Implement pure virtual methods from AbstractAIScoreCalculator
    [[nodiscard]] auto InterpretDefenderOutcome(GameOutcome outcome) const -> ScoreValue override;
    [[nodiscard]] auto InterpretAttackerOutcome(GameOutcome outcome) const -> ScoreValue override;

    [[nodiscard]] auto AttackerFleeStrategyScoreForState(const GameStateW &gameState) const
            -> ScoreValue override;

    [[nodiscard]] auto CombineAttackerScores(
            const UnitsScoreComponents &components,
            double victoryConditionScore,
            int roundsRemaining) const -> ScoreValue override;

    [[nodiscard]] auto CombineDefenderScatterScores(const UnitsScoreComponents &components) const
            -> ScoreValue override;

    [[nodiscard]] auto CombineDefenderHoldCastlesScores(
            const UnitsScoreComponents &components,
            double victoryConditionScore,
            int roundsRemaining) const -> ScoreValue override;

private:
    [[nodiscard]] auto DefenderFleeStrategyScoreForState(const GameStateW &gameState) const
            -> ScoreValue override;
};

// Implementation of MCTSOptimizedAIScoreCalculator methods

auto MCTSOptimizedAIScoreCalculator::InterpretDefenderOutcome(GameOutcome outcome) const
        -> ScoreValue {
    // Use bounded values instead of INT_MAX/MIN for numerical stability
    switch (outcome) {
        case GameOutcome::DEFENDER_VICTORY: return 1000.0;
        case GameOutcome::ATTACKER_VICTORY: return -1000.0;
        case GameOutcome::DRAW: return 0.0;
        case GameOutcome::FLEE_OUTCOME: return 0.0;
    }
    throw ShardokInternalErrorException("Unknown GameOutcome");
}

auto MCTSOptimizedAIScoreCalculator::InterpretAttackerOutcome(GameOutcome outcome) const
        -> ScoreValue {
    // Use bounded values instead of INT_MAX/MIN for numerical stability
    switch (outcome) {
        case GameOutcome::ATTACKER_VICTORY: return 1000.0;
        case GameOutcome::DEFENDER_VICTORY: return -1000.0;
        case GameOutcome::DRAW: return 0.0;
        case GameOutcome::FLEE_OUTCOME: return 0.0;
    }
    throw ShardokInternalErrorException("Unknown GameOutcome");
}

auto MCTSOptimizedAIScoreCalculator::CombineAttackerScores(
        const UnitsScoreComponents &components,
        const double victoryConditionScore,
        const int roundsRemaining) const -> ScoreValue {
    // Use actual total army value as reference (scales with battle size)
    const double totalArmyValue = components.attackerUnitsValue + components.defenderUnitsValue;
    const double reference = std::max(totalArmyValue, MIN_REFERENCE_VALUE);

    // Normalize proportional unit difference to approximately [-80, +80] range
    const double unitsDiff = components.attackerUnitsValue - components.defenderUnitsValue;
    const double unitsScore = (unitsDiff / reference) * UNITS_SCORE_SCALE;

    // Victory condition score is an absolute strategic value, not normalized by army size
    // Scaling factor to bring victory scores into similar magnitude as unit scores
    const double victoryScore = victoryConditionScore * 0.01;

    // Weight units by rounds remaining (early: units matter less, late: units dominate)
    const double unitsMultiplier =
            static_cast<double>(roundsRemaining) / static_cast<double>(GetMaxRounds());

    return unitsMultiplier * unitsScore + victoryScore;
}

auto MCTSOptimizedAIScoreCalculator::CombineDefenderScatterScores(
        const UnitsScoreComponents &components) const -> ScoreValue {
    // Use actual total army value as reference
    const double totalArmyValue = components.attackerUnitsValue + components.defenderUnitsValue;
    const double reference = std::max(totalArmyValue, MIN_REFERENCE_VALUE);

    // For scatter strategy, just maximize proportional defender advantage
    const double unitsDiff = components.defenderUnitsValue - components.attackerUnitsValue;
    return (unitsDiff / reference) * UNITS_SCORE_SCALE;
}

auto MCTSOptimizedAIScoreCalculator::CombineDefenderHoldCastlesScores(
        const UnitsScoreComponents &components,
        const double victoryConditionScore,
        const int roundsRemaining) const -> ScoreValue {
    // Use actual total army value as reference
    const double totalArmyValue = components.attackerUnitsValue + components.defenderUnitsValue;
    const double reference = std::max(totalArmyValue, MIN_REFERENCE_VALUE);

    // Similar to attacker, but from defender's perspective
    const double unitsDiff = components.defenderUnitsValue - components.attackerUnitsValue;
    const double unitsScore = (unitsDiff / reference) * UNITS_SCORE_SCALE;

    // Victory condition score is an absolute strategic value, not normalized by army size
    // Scaling factor to bring victory scores into similar magnitude as unit scores
    const double victoryScore = victoryConditionScore * 0.01;

    const double unitsMultiplier =
            static_cast<double>(roundsRemaining) / static_cast<double>(GetMaxRounds());

    return unitsMultiplier * unitsScore + victoryScore;
}

auto MCTSOptimizedAIScoreCalculator::DefenderFleeStrategyScoreForState(
        const GameStateW &gameState) const -> ScoreValue {
    for (const auto *pi : *gameState->player_infos()) {
        if (pi->is_defender()) { return FleeStrategyScoreForState(gameState, pi->player_id()); }
    }
    throw ShardokInternalErrorException("Unable to find defender for FleeStrategy");
}

auto MCTSOptimizedAIScoreCalculator::AttackerFleeStrategyScoreForState(
        const GameStateW &gameState) const -> ScoreValue {
    for (const PlayerInfo *pi : *gameState->player_infos()) {
        if (!pi->is_defender()) { return FleeStrategyScoreForState(gameState, pi->player_id()); }
    }
    throw ShardokInternalErrorException("Unable to find attacker for FleeStrategy");
}

auto MCTSOptimizedAIScoreCalculator::GuessedStateScore(
        const bool isDefender,
        const GameStateW &state,
        const AIStrategy &aiStrategy,
        const CoordsSet &allCastleCoords) const -> ScoreValue {
    const int roundsRemaining = GetMaxRounds() - state->current_round();

    if (isDefender) {
        return DefenderScoreForState(state, aiStrategy, allCastleCoords, roundsRemaining);
    }
    return AttackerScoreForState(state, aiStrategy, allCastleCoords, roundsRemaining);
}

// Factory function implementation
auto MakeMCTSOptimizedAIScoreCalculator(
        const SettingsGetter &settingsGetter,
        const APDCache &apdCache,
        const ALCache &alCache) -> std::unique_ptr<AIScoreCalculator> {
    // Extract all battalion types into a vector indexed by BattalionTypeId
    std::vector<BattalionTypeSPtr> battalionTypes(BattalionTypeId::BattalionTypeId_MAX + 1);
    for (int typeId = BattalionTypeId::BattalionTypeId_MIN;
         typeId <= BattalionTypeId::BattalionTypeId_MAX;
         ++typeId) {
        auto battalionTypeId = static_cast<BattalionTypeId>(typeId);
        battalionTypes[battalionTypeId] = settingsGetter.GetBattalionType(battalionTypeId);
    }

    return std::make_unique<MCTSOptimizedAIScoreCalculator>(
            settingsGetter.Backing().max_rounds(),
            settingsGetter.Backing().brave_water_action_point_cost(),
            settingsGetter.Backing().archery_action_point_cost(),
            settingsGetter.Backing().meteor_range(),
            settingsGetter.Backing().meteor_cast_vigor_cost(),
            settingsGetter.Backing().ai_minimum_flee_odds_threshold(),
            settingsGetter.Backing().ai_desperate_flee_threshold(),
            std::move(battalionTypes),
            apdCache,
            alCache);
}

}  // namespace shardok
