//
// AI Battle Simulator - CLI Entry Point
//

#include <fcntl.h>
#include <unistd.h>

#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

#include "AiBattleConfig.hpp"
#include "AiBattleSimulator.hpp"
#include "src/main/cpp/net/eagle0/common/FilesystemUtils.hpp"

using shardok::GameStateW;
using shardok::ai_battle_simulator::AiBattleConfigLoader;
using shardok::ai_battle_simulator::AiBattleSimulator;
using shardok::ai_battle_simulator::BattleResult;
using shardok::ai_battle_simulator::BattleTraceOptions;

namespace {

enum class SummaryFormat {
    HUMAN,
    CSV,
    JSON,
};

class NullBuffer : public std::streambuf {
protected:
    int overflow(int c) override { return c; }
};

void PrintUsage(const char* programName) {
    std::cout << "AI Battle Simulator - AI vs AI Battle Testing Tool\n"
              << "\n"
              << "Usage:\n"
              << "  " << programName << " --config=<path>    Run battle from JSON config file\n"
              << "  " << programName << " --config=<path> --config=<path> --summary=csv --quiet\n"
              << "  " << programName
              << " --config=<path> --state-file=<path> --summary=csv --quiet\n"
              << "  " << programName
              << " --config=<path> --trace-jsonl=<path> --run-label=baseline\n"
              << "  " << programName << " --generate-config  Generate sample config to stdout\n"
              << "  " << programName
              << " --generate-config --output=<path>  Generate sample config to file\n"
              << "  " << programName << " --help             Show this help message\n"
              << "\n"
              << "Options:\n"
              << "  --summary=human|csv|json  Output format for completed battle summaries\n"
              << "  --quiet                   Suppress simulator progress logs\n"
              << "  --trace-jsonl=<path>      Write one JSON object per AI-selected command\n"
              << "  --run-label=<label>       Label included in trace rows\n"
              << "\n"
              << "Examples:\n"
              << "  # Generate sample config\n"
              << "  " << programName << " --generate-config > my_battle.json\n"
              << "\n"
              << "  # Run battle from config\n"
              << "  " << programName << " --config=my_battle.json\n"
              << "\n"
              << "  # Run a compact benchmark suite\n"
              << "  " << programName
              << " --config=baseline.json --config=candidate.json --summary=csv --quiet\n"
              << "\n"
              << "  # Run an AI config from a saved Shardok GameState flatbuffer\n"
              << "  " << programName
              << " --config=ai_config.json --state-file=state.fb --summary=csv --quiet\n"
              << "\n";
}

void GenerateConfigFile(const std::string& outputPath) {
    auto config = AiBattleConfigLoader::CreateDefaultPerfConfig();
    std::string jsonString = AiBattleConfigLoader::ToJsonString(*config);

    if (outputPath.empty() || outputPath == "-") {
        // Output to stdout
        std::cout << jsonString << "\n";
    } else {
        // Output to file
        std::ofstream outFile(outputPath);
        if (!outFile.is_open()) {
            throw std::runtime_error("Failed to open output file: " + outputPath);
        }
        outFile << jsonString << "\n";
        std::cout << "Sample config written to: " << outputPath << "\n";
    }
}

std::string CsvEscape(const std::string& value) {
    bool mustQuote = false;
    std::string escaped;
    escaped.reserve(value.size());
    for (const char c : value) {
        if (c == '"' || c == ',' || c == '\n' || c == '\r') { mustQuote = true; }
        if (c == '"') { escaped.push_back('"'); }
        escaped.push_back(c);
    }
    if (!mustQuote) { return escaped; }
    return "\"" + escaped + "\"";
}

std::string JsonEscape(const std::string& value) {
    std::ostringstream out;
    for (const char c : value) {
        switch (c) {
            case '"': out << "\\\""; break;
            case '\\': out << "\\\\"; break;
            case '\n': out << "\\n"; break;
            case '\r': out << "\\r"; break;
            case '\t': out << "\\t"; break;
            default: out << c; break;
        }
    }
    return out.str();
}

std::string WinnerLabel(int winner) {
    if (winner == 0) { return "attacker"; }
    if (winner == 1) { return "defender"; }
    return "draw";
}

std::string EndReasonLabel(BattleResult::EndReason endReason) {
    switch (endReason) {
        case BattleResult::EndReason::LAST_PLAYER_STANDING: return "last_player_standing";
        case BattleResult::EndReason::CRITICAL_TILES_CONTROLLED: return "critical_tiles_controlled";
        case BattleResult::EndReason::MAX_ROUNDS_REACHED: return "max_rounds_reached";
        case BattleResult::EndReason::DRAW: return "draw";
    }
}

void PrintHumanSummary(const BattleResult& result) {
    std::cout << "\n";
    std::cout << "=================================\n";
    std::cout << "Battle Complete\n";
    std::cout << "=================================\n";
    std::cout << "Winner: ";
    if (result.winner == 0) {
        std::cout << "Attacker (Player 0)\n";
    } else if (result.winner == 1) {
        std::cout << "Defender (Player 1)\n";
    } else {
        std::cout << "Draw\n";
    }
    std::cout << "Result: " << result.description << "\n";
    std::cout << "Total Rounds: " << result.totalRounds << "\n";
    std::cout << "Total Commands: " << result.totalCommands << "\n";
    std::cout << "Attacker AI Commands: " << result.attackerCommands << "\n";
    std::cout << "Defender AI Commands: " << result.defenderCommands << "\n";
    std::cout << "Attacker Avg Depth: " << result.attackerAverageDepth << "\n";
    std::cout << "Defender Avg Depth: " << result.defenderAverageDepth << "\n";
    std::cout << "Attacker Survivors: " << result.attackerSurvivingUnits << " units, "
              << result.attackerSurvivingTroops << " troops\n";
    std::cout << "Defender Survivors: " << result.defenderSurvivingUnits << " units, "
              << result.defenderSurvivingTroops << " troops\n";

    if (!result.survivingUnits.empty()) {
        std::cout << "\nWinning/Draw Surviving Units (" << result.survivingUnits.size() << "):\n";
        for (const auto& unit : result.survivingUnits) {
            std::cout << "  Unit " << unit.unitId << ": " << unit.battalionTypeName << " ("
                      << static_cast<int>(unit.battalionSize) << " troops)";
            if (unit.profession > 0) { std::cout << " - Profession " << unit.profession; }
            std::cout << "\n";
        }
    }

    std::cout << "=================================\n";
}

void PrintCsvHeader() {
    std::cout << "config,state_file,winner,end_reason,result,total_rounds,total_commands,"
              << "attacker_ai_commands,defender_ai_commands,attacker_avg_depth,"
              << "defender_avg_depth,attacker_surviving_units,defender_surviving_units,"
              << "attacker_surviving_troops,defender_surviving_troops\n";
}

void PrintCsvRow(
        const std::string& configPath,
        const std::string& statePath,
        const BattleResult& result) {
    std::cout << CsvEscape(configPath) << "," << CsvEscape(statePath) << ","
              << WinnerLabel(result.winner) << "," << EndReasonLabel(result.endReason) << ","
              << CsvEscape(result.description) << "," << result.totalRounds << ","
              << result.totalCommands << "," << result.attackerCommands << ","
              << result.defenderCommands << "," << result.attackerAverageDepth << ","
              << result.defenderAverageDepth << "," << result.attackerSurvivingUnits << ","
              << result.defenderSurvivingUnits << "," << result.attackerSurvivingTroops << ","
              << result.defenderSurvivingTroops << "\n";
}

void PrintJsonSummary(
        const std::string& configPath,
        const std::string& statePath,
        const BattleResult& result) {
    std::cout << "{"
              << "\"config\":\"" << JsonEscape(configPath) << "\","
              << "\"state_file\":\"" << JsonEscape(statePath) << "\","
              << "\"winner\":\"" << WinnerLabel(result.winner) << "\","
              << "\"end_reason\":\"" << EndReasonLabel(result.endReason) << "\","
              << "\"result\":\"" << JsonEscape(result.description) << "\","
              << "\"total_rounds\":" << result.totalRounds << ","
              << "\"total_commands\":" << result.totalCommands << ","
              << "\"attacker_ai_commands\":" << result.attackerCommands << ","
              << "\"defender_ai_commands\":" << result.defenderCommands << ","
              << "\"attacker_avg_depth\":" << result.attackerAverageDepth << ","
              << "\"defender_avg_depth\":" << result.defenderAverageDepth << ","
              << "\"attacker_surviving_units\":" << result.attackerSurvivingUnits << ","
              << "\"defender_surviving_units\":" << result.defenderSurvivingUnits << ","
              << "\"attacker_surviving_troops\":" << result.attackerSurvivingTroops << ","
              << "\"defender_surviving_troops\":" << result.defenderSurvivingTroops << "}\n";
}

std::unique_ptr<shardok::ai_battle_simulator::BattleConfigProto> LoadConfig(
        const std::string& configPath) {
    if (configPath.empty()) { return AiBattleConfigLoader::CreateDefaultPerfConfig(); }
    return AiBattleConfigLoader::LoadFromJsonFile(configPath);
}

GameStateW LoadGameStateFromFile(const std::string& statePath) {
    if (statePath.empty()) { return GameStateW{}; }
    auto bytes = FilesystemUtils::LoadFromPath(statePath);
    if (bytes.empty()) { throw std::runtime_error("Failed to load state file: " + statePath); }
    return GameStateW::FromByteVector(bytes);
}

BattleResult RunBattle(
        const std::string& configPath,
        const std::string& statePath,
        bool quiet,
        const std::string& tracePath,
        const std::string& runLabel) {
    std::ofstream traceFile;
    BattleTraceOptions traceOptions;
    if (!tracePath.empty()) {
        traceFile.open(tracePath);
        if (!traceFile.is_open()) {
            throw std::runtime_error("Failed to open trace file: " + tracePath);
        }
        traceOptions.output = &traceFile;
        traceOptions.runLabel = runLabel;
        traceOptions.configPath = configPath;
        traceOptions.statePath = statePath;
    }

    NullBuffer nullBuffer;
    std::ostream nullStream(&nullBuffer);
    auto* originalCout = std::cout.rdbuf();
    auto* originalCerr = std::cerr.rdbuf();
    int stdoutCopy = -1;
    int stderrCopy = -1;
    if (quiet) {
        std::cout.rdbuf(nullStream.rdbuf());
        std::cerr.rdbuf(nullStream.rdbuf());
        std::fflush(stdout);
        std::fflush(stderr);
        stdoutCopy = dup(STDOUT_FILENO);
        stderrCopy = dup(STDERR_FILENO);
        const int nullFd = open("/dev/null", O_WRONLY);
        if (nullFd >= 0) {
            dup2(nullFd, STDOUT_FILENO);
            dup2(nullFd, STDERR_FILENO);
            close(nullFd);
        }
    }

    auto restoreOutput = [&]() {
        if (!quiet) { return; }
        std::fflush(stdout);
        std::fflush(stderr);
        if (stdoutCopy >= 0) {
            dup2(stdoutCopy, STDOUT_FILENO);
            close(stdoutCopy);
        }
        if (stderrCopy >= 0) {
            dup2(stderrCopy, STDERR_FILENO);
            close(stderrCopy);
        }
        std::cout.rdbuf(originalCout);
        std::cerr.rdbuf(originalCerr);
    };

    // Load config
    if (!configPath.empty()) { std::cout << "Loading config from: " << configPath << "\n"; }
    auto config = LoadConfig(configPath);

    if (!config) {
        restoreOutput();
        throw std::runtime_error("Failed to load config file");
    }

    // Create simulator
    std::optional<GameStateW> initialGameState;
    if (!statePath.empty()) {
        std::cout << "Loading saved state from: " << statePath << "\n";
        initialGameState = LoadGameStateFromFile(statePath);
    }
    AiBattleSimulator simulator(*config, nullptr, std::move(initialGameState), traceOptions);

    // Run battle
    auto result = simulator.RunBattle();

    restoreOutput();
    return result;
}

}  // namespace

