//
// Created by Dan Crosby on 2025-01-15.
//

#include "AIPerformanceRunner.hpp"

#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>

#include "PerformanceTestGameStateBuilder.hpp"
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/FixedActionPointDistances.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/command_type.pb.h"

using namespace shardok;

namespace {

/**
 * Convert completion reason to human-readable string.
 */
auto CompletionReasonToString(EvaluationCompletionReason reason) -> std::string {
    switch (reason) {
        case EvaluationCompletionReason::RAN_OUT_OF_COMMANDS:
            return "completed all meaningful commands";
        case EvaluationCompletionReason::RAN_OUT_OF_TIME: return "time budget exhausted";
        case EvaluationCompletionReason::NOT_ENOUGH_TIME_TO_CONTINUE:
            return "insufficient time for next depth";
        default: return "unknown";
    }
}

/**
 * Parse command line arguments into a configuration struct.
 */
auto ParseCommandLineArgs(int argc, char* argv[]) -> PerformanceTestConfig {
    PerformanceTestConfig config;

    for (int i = 1; i < argc; ++i) {
        std::string arg(argv[i]);

        if (arg == "--help" || arg == "-h") {
            std::cout << "Shardok AI Performance Runner\n"
                      << "Usage: " << argv[0] << " [options]\n"
                      << "\n"
                      << "Options:\n"
                      << "  --map=NAME         Map name (default: Alah)\n"
                      << "  --turns=N          Number of turns to test (default: 5)\n"
                      << "  --defender=BOOL    AI is defender (default: false)\n"
                      << "  --verbose          Enable verbose output\n"
                      << "  --help, -h         Show this help message\n";
            std::exit(0);
        } else if (arg.starts_with("--map=")) {
            config.mapName = arg.substr(6);
        } else if (arg.starts_with("--turns=")) {
            config.numTurns = std::stoi(arg.substr(8));
        } else if (arg.starts_with("--defender=")) {
            std::string value = arg.substr(11);
            config.defenderToggle = (value == "true" || value == "1");
        } else if (arg == "--verbose") {
            config.verbose = true;
        } else {
            std::cerr << "Unknown argument: " << arg << "\n";
            std::cerr << "Use --help for usage information.\n";
            std::exit(1);
        }
    }

    return config;
}

}  // namespace

