#include "data_processor.hpp"

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <span>
#include <stdexcept>
#include <thread>
#include <utility>

#include "bscan_processor.hpp"
#include "gpr_processor.hpp"
#include "passthrough_processor.hpp"

namespace radar::processing {
namespace {

constexpr const char* kDefaultProcessorMode = "pass_through";
constexpr std::size_t kBscanReplayWindow = 50U;

[[nodiscard]] auto replay_history_limit(const config::RunConfig& config) -> std::size_t {
    const auto preprocessed_capacity = static_cast<std::size_t>(std::max<std::uint32_t>(1U, config.rings.preprocessed.capacity));
    const auto results_capacity = static_cast<std::size_t>(std::max<std::uint32_t>(1U, config.rings.results.capacity));
    return std::min({preprocessed_capacity, results_capacity, kBscanReplayWindow});
}

void publish_result_collection(const ipc::ResultCollection& collection, ipc::ShmRing& results_ring) {
    const auto out_bytes = ipc::serialize_result_collection(collection);
    if (!results_ring.push(out_bytes)) {
        throw std::runtime_error("Results ring slot is too small for serialized collection");
    }
}

}  // namespace

DataProcessor::DataProcessor(
    const config::RunConfig& config,
    ipc::ShmRing& preprocessed_ring,
    ipc::ShmRing& results_ring,
    ProcessorRegistry processors
)
    : config_(config),
      preprocessed_ring_(preprocessed_ring),
      results_ring_(results_ring),
      processors_(std::move(processors)),
      default_processor_mode_(kDefaultProcessorMode),
      live_config_loader_(config.runtime.processing_live_config_path) {
    if (processors_.empty()) {
        throw std::runtime_error("DataProcessor requires at least one processor");
    }
    if (const auto found = processors_.find(default_processor_mode_); found == processors_.end()) {
        default_processor_mode_ = processors_.begin()->first;
    }
}

void DataProcessor::run(const std::atomic<bool>& stop_requested) {
    std::vector<std::uint8_t> bytes{};
    std::vector<ipc::PreprocessedCollection> preprocessed_history{};
    const std::size_t history_limit = replay_history_limit(config_);
    std::uint64_t last_replayed_revision = live_config_loader_.revision();
    std::uint64_t last_applied_history_command_seq = 0;

    while (!stop_requested.load(std::memory_order_relaxed)) {
        const auto live_config = live_config_loader_.refresh_if_needed();
        const auto live_revision = live_config_loader_.revision();
        auto& processor = resolve_processor(live_config);

        if (live_revision != last_replayed_revision) {
            if (live_config.history_command_seq > last_applied_history_command_seq) {
                if (live_config.history_command == HistoryCommand::RemoveLast) {
                    if (!preprocessed_history.empty()) {
                        preprocessed_history.pop_back();
                    }
                } else if (live_config.history_command == HistoryCommand::ClearAll) {
                    preprocessed_history.clear();
                }
                last_applied_history_command_seq = live_config.history_command_seq;
            }

            if (!live_config.reprocess_current_result) {
                // Socket-fed speed updates should affect only future preprocessed collections,
                // not replay the current history entry.
            } else if (should_replay_entire_history(live_config)) {
                for (std::size_t index = 0; index < preprocessed_history.size(); ++index) {
                    const auto replay_result = process_collection(
                        preprocessed_history[index],
                        std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), index),
                        processor,
                        live_config
                    );
                    publish_result_collection(replay_result, results_ring_);
                }
            } else if (!preprocessed_history.empty()) {
                const auto replay_result = process_collection(
                    preprocessed_history.back(),
                    std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), preprocessed_history.size() - 1U),
                    processor,
                    live_config
                );
                publish_result_collection(replay_result, results_ring_);
            }
            last_replayed_revision = live_revision;
        }

        if (preprocessed_ring_.pop(bytes)) {
            auto preprocessed = ipc::deserialize_preprocessed_collection(bytes);
            preprocessed_history.push_back(std::move(preprocessed));
            while (preprocessed_history.size() > history_limit) {
                preprocessed_history.erase(preprocessed_history.begin());
            }

            const auto result_collection = process_collection(
                preprocessed_history.back(),
                std::span<const ipc::PreprocessedCollection>(preprocessed_history.data(), preprocessed_history.size() - 1U),
                processor,
                live_config
            );
            publish_result_collection(result_collection, results_ring_);
            continue;
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(config_.runtime.idle_sleep_ms));
    }
}

auto DataProcessor::process_collection(
    const ipc::PreprocessedCollection& preprocessed,
    std::span<const ipc::PreprocessedCollection> previous_collections,
    ProcessorInterface& processor,
    const ProcessingLiveConfig& live_config
) -> ipc::ResultCollection {
    return processor.process_collection(config_, preprocessed, previous_collections, live_config);
}

auto DataProcessor::resolve_processor(const ProcessingLiveConfig& live_config) -> ProcessorInterface& {
    const std::string requested_mode =
        live_config.processor_mode.empty() ? default_processor_mode_ : live_config.processor_mode;

    if (auto found = processors_.find(requested_mode); found != processors_.end()) {
        return *(found->second);
    }
    if (auto fallback = processors_.find(default_processor_mode_); fallback != processors_.end()) {
        return *(fallback->second);
    }
    return *(processors_.begin()->second);
}

auto DataProcessor::should_replay_entire_history(const ProcessingLiveConfig& live_config) const -> bool {
    const std::string requested_mode =
        live_config.processor_mode.empty() ? default_processor_mode_ : live_config.processor_mode;
    return requested_mode == "bscan";
}

auto create_default_processors() -> ProcessorRegistry {
    ProcessorRegistry processors{};
    {
        auto processor = std::make_unique<PassThroughProcessor>();
        processors.emplace(processor->name(), std::move(processor));
    }
    {
        auto processor = std::make_unique<BScanProcessor>();
        processors.emplace(processor->name(), std::move(processor));
    }
    {
        auto processor = std::make_unique<GprProcessor>();
        processors.emplace(processor->name(), std::move(processor));
    }
    {
        auto processor = std::make_unique<LegacyGprProcessor>();
        processors.emplace(processor->name(), std::move(processor));
    }
    return processors;
}

}  // namespace radar::processing
