//
// Standard implementation of AIScoreCalculator
//

#include "StandardAIScoreCalculator.hpp"

#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <unordered_map>

#include "private/AIScoreCalculatorSharedUtilities.hpp"
#include "private/AbstractAIScoreCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackGroups.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIAttackLocations.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIExperimentConfig.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::AttackerMultiplierForTargetDistance;
using score_calculator_internal::CAPTURED_UNIT_SCORE;
using score_calculator_internal::CAPTURED_VIP_SCORE;
using score_calculator_internal::EffectiveDistanceCache;
using score_calculator_internal::FleeStrategyScoreForState;
using score_calculator_internal::UNITS_BASE_MULTIPLIER;

// Forward declare the implementation class
class StandardAIScoreCalculator;

// Anonymous namespace for helper functions that don't need access to scorer
namespace {

// Performance logging for AttackerScoreForState
struct AttackerScorePerformanceLogger {
    static constexpr int LOG_INTERVAL = 100000;

    static std::atomic<int> callCount;
    static std::atomic<double> intervalTime;
    static std::atomic<double> totalTime;

    static void LogCall(double duration) {
        callCount.fetch_add(1);
        intervalTime.fetch_add(duration);
        totalTime.fetch_add(duration);

        if (callCount.load() % LOG_INTERVAL == 0) {
            double intervalAvg = intervalTime.load() / LOG_INTERVAL;
            double overallAvg = totalTime.load() / callCount.load();
            std::ostringstream message;
            message << std::fixed << std::setprecision(1)
                    << "AttackerScoreForState: " << callCount.load() << " calls, last "
                    << LOG_INTERVAL << " avg: " << intervalAvg * 1000000.0
                    << " µs, overall avg: " << overallAvg * 1000000.0 << " µs\n";
            std::cout << message.str();
            intervalTime.store(0.0);  // Reset for next interval
        }
    }
};

struct StandardScoringExperiment {
    double attackerUnitsScale = 1.0;
    double defenderUnitsScale = 1.0;
    double victoryConditionScale = 1.0;
    double unitsBaseMultiplierScale = 1.0;
    double attackerRoundMultiplier = 1.0;
    double defenderRoundAdvantageScale = 1.0;
};

auto ExperimentalScoringId() -> int { return CurrentAIExperimentId(); }

auto ScoringExperimentForId(const int id) -> StandardScoringExperiment {
    StandardScoringExperiment experiment;

    switch (id) {
        case 1: experiment.attackerUnitsScale = 1.05; break;
        case 2: experiment.attackerUnitsScale = 1.10; break;
        case 3: experiment.attackerUnitsScale = 0.95; break;
        case 4: experiment.attackerUnitsScale = 0.90; break;
        case 5: experiment.defenderUnitsScale = 0.95; break;
        case 6: experiment.defenderUnitsScale = 0.90; break;
        case 7: experiment.defenderUnitsScale = 1.05; break;
        case 8: experiment.defenderUnitsScale = 1.10; break;
        case 9: experiment.victoryConditionScale = 1.10; break;
        case 10: experiment.victoryConditionScale = 1.25; break;
        case 11: experiment.victoryConditionScale = 0.90; break;
        case 12: experiment.victoryConditionScale = 0.75; break;
        case 13: experiment.unitsBaseMultiplierScale = 0.90; break;
        case 14: experiment.unitsBaseMultiplierScale = 1.10; break;
        case 15: experiment.attackerRoundMultiplier = 0.90; break;
        case 16: experiment.attackerRoundMultiplier = 0.75; break;
        case 17: experiment.defenderRoundAdvantageScale = 0.50; break;
        case 18: experiment.defenderRoundAdvantageScale = 0.0; break;
        case 19:
            experiment.attackerUnitsScale = 1.10;
            experiment.victoryConditionScale = 1.10;
            break;
        case 20:
            experiment.defenderUnitsScale = 0.90;
            experiment.victoryConditionScale = 1.10;
            break;
        default: break;
    }

    return experiment;
}

auto AdjustDefenderUnitsForExperiment(
        double defenderUnitsValue,
        const int roundsRemaining,
        const int maxRounds,
        const StandardScoringExperiment &experiment) -> double {
    const double currentRound = static_cast<double>(maxRounds - roundsRemaining);
    const double standardAdvantage = 1.0 + currentRound / 31.0;
    if (standardAdvantage <= 0.0) { return defenderUnitsValue * experiment.defenderUnitsScale; }

    const double unadjustedDefenderValue = defenderUnitsValue / standardAdvantage;
    const double adjustedAdvantage =
            1.0 + experiment.defenderRoundAdvantageScale * currentRound / 31.0;
    return unadjustedDefenderValue * adjustedAdvantage * experiment.defenderUnitsScale;
}

std::atomic<int> AttackerScorePerformanceLogger::callCount{0};
std::atomic<double> AttackerScorePerformanceLogger::intervalTime{0.0};
std::atomic<double> AttackerScorePerformanceLogger::totalTime{0.0};

// RAII timer for automatic performance logging
class AttackerScoreTimer {
private:
    std::chrono::high_resolution_clock::time_point startTime;

public:
    AttackerScoreTimer() : startTime(std::chrono::high_resolution_clock::now()) {}
    AttackerScoreTimer(const AttackerScoreTimer &) = delete;
    auto operator=(const AttackerScoreTimer &) -> AttackerScoreTimer & = delete;
    AttackerScoreTimer(AttackerScoreTimer &&) = delete;
    auto operator=(AttackerScoreTimer &&) -> AttackerScoreTimer & = delete;

