/**
 * Scenario 13: Cross-System Integration (IO + DataNode)
 *
 * Tests integration between IntraIO pub/sub system and IDataTree/IDataNode system.
 * Validates that modules can communicate via IO while sharing data via DataNode.
 */

#include "grove/JsonDataNode.h"
#include "grove/JsonDataTree.h"
#include "grove/IOFactory.h"
#include "../helpers/TestMetrics.h"
#include "../helpers/TestAssertions.h"
#include "../helpers/TestReporter.h"

#include <iostream>
#include <fstream>
#include <filesystem>
#include <thread>
#include <chrono>
#include <atomic>

using namespace grove;

int main() {
    TestReporter reporter("Cross-System Integration Test");
    TestMetrics metrics;

    std::cout << "================================================================================\n";
    std::cout << "TEST: Cross-System Integration (IO + DataNode)\n";
    std::cout << "================================================================================\n\n";

    // === SETUP ===
    std::cout << "Setup: Creating test directories...\n";
    std::filesystem::create_directories("test_cross/config");
    std::filesystem::create_directories("test_cross/data");
    std::cout << "  ✓ Directories created\n";

    std::cout << "  Creating JsonDataTree...\n";
    auto tree = std::make_unique<JsonDataTree>("test_cross");
    std::cout << "  ✓ JsonDataTree created\n";

    // Create IO instances
    std::cout << "  Creating ConfigWatcherIO...\n";
    auto configWatcherIO = IOFactory::create("intra", "ConfigWatcher");
    std::cout << "  ✓ ConfigWatcherIO created\n";

    std::cout << "  Creating PlayerIO...\n";
    auto playerIO = IOFactory::create("intra", "Player");
    std::cout << "  ✓ PlayerIO created\n";

    std::cout << "  Creating EconomyIO...\n";
    auto economyIO = IOFactory::create("intra", "Economy");
    std::cout << "  ✓ EconomyIO created\n";

    std::cout << "  Creating MetricsIO...\n";
    auto metricsIO = IOFactory::create("intra", "Metrics");
    std::cout << "  ✓ MetricsIO created\n";

    if (!configWatcherIO || !playerIO || !economyIO || !metricsIO) {
        std::cerr << "❌ Failed to create IO instances\n";
        return 1;
    }

    // ========================================================================
    // TEST 1: Config Hot-Reload → IO Broadcast
    // ========================================================================
    std::cout << "\n=== TEST 1: Config Hot-Reload → IO Broadcast ===\n";

    // Create initial config file
    nlohmann::json gameplayConfig = {
        {"difficulty", "normal"},
        {"hpMultiplier", 1.0}
    };

    std::ofstream configFile("test_cross/config/gameplay.json");
    configFile << gameplayConfig.dump(2);
    configFile.close();

    // Load config
    tree->loadConfigFile("gameplay.json");

    // Player subscribes to config changes
    playerIO->subscribe("config:gameplay:changed");

    // Setup reload callback for ConfigWatcher
    std::atomic<int> configChangedEvents{0};
    tree->onTreeReloaded([&]() {
        std::cout << "  → Config reloaded, publishing event...\n";
        auto data = std::make_unique<JsonDataNode>("configChange", nlohmann::json{
            {"config", "gameplay"},
            {"timestamp", 12345}
        });
        configWatcherIO->publish("config:gameplay:changed", std::move(data));
    });

    // Modify config file
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    gameplayConfig["difficulty"] = "hard";
    gameplayConfig["hpMultiplier"] = 1.5;

    std::ofstream configFile2("test_cross/config/gameplay.json");
    configFile2 << gameplayConfig.dump(2);
    configFile2.close();

    auto reloadStart = std::chrono::high_resolution_clock::now();

    // Trigger reload
    if (tree->reloadIfChanged()) {
        std::cout << "  Config was reloaded\n";
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(10));

    // Check if player received message
    if (playerIO->hasMessages() > 0) {
        auto msg = playerIO->pullMessage();
        configChangedEvents++;

        // Read new config from tree
        auto configRoot = tree->getConfigRoot();
        auto gameplay = configRoot->getChild("gameplay");
        if (gameplay) {
            std::string difficulty = gameplay->getString("difficulty");
            double hpMult = gameplay->getDouble("hpMultiplier");

            std::cout << "  PlayerModule received config change: difficulty=" << difficulty
                      << ", hpMult=" << hpMult << "\n";

            ASSERT_EQ(difficulty, "hard", "Difficulty should be updated");
            ASSERT_TRUE(std::abs(hpMult - 1.5) < 0.001, "HP multiplier should be updated");
        }
    }

    auto reloadEnd = std::chrono::high_resolution_clock::now();
    float reloadLatency = std::chrono::duration<float, std::milli>(reloadEnd - reloadStart).count();

    std::cout << "Total latency (reload + publish + subscribe + read): " << reloadLatency << "ms\n";
    ASSERT_LT(reloadLatency, 200.0f, "Total latency should be reasonable");
    ASSERT_EQ(configChangedEvents.load(), 1, "Should receive exactly 1 config change event");

    reporter.addMetric("config_reload_latency_ms", reloadLatency);
    reporter.addAssertion("config_hotreload_chain", true);
    std::cout << "✓ TEST 1 PASSED\n";

    // ========================================================================
    // TEST 2: State Persistence + Event Publishing
    // ========================================================================
    std::cout << "\n=== TEST 2: State Persistence + Event Publishing ===\n";

    auto dataRoot = tree->getDataRoot();

    // Create player node
    auto player = std::make_unique<JsonDataNode>("player", nlohmann::json::object());
    auto profile = std::make_unique<JsonDataNode>("profile", nlohmann::json{
        {"name", "TestPlayer"},
        {"level", 5},
        {"gold", 1000}
    });

    player->setChild("profile", std::move(profile));
    dataRoot->setChild("player", std::move(player));

    // Save to disk
    bool saved = tree->saveData();
    ASSERT_TRUE(saved, "Should save data successfully");

    std::cout << "  Data saved to disk\n";

    // Economy subscribes to player events FIRST
    economyIO->subscribe("player:*");

    // Then publish level up event
    auto levelUpData = std::make_unique<JsonDataNode>("levelUp", nlohmann::json{
        {"event", "level_up"},
        {"newLevel", 6},
        {"goldBonus", 500}
    });
    playerIO->publish("player:level_up", std::move(levelUpData));

    std::this_thread::sleep_for(std::chrono::milliseconds(10));

    // Economy processes message
    int messagesReceived = 0;
    while (economyIO->hasMessages() > 0) {
        auto msg = economyIO->pullMessage();
        messagesReceived++;
        std::cout << "  EconomyModule received: " << msg.topic << "\n";

        // Read player data from tree using read-only access
        auto dataRoot = tree->getDataRoot();
        if (dataRoot) {
            auto playerData = dataRoot->getChildReadOnly("player");
            if (playerData) {
                auto profileData = playerData->getChildReadOnly("profile");
                if (profileData) {
                    int gold = profileData->getInt("gold");
                    std::cout << "  Player gold: " << gold << "\n";
                    ASSERT_EQ(gold, 1000, "Gold should match saved value");
                }
            }
        }
    }

    ASSERT_EQ(messagesReceived, 1, "Should receive 1 player event");

    reporter.addAssertion("state_persistence_chain", true);
    std::cout << "✓ TEST 2 PASSED\n";

    // ========================================================================
    // TEST 3: Multi-Module State Synchronization
    // ========================================================================
    std::cout << "\n=== TEST 3: Multi-Module State Synchronization ===\n";

    int syncErrors = 0;

    for (int i = 0; i < 10; i++) {
        // Update gold in DataNode using read-only access
        int goldValue = 1000 + i * 10;
        auto dataRoot = tree->getDataRoot();
        if (dataRoot) {
            auto playerNode = dataRoot->getChildReadOnly("player");
            if (playerNode) {
                auto profileNode = playerNode->getChildReadOnly("profile");
                if (profileNode) {
                    profileNode->setInt("gold", goldValue);
                }
            }
        }

        // Publish event with same value
        auto goldUpdate = std::make_unique<JsonDataNode>("goldUpdate", nlohmann::json{
            {"event", "gold_updated"},
            {"gold", goldValue}
        });
        playerIO->publish("player:gold:updated", std::move(goldUpdate));

        std::this_thread::sleep_for(std::chrono::milliseconds(5));

        // Economy verifies synchronization
        if (economyIO->hasMessages() > 0) {
            auto msg = economyIO->pullMessage();
            int msgGold = msg.data->getInt("gold");

            // Read from DataNode using read-only access
            auto dataRoot = tree->getDataRoot();
            if (dataRoot) {
                auto playerCheck = dataRoot->getChildReadOnly("player");
                if (playerCheck) {
                    auto profileCheck = playerCheck->getChildReadOnly("profile");
                    if (profileCheck) {
                        int dataGold = profileCheck->getInt("gold");

                        if (msgGold != dataGold) {
                            std::cerr << "  SYNC ERROR: msg=" << msgGold << " data=" << dataGold << "\n";
                            syncErrors++;
                        }
                    }
                }
            }
        }
    }

    std::cout << "Synchronization errors: " << syncErrors << " / 10\n";
    ASSERT_EQ(syncErrors, 0, "Should have zero synchronization errors");

    reporter.addMetric("sync_errors", syncErrors);
    reporter.addAssertion("state_synchronization", syncErrors == 0);
    std::cout << "✓ TEST 3 PASSED\n";

    // ========================================================================
    // TEST 4: Runtime Metrics Collection
    // ========================================================================
    std::cout << "\n=== TEST 4: Runtime Metrics Collection ===\n";

    auto runtimeRoot = tree->getRuntimeRoot();

    // Subscribe to metrics with low-frequency
    SubscriptionConfig metricsConfig;
    metricsConfig.replaceable = true;
    metricsConfig.batchInterval = 1000; // 1 second

    playerIO->subscribeLowFreq("metrics:*", metricsConfig);

    // Publish 20 metrics over 2 seconds
    for (int i = 0; i < 20; i++) {
        auto metricsData = std::make_unique<JsonDataNode>("metrics", nlohmann::json{
            {"fps", 60.0},
            {"memory", 125000000 + i * 1000},
            {"messageCount", i}
        });
        metricsIO->publish("metrics:snapshot", std::move(metricsData));

        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    // Check batched messages
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    int snapshotsReceived = 0;
    while (playerIO->hasMessages() > 0) {
        playerIO->pullMessage();
        snapshotsReceived++;
    }

    std::cout << "Snapshots received: " << snapshotsReceived << " (expected ~2 due to batching)\n";
    ASSERT_TRUE(snapshotsReceived >= 1 && snapshotsReceived <= 4,
                "Should receive batched snapshots");

    // Verify runtime not persisted
    ASSERT_FALSE(std::filesystem::exists("test_cross/runtime"),
                 "Runtime data should not be persisted");

    reporter.addMetric("batched_snapshots", snapshotsReceived);
    reporter.addAssertion("runtime_metrics", true);
    std::cout << "✓ TEST 4 PASSED\n";

    // ========================================================================
    // TEST 5: Concurrent Access (IO + DataNode with new read-only API)
    // ========================================================================
    std::cout << "\n=== TEST 5: Concurrent Access (IO + DataNode) ===\n";

    // Recreate player data for TEST 5 (ensure fresh data exists for concurrent reads)
    auto player5 = std::make_unique<JsonDataNode>("player", nlohmann::json::object());
    auto profile5 = std::make_unique<JsonDataNode>("profile", nlohmann::json{
        {"name", "TestPlayer"},
        {"level", 6},
        {"gold", 1090}  // Final gold value from TEST 3
    });
    player5->setChild("profile", std::move(profile5));

    // Use getDataRootReadOnly() to get the actual root (not a copy) and add the child
    auto dataRootPtr = tree->getDataRootReadOnly();
    if (dataRootPtr) {
        dataRootPtr->setChild("player", std::move(player5));
        std::cout << "  Player data recreated for concurrent test\n";
    } else {
        std::cerr << "  ERROR: Could not get data root!\n";
    }

    std::atomic<bool> running{true};
    std::atomic<int> publishCount{0};
    std::atomic<int> readCount{0};
    std::atomic<int> errors{0};

    // Thread 1: Publish events
    std::thread pubThread([&]() {
        while (running) {
            try {
                auto data = std::make_unique<JsonDataNode>("data", nlohmann::json{{"id", publishCount++}});
                playerIO->publish("concurrent:test", std::move(data));
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            } catch (...) {
                errors++;
            }
        }
    });

    // Thread 2: Read DataNode concurrently using new read-only API
    std::thread readThread([&]() {
        while (running) {
            try {
                // Use getDataRootReadOnly() instead of getDataRoot() to avoid copying
                auto dataRoot = tree->getDataRootReadOnly();
                if (dataRoot) {
                    // Use getChildReadOnly() instead of getChild() to avoid copying/ownership transfer
                    auto playerData = dataRoot->getChildReadOnly("player");
                    if (playerData) {
                        auto profileData = playerData->getChildReadOnly("profile");
                        if (profileData) {
                            int gold = profileData->getInt("gold", 0);
                            readCount++;
                        }
                    }
                }
                std::this_thread::sleep_for(std::chrono::milliseconds(20));
            } catch (...) {
                errors++;
            }
        }
    });

    // Run for 2 seconds
    std::this_thread::sleep_for(std::chrono::seconds(2));
    running = false;

    pubThread.join();
    readThread.join();

    std::cout << "Concurrent test completed:\n";
    std::cout << "  Publishes: " << publishCount << "\n";
    std::cout << "  Reads: " << readCount << "\n";
    std::cout << "  Errors: " << errors << "\n";

    ASSERT_EQ(errors.load(), 0, "Should have zero exceptions during concurrent access");
    ASSERT_GT(publishCount.load(), 0, "Should have published messages");
    ASSERT_GT(readCount.load(), 0, "Should have successfully read DataNode data");

    reporter.addMetric("concurrent_publishes", publishCount);
    reporter.addMetric("concurrent_reads", readCount);
    reporter.addMetric("concurrent_errors", errors);
    reporter.addAssertion("concurrent_access", errors == 0);
    std::cout << "✓ TEST 5 PASSED\n";

    // ========================================================================
    // CLEANUP
    // ========================================================================
    std::filesystem::remove_all("test_cross");

    // ========================================================================
    // RAPPORT FINAL
    // ========================================================================

    metrics.printReport();
    reporter.printFinalReport();

    return reporter.getExitCode();
}