int main(int argc, char* argv[]) {
    // Set exec path for FilesystemUtils
    FilesystemUtils::SetExecPath(argv[0]);

    try {
        if (argc < 2) {
            PrintUsage(argv[0]);
            return 1;
        }

        std::vector<std::string> configPaths;
        std::vector<std::string> statePaths;
        configPaths.reserve(argc - 1);
        statePaths.reserve(argc - 1);
        std::string outputPath;
        std::string tracePath;
        std::string runLabel;
        bool generateConfig = false;
        bool quiet = false;
        SummaryFormat summaryFormat = SummaryFormat::HUMAN;

        // Parse command line arguments
        for (int i = 1; i < argc; ++i) {
            std::string arg(argv[i]);

            if (arg == "--help" || arg == "-h") {
                PrintUsage(argv[0]);
                return 0;
            } else if (arg == "--generate-config") {
                generateConfig = true;
            } else if (arg.starts_with("--config=")) {
                configPaths.push_back(arg.substr(9));
            } else if (arg.starts_with("--state-file=")) {
                statePaths.push_back(arg.substr(13));
            } else if (arg.starts_with("--output=")) {
                outputPath = arg.substr(9);
            } else if (arg.starts_with("--trace-jsonl=")) {
                tracePath = arg.substr(14);
            } else if (arg.starts_with("--run-label=")) {
                runLabel = arg.substr(12);
            } else if (arg == "--quiet") {
                quiet = true;
            } else if (arg.starts_with("--summary=")) {
                const auto format = arg.substr(10);
                if (format == "human") {
                    summaryFormat = SummaryFormat::HUMAN;
                } else if (format == "csv") {
                    summaryFormat = SummaryFormat::CSV;
                } else if (format == "json") {
                    summaryFormat = SummaryFormat::JSON;
                } else {
                    std::cerr << "Error: Unknown summary format: " << format << "\n";
                    PrintUsage(argv[0]);
                    return 1;
                }
            } else {
                std::cerr << "Error: Unknown argument: " << arg << "\n";
                PrintUsage(argv[0]);
                return 1;
            }
        }

        // Execute appropriate action
        if (generateConfig) {
            GenerateConfigFile(outputPath);
        } else if (!configPaths.empty() || !statePaths.empty()) {
            if (summaryFormat == SummaryFormat::CSV) { PrintCsvHeader(); }
            if (configPaths.empty()) { configPaths.emplace_back(""); }
            if (statePaths.empty()) { statePaths.emplace_back(""); }
            for (const auto& configPath : configPaths) {
                for (const auto& statePath : statePaths) {
                    const auto result =
                            RunBattle(configPath, statePath, quiet, tracePath, runLabel);
                    switch (summaryFormat) {
                        case SummaryFormat::HUMAN: PrintHumanSummary(result); break;
                        case SummaryFormat::CSV: PrintCsvRow(configPath, statePath, result); break;
                        case SummaryFormat::JSON:
                            PrintJsonSummary(configPath, statePath, result);
                            break;
                    }
                }
            }
        } else {
            std::cerr << "Error: --generate-config, --config, or --state-file must be specified\n";
            PrintUsage(argv[0]);
            return 1;
        }

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

    return 0;
}