    ~AttackerScoreTimer() {
        auto endTime = std::chrono::high_resolution_clock::now();
        auto duration =
                std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime);
        AttackerScorePerformanceLogger::LogCall(duration.count());
    }
};
}  // anonymous namespace

/// Standard implementation of AIScoreCalculator that uses the default scoring algorithm.
/// Inherits from AbstractAIScoreCalculator to share common functionality.
class StandardAIScoreCalculator : public AbstractAIScoreCalculator {
public:
    StandardAIScoreCalculator(
            int maxRounds,
            ActionPoints braveWaterCost,
            ActionPoints archeryActionPointCost,
            int meteorRange,
            double meteorCastVigorCost,
            int minimumFleeOddsThreshold,
            int desperateFleeThreshold,
            std::vector<BattalionTypeSPtr> battalionTypes,
            const APDCache &apdCache,
            const ALCache &alCache,
            StandardScoringExperiment experiment = {})
        : AbstractAIScoreCalculator(
                  maxRounds,
                  braveWaterCost,
                  archeryActionPointCost,
                  meteorRange,
                  meteorCastVigorCost,
                  minimumFleeOddsThreshold,
                  desperateFleeThreshold,
                  std::move(battalionTypes),
                  apdCache,
                  alCache),
          experiment_(experiment) {}