int main(int argc, char* argv[]) {
    std::cout << "Starting AI Performance Runner..." << std::endl;

    // Set exec path so FilesystemUtils can find resource files
    FilesystemUtils::SetExecPath(argv[0]);

    // Set cache directory for ActionPointDistances
    FixedActionPointDistances::SetCacheDirectory(
            FilesystemUtils::CacheFilesDirectory() + "apdCache/");

    try {
        std::cout << "Shardok AI Performance Runner\n";
        std::cout << "==============================\n";

        // Parse command line arguments
        auto config = ParseCommandLineArgs(argc, argv);

        if (config.verbose) {
            std::cout << "Configuration:\n";
            std::cout << "  Map: " << config.mapName << "\n";
            std::cout << "  Turns: " << config.numTurns << "\n";
            std::cout << "  AI is defender: " << (config.defenderToggle ? "Yes" : "No") << "\n";
        }

        // Initialize game settings
        auto settings = PerformanceTestGameStateBuilder::InitializeGameSettings();

        // Create test game state
        auto gameState = PerformanceTestGameStateBuilder::CreatePerfTestGameState(
                settings,
                config.defenderToggle);

        // Create engine
        ShardokEngine engine(settings, gameState);

        // Test basic functionality
        auto currentState = engine.GetCurrentGameState();

        // Create AI client for testing
        const PlayerId aiPlayerId = 0;
        const bool isDefender = config.defenderToggle;
        const auto* hexMap = currentState->hex_map();
        const auto settingsGetter = settings->GetGetter();

        ShardokAIClient aiClient(aiPlayerId, isDefender, hexMap, settingsGetter);

        // Create a second AI client for the human player during setup
        // This ensures consistent state handling during setup phase
        const PlayerId humanPlayerId = 1;
        ShardokAIClient humanSetupAI(humanPlayerId, !isDefender, hexMap, settingsGetter);

        // Complete setup phase - AI makes intelligent placement decisions
        if (currentState->status()->state() ==
            net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP) {
            while (currentState->status()->state() ==
                   net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP) {
                PlayerId currentPlayer = currentState->current_player();
                auto availableCommands = engine.GetAvailableCommandProtos(currentPlayer, false);

                if (availableCommands.empty()) {
                    std::cout << "No commands available for player "
                              << static_cast<int>(currentPlayer) << "\n";
                    break;
                }

                if (currentPlayer == aiPlayerId) {
                    // Let AI make intelligent placement decisions
                    auto choiceResults = aiClient.ChooseCommandIndex(engine);
                    engine.PostCommand(currentPlayer, choiceResults.chosenIndex);
                } else {
                    // Human player: use AI for setup to ensure consistent state handling
                    auto choiceResults = humanSetupAI.ChooseCommandIndex(engine);
                    engine.PostCommand(currentPlayer, choiceResults.chosenIndex);
                }

                currentState = engine.GetCurrentGameState();
            }
        }

        // Test AI performance for configured number of turns
        std::cout << "Running AI performance test for " << config.numTurns << " turns...\n";

        std::vector<AIPerformanceMetrics> metrics;

        for (int turn = 0; turn < config.numTurns; ++turn) {
            // Check if AI can make a move
            const auto availableCommands = engine.GetAvailableCommandProtos(aiPlayerId, false);
            if (availableCommands.empty()) {
                std::cout << "  No commands available for AI player. Ending test.\n";
                break;
            }

            // Get AI decision with performance metrics
            auto choiceResults = aiClient.ChooseCommandIndex(engine);

            std::cout << "  AI chose command index: " << choiceResults.chosenIndex << "\n";
            std::cout << "  Depth achieved: " << choiceResults.depthAchieved << "\n";
            std::cout << "  Commands evaluated: " << choiceResults.commandCountEvaluated << "/"
                      << choiceResults.availableCommandCount << "\n";

            // Create metrics for this turn
            AIPerformanceMetrics turnMetrics;
            turnMetrics.commandNumber = turn + 1;
            turnMetrics.totalCommands = static_cast<int>(choiceResults.availableCommandCount);
            turnMetrics.depthAchieved = choiceResults.depthAchieved;
            turnMetrics.commandsEvaluated = static_cast<int>(choiceResults.commandCountEvaluated);
            turnMetrics.selectedCommandType = net::eagle0::shardok::common::CommandType_Name(
                    availableCommands[choiceResults.chosenIndex].type());
            turnMetrics.completionReason = choiceResults.completionReason;

            metrics.push_back(turnMetrics);

            if (config.verbose) {
                std::cout << "  Command: " << turnMetrics.selectedCommandType << "\n";
                std::cout << "  Search depth: " << turnMetrics.depthAchieved << "\n";
                std::cout << "  Commands evaluated: " << turnMetrics.commandsEvaluated << "\n";
                std::cout << "  Applying command...\n";
            }

            // Apply the chosen command
            engine.PostCommand(aiPlayerId, choiceResults.chosenIndex);

            // Check if game is over
            if (engine.GameIsOver()) {
                std::cout << "  Game over after " << (turn + 1) << " turns.\n";
                break;
            }
        }

        // Print summary
        std::cout << "\nAI Search Performance Summary:\n";
        std::cout << "==============================\n";
        std::cout << "Total turns: " << metrics.size() << "\n";

        if (!metrics.empty()) {
            // Calculate summary statistics
            double avgDepth = 0.0;
            int totalEvaluated = 0;
            int totalAvailable = 0;

            for (const auto& metric : metrics) {
                avgDepth += metric.depthAchieved;
                totalEvaluated += metric.commandsEvaluated;
                totalAvailable += metric.totalCommands;
            }

            avgDepth /= metrics.size();

            std::cout << "Average search depth: " << std::fixed << std::setprecision(1) << avgDepth
                      << "\n";
            std::cout << "Total commands evaluated: " << totalEvaluated << "/" << totalAvailable
                      << "\n";

            // Calculate evaluation rate by depth
            // Find max depth achieved across all turns
            int maxDepth = 0;
            for (const auto& metric : metrics) {
                maxDepth = std::max(maxDepth, metric.depthAchieved);
            }

            if (maxDepth >= 2) {
                std::cout << "\nCommands evaluated by depth:\n";
                for (int depth = 2; depth <= maxDepth; ++depth) {
                    int turnsAtThisDepth = 0;
                    int totalCommandsAtDepth = 0;
                    int totalCommandsAvailableAtDepth = 0;

                    for (const auto& metric : metrics) {
                        bool reachedThisDepth = metric.depthAchieved >= depth;
                        bool completedAtLowerDepth =
                                (metric.depthAchieved < depth &&
                                 metric.completionReason ==
                                         EvaluationCompletionReason::RAN_OUT_OF_COMMANDS);

                        if (reachedThisDepth || completedAtLowerDepth) {
                            turnsAtThisDepth++;
                            totalCommandsAvailableAtDepth += metric.totalCommands;

                            if (metric.depthAchieved > depth || completedAtLowerDepth) {
                                // If achieved higher depth OR completed all commands at lower
                                // depth, we evaluated ALL commands at this depth
                                totalCommandsAtDepth += metric.totalCommands;
                            } else if (metric.depthAchieved == depth) {
                                // If stopped at this depth, we evaluated commandsEvaluated commands
                                if (metric.completionReason ==
                                    EvaluationCompletionReason::RAN_OUT_OF_COMMANDS) {
                                    // If ran out of commands, we evaluated all of them
                                    totalCommandsAtDepth += metric.totalCommands;
                                } else {
                                    // Otherwise we evaluated the reported number
                                    totalCommandsAtDepth += metric.commandsEvaluated;
                                }
                            }
                        }
                        // If didn't reach this depth, contributes 0 commands (implicit)
                    }

                    double evalRate =
                            totalCommandsAvailableAtDepth > 0
                                    ? (100.0 * totalCommandsAtDepth / totalCommandsAvailableAtDepth)
                                    : 0.0;

                    std::cout << "  Depth " << depth << ": " << totalCommandsAtDepth << "/"
                              << totalCommandsAvailableAtDepth << " commands (" << std::fixed
                              << std::setprecision(1) << evalRate << "%, " << turnsAtThisDepth
                              << "/" << metrics.size() << " turns reached)\n";
                }
            }

            std::cout << "\nTurn-by-turn details:\n";
            for (const auto& metric : metrics) {
                std::string depthStr = std::to_string(metric.depthAchieved);
                if (metric.completionReason == EvaluationCompletionReason::RAN_OUT_OF_COMMANDS) {
                    depthStr += "*";
                }
                std::cout << "Turn " << metric.commandNumber << ": depth " << depthStr
                          << ", evaluated " << metric.commandsEvaluated << "/"
                          << metric.totalCommands << ", chose " << metric.selectedCommandType
                          << " (" << CompletionReasonToString(metric.completionReason) << ")\n";
            }
        }

    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";
        return 1;
    }

    return 0;
}