    [[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:
    // Implementation methods (converted from internal namespace functions)
    [[nodiscard]] auto AttackerUnitsScore(
            const GameStateW &gameState,
            int roundsRemaining,
            bool attackerWantsCastles,
            bool defenderShouldScatter,
            const vector<TargetPriorityList> &attackerTargetPriorities,
            const MapId &mapId) const -> ScoreValue;

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

    StandardScoringExperiment experiment_;
};

// Implementation of StandardAIScoreCalculator methods

auto StandardAIScoreCalculator::InterpretDefenderOutcome(GameOutcome outcome) const -> ScoreValue {
    switch (outcome) {
        case GameOutcome::DEFENDER_VICTORY: return INT_MAX;
        case GameOutcome::ATTACKER_VICTORY: return INT_MIN;
        case GameOutcome::DRAW: return INT_MAX;
        case GameOutcome::FLEE_OUTCOME: return 0;
    }
    throw ShardokInternalErrorException("Unknown GameOutcome");
}

auto StandardAIScoreCalculator::InterpretAttackerOutcome(GameOutcome outcome) const -> ScoreValue {
    switch (outcome) {
        case GameOutcome::ATTACKER_VICTORY: return INT_MAX;
        case GameOutcome::DEFENDER_VICTORY: return INT_MIN;
        case GameOutcome::DRAW: return INT_MIN;
        case GameOutcome::FLEE_OUTCOME: return 0;
    }
    throw ShardokInternalErrorException("Unknown GameOutcome");
}

auto StandardAIScoreCalculator::AttackerUnitsScore(
        const GameStateW &gameState,
        int roundsRemaining,
        bool attackerWantsCastles,
        bool defenderShouldScatter,
        const vector<TargetPriorityList> &attackerTargetPriorities,
        const MapId &mapId) const -> ScoreValue {
    // Use the base class implementation to get separated attacker/defender values
    const auto components = CalculateUnitsScoreComponents(
            gameState,
            roundsRemaining,
            attackerWantsCastles,
            defenderShouldScatter,
            attackerTargetPriorities,
            mapId);

    // Standard scorer returns the difference (attacker - defender)
    return components.attackerUnitsValue - components.defenderUnitsValue;
}

auto StandardAIScoreCalculator::CombineDefenderScatterScores(
        const UnitsScoreComponents &components) const -> ScoreValue {
    // For defender scatter, we want to maximize defender units and minimize attacker units
    // From defender's perspective: negate the attacker-defender difference
    return components.defenderUnitsValue - components.attackerUnitsValue;
}

auto StandardAIScoreCalculator::CombineDefenderHoldCastlesScores(
        const UnitsScoreComponents &components,
        const double victoryConditionScore,
        const int roundsRemaining) const -> ScoreValue {
    (void)roundsRemaining;  // Intentionally unused for now
    // From defender's perspective: negate the attacker-defender difference
    const double unitsDifference = components.defenderUnitsValue - components.attackerUnitsValue;
    // TODO: The time-decay multiplier (roundsRemaining/maxRounds) was causing END_TURN
    // to score better than tactical actions because it reduced the penalty for having
    // fewer units. Setting to constant 1.0 for now to fix tactical decision-making.
    const double unitsMultiplier = 1.0;
    // const double unitsMultiplier =
    //         static_cast<double>(roundsRemaining) / static_cast<double>(GetMaxRounds());
    const double finalScore =
            UNITS_BASE_MULTIPLIER * unitsMultiplier * unitsDifference + victoryConditionScore;

    return finalScore;
}

auto StandardAIScoreCalculator::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 StandardAIScoreCalculator::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 StandardAIScoreCalculator::CombineAttackerScores(
        const UnitsScoreComponents &components,
        const double victoryConditionScore,
        const int roundsRemaining) const -> ScoreValue {
    const double attackerUnitsValue =
            components.attackerUnitsValue * experiment_.attackerUnitsScale;
    const double defenderUnitsValue = AdjustDefenderUnitsForExperiment(
            components.defenderUnitsValue,
            roundsRemaining,
            GetMaxRounds(),
            experiment_);
    const double unitsDifference = attackerUnitsValue - defenderUnitsValue;
    // Keep tactical unit value relevant late in the battle. The previous time decay could make
    // attackers undervalue decisive combat near the round limit.
    const double unitsMultiplier = experiment_.attackerRoundMultiplier;
    return UNITS_BASE_MULTIPLIER * experiment_.unitsBaseMultiplierScale * unitsMultiplier *
                   unitsDifference +
           victoryConditionScore * experiment_.victoryConditionScale;
}

auto StandardAIScoreCalculator::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 MakeStandardAIScoreCalculatorWithExperiment(
        const SettingsGetter &settingsGetter,
        const APDCache &apdCache,
        const ALCache &alCache,
        const StandardScoringExperiment &experiment) -> 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<StandardAIScoreCalculator>(
            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,
            experiment);
}

auto MakeStandardAIScoreCalculator(
        const SettingsGetter &settingsGetter,
        const APDCache &apdCache,
        const ALCache &alCache) -> std::unique_ptr<AIScoreCalculator> {
    return MakeStandardAIScoreCalculatorWithExperiment(
            settingsGetter,
            apdCache,
            alCache,
            StandardScoringExperiment{});
}

auto MakeExperimentalAIScoreCalculator(
        const SettingsGetter &settingsGetter,
        const APDCache &apdCache,
        const ALCache &alCache) -> std::unique_ptr<AIScoreCalculator> {
    return MakeStandardAIScoreCalculatorWithExperiment(
            settingsGetter,
            apdCache,
            alCache,
            ScoringExperimentForId(ExperimentalScoringId()));
}

}  // namespace shardok
