#include <hgraph/runtime/evaluation_engine.h>
#include <hgraph/types/node.h>
#include <hgraph/types/graph.h>

namespace hgraph {
    void BaseEvaluationClock::set_evaluation_time(engine_time_t value) {
        _evaluation_time = value;
        _next_scheduled_evaluation_time = MAX_DT;
    }

    EngineEvaluationClockDelegate::EngineEvaluationClockDelegate(EngineEvaluationClock::ptr clock)
        : _engine_evalaution_clock{std::move(clock)} {
    }

    engine_time_t EngineEvaluationClockDelegate::evaluation_time() const {
        return _engine_evalaution_clock->evaluation_time();
    }

    engine_time_t EngineEvaluationClockDelegate::now() const { return _engine_evalaution_clock->now(); }

    engine_time_t EngineEvaluationClockDelegate::next_cycle_evaluation_time() const {
        return _engine_evalaution_clock->next_cycle_evaluation_time();
    }

    engine_time_delta_t EngineEvaluationClockDelegate::cycle_time() const {
        return _engine_evalaution_clock->cycle_time();
    }

    void EngineEvaluationClockDelegate::set_evaluation_time(engine_time_t et) {
        _engine_evalaution_clock->set_evaluation_time(et);
    }

    engine_time_t EngineEvaluationClockDelegate::next_scheduled_evaluation_time() const {
        return _engine_evalaution_clock->next_scheduled_evaluation_time();
    }

    void EngineEvaluationClockDelegate::update_next_scheduled_evaluation_time(engine_time_t et) {
        _engine_evalaution_clock->update_next_scheduled_evaluation_time(et);
    }

    void EngineEvaluationClockDelegate::advance_to_next_scheduled_time() {
        _engine_evalaution_clock->advance_to_next_scheduled_time();
    }

    void EngineEvaluationClockDelegate::mark_push_node_requires_scheduling() {
        _engine_evalaution_clock->mark_push_node_requires_scheduling();
    }

    bool EngineEvaluationClockDelegate::push_node_requires_scheduling() const {
        return _engine_evalaution_clock->push_node_requires_scheduling();
    }

    void EngineEvaluationClockDelegate::reset_push_node_requires_scheduling() {
        _engine_evalaution_clock->reset_push_node_requires_scheduling();
    }

    const engine_time_t* EngineEvaluationClockDelegate::evaluation_time_ptr() const {
        return _engine_evalaution_clock->evaluation_time_ptr();
    }

    NotifyGraphEvaluation::NotifyGraphEvaluation(EvaluationEngine::ptr evaluation_engine, graph_ptr graph)
        : _evaluation_engine{std::move(evaluation_engine)}, _graph{graph} {
        _evaluation_engine->notify_before_graph_evaluation(_graph);
    }

    NotifyGraphEvaluation::~NotifyGraphEvaluation() noexcept {
        try {
            _evaluation_engine->notify_after_graph_evaluation(_graph);
        } catch (const std::exception &e) {
            fprintf(stderr, "Warning: exception during notify_after_graph_evaluation: %s\n", e.what());
        } catch (...) {
            fprintf(stderr, "Warning: unknown exception during notify_after_graph_evaluation\n");
        }
    }

    NotifyNodeEvaluation::NotifyNodeEvaluation(EvaluationEngine::ptr evaluation_engine, node_ptr node)
        : _evaluation_engine{std::move(evaluation_engine)}, _node{node} {
        _evaluation_engine->notify_before_node_evaluation(_node);
    }

    NotifyNodeEvaluation::~NotifyNodeEvaluation() noexcept {
        try {
            _evaluation_engine->notify_after_node_evaluation(_node);
        } catch (const std::exception &e) {
            fprintf(stderr, "Warning: exception during notify_after_node_evaluation: %s\n", e.what());
        } catch (...) {
            fprintf(stderr, "Warning: unknown exception during notify_after_node_evaluation\n");
        }
    }

    EvaluationEngineDelegate::EvaluationEngineDelegate(s_ptr api) : _evaluation_engine{std::move(api)} {
    }

    EvaluationMode EvaluationEngineDelegate::evaluation_mode() const { return _evaluation_engine->evaluation_mode(); }

    engine_time_t EvaluationEngineDelegate::start_time() const { return _evaluation_engine->start_time(); }

    engine_time_t EvaluationEngineDelegate::end_time() const { return _evaluation_engine->end_time(); }

    EvaluationClock::s_ptr EvaluationEngineDelegate::evaluation_clock() { return _evaluation_engine->evaluation_clock(); }

    const EngineEvaluationClock::s_ptr& EvaluationEngineDelegate::engine_evaluation_clock() {
        return _evaluation_engine->engine_evaluation_clock();
    }

    void EvaluationEngineDelegate::request_engine_stop() { _evaluation_engine->request_engine_stop(); }

    bool EvaluationEngineDelegate::is_stop_requested() { return _evaluation_engine->is_stop_requested(); }

    void EvaluationEngineDelegate::add_before_evaluation_notification(std::function<void()> &&fn) {
        _evaluation_engine->add_before_evaluation_notification(std::forward<std::function<void()> >(fn));
    }

    void EvaluationEngineDelegate::add_after_evaluation_notification(std::function<void()> &&fn) {
        _evaluation_engine->add_after_evaluation_notification(std::forward<std::function<void()> >(fn));
    }

    void EvaluationEngineDelegate::add_life_cycle_observer(EvaluationLifeCycleObserver::s_ptr observer) {
        _evaluation_engine->add_life_cycle_observer(std::move(observer));
    }

    void EvaluationEngineDelegate::remove_life_cycle_observer(const EvaluationLifeCycleObserver::s_ptr& observer) {
        _evaluation_engine->remove_life_cycle_observer(observer);
    }

    void EvaluationEngineDelegate::advance_engine_time() { _evaluation_engine->advance_engine_time(); }

    void EvaluationEngineDelegate::notify_before_evaluation() { _evaluation_engine->notify_before_evaluation(); }

    void EvaluationEngineDelegate::notify_after_evaluation() { _evaluation_engine->notify_after_evaluation(); }

    void EvaluationEngineDelegate::notify_before_start_graph(graph_ptr graph) {
        _evaluation_engine->notify_before_start_graph(graph);
    }

    void EvaluationEngineDelegate::notify_after_start_graph(graph_ptr graph) {
        _evaluation_engine->notify_after_start_graph(graph);
    }

    void EvaluationEngineDelegate::notify_before_start_node(node_ptr node) {
        _evaluation_engine->notify_before_start_node(node);
    }

    void EvaluationEngineDelegate::notify_after_start_node(node_ptr node) {
        _evaluation_engine->notify_after_start_node(node);
    }

    void EvaluationEngineDelegate::notify_before_graph_evaluation(graph_ptr graph) {
        _evaluation_engine->notify_before_graph_evaluation(graph);
    }

    void EvaluationEngineDelegate::notify_after_graph_evaluation(graph_ptr graph) {
        _evaluation_engine->notify_after_graph_evaluation(graph);
    }

    void EvaluationEngineDelegate::notify_after_push_nodes_evaluation(graph_ptr graph) {
        _evaluation_engine->notify_after_push_nodes_evaluation(graph);
    }

    void EvaluationEngineDelegate::notify_before_node_evaluation(node_ptr node) {
        _evaluation_engine->notify_before_node_evaluation(node);
    }

    void EvaluationEngineDelegate::notify_after_node_evaluation(node_ptr node) {
        _evaluation_engine->notify_after_node_evaluation(node);
    }

    void EvaluationEngineDelegate::notify_before_stop_node(node_ptr node) {
        _evaluation_engine->notify_before_stop_node(node);
    }

    void EvaluationEngineDelegate::notify_after_stop_node(node_ptr node) {
        _evaluation_engine->notify_after_stop_node(node);
    }

    void EvaluationEngineDelegate::notify_before_stop_graph(graph_ptr graph) {
        _evaluation_engine->notify_before_stop_graph(graph);
    }

    void EvaluationEngineDelegate::notify_after_stop_graph(graph_ptr graph) {
        _evaluation_engine->notify_after_stop_graph(graph);
    }

    void EvaluationEngineDelegate::initialise() { _evaluation_engine->initialise(); }

    void EvaluationEngineDelegate::start() { _evaluation_engine->start(); }

    void EvaluationEngineDelegate::stop() { _evaluation_engine->stop(); }

    void EvaluationEngineDelegate::dispose() { _evaluation_engine->dispose(); }

    BaseEvaluationClock::BaseEvaluationClock(engine_time_t start_time)
        : _evaluation_time{start_time}, _next_scheduled_evaluation_time{MAX_DT} {
    }

    engine_time_t BaseEvaluationClock::evaluation_time() const { return _evaluation_time; }

    engine_time_t BaseEvaluationClock::next_cycle_evaluation_time() const { return _evaluation_time + MIN_TD; }

    engine_time_t BaseEvaluationClock::next_scheduled_evaluation_time() const {
        return _next_scheduled_evaluation_time;
    }

    void BaseEvaluationClock::update_next_scheduled_evaluation_time(engine_time_t scheduled_time) {
        if (scheduled_time == _evaluation_time) {
            return; // This will be evaluated in the current cycle, nothing to do.
        }
        _next_scheduled_evaluation_time =
                std::max(next_cycle_evaluation_time(), std::min(_next_scheduled_evaluation_time, scheduled_time));
    }

    const engine_time_t *BaseEvaluationClock::evaluation_time_ptr() const { return &_evaluation_time; }


    SimulationEvaluationClock::SimulationEvaluationClock(engine_time_t current_time)
        : BaseEvaluationClock(current_time),
          _system_clock_at_start_of_evaluation{
              std::chrono::time_point_cast<std::chrono::microseconds>(engine_clock::now())
          } {
    }

    void SimulationEvaluationClock::set_evaluation_time(engine_time_t value) {
        BaseEvaluationClock::set_evaluation_time(value);
        _system_clock_at_start_of_evaluation = std::chrono::time_point_cast<std::chrono::microseconds>(
            engine_clock::now());
    }

    engine_time_t SimulationEvaluationClock::now() const { return evaluation_time() + cycle_time(); }

    engine_time_delta_t SimulationEvaluationClock::cycle_time() const {
        return std::chrono::duration_cast<engine_time_delta_t>(
            std::chrono::time_point_cast<std::chrono::microseconds>(engine_clock::now()) -
            _system_clock_at_start_of_evaluation);
    }

    void SimulationEvaluationClock::advance_to_next_scheduled_time() {
        set_evaluation_time(next_scheduled_evaluation_time());
    }

    void SimulationEvaluationClock::mark_push_node_requires_scheduling() {
        throw std::runtime_error("Simulation mode does not support push nodes.");
    }

    bool SimulationEvaluationClock::push_node_requires_scheduling() const { return false; }

    void SimulationEvaluationClock::reset_push_node_requires_scheduling() {
        throw std::runtime_error("Simulation mode does not support push nodes.");
    }

    RealTimeEvaluationClock::RealTimeEvaluationClock(engine_time_t start_time)
        : BaseEvaluationClock(start_time), _push_node_requires_scheduling(false), _ready_to_push(false),
          _last_time_allowed_push(MIN_TD) {
    }

    engine_time_t RealTimeEvaluationClock::now() const {
        return std::chrono::time_point_cast<std::chrono::microseconds>(engine_clock::now());
    }

    engine_time_delta_t RealTimeEvaluationClock::cycle_time() const {
        return std::chrono::duration_cast<engine_time_delta_t>(
            std::chrono::time_point_cast<std::chrono::microseconds>(engine_clock::now()) - evaluation_time());
    }

    void RealTimeEvaluationClock::mark_push_node_requires_scheduling() {
        std::unique_lock<std::mutex> lock(_condition_mutex);
        _push_node_requires_scheduling = true;
        _push_node_requires_scheduling_condition.notify_all();
    }

    bool RealTimeEvaluationClock::push_node_requires_scheduling() const {
        if (!_ready_to_push) { return false; }
        std::unique_lock<std::mutex> lock(_condition_mutex);
        return _push_node_requires_scheduling;
    }

    void RealTimeEvaluationClock::advance_to_next_scheduled_time() {
        engine_time_t next_scheduled_time = next_scheduled_evaluation_time();
        engine_time_t now = std::chrono::time_point_cast<std::chrono::microseconds>(engine_clock::now());

        // Process all alarms that are due and adjust the next scheduled time
        while (!_alarms.empty()) {
            auto it = _alarms.begin();
            if (now >= it->first) {
                // Extract the alarm key before erasing to avoid redundant copy
                auto alarm_key = std::move(*it);
                _alarms.erase(it);
                next_scheduled_time = std::max(next_scheduled_time, evaluation_time() + MIN_TD);

                auto cb = _alarm_callbacks.find(alarm_key);
                if (cb != _alarm_callbacks.end()) {
                    cb->second(next_scheduled_time);
                    _alarm_callbacks.erase(cb);
                }
            } else if (next_scheduled_time > it->first) {
                next_scheduled_time = it->first;
                break;
            } else {
                break;
            }
        }

        _ready_to_push = false;
        if (next_scheduled_time > evaluation_time() + MIN_TD || now > _last_time_allowed_push +
            std::chrono::seconds(15)) {
            // Mark ready-to-push and remember the last time we allowed pushing
            {
                std::unique_lock<std::mutex> lock(_condition_mutex);
                _ready_to_push = true;
                _last_time_allowed_push = now;
            }

            // Wait until either the next scheduled time arrives or a push-node requests scheduling.
            // Important: Avoid GIL/lock inversion by ensuring that the mutex is not held when the
            // GIL is reacquired (gil_scoped_release destructor). We achieve this by:
            //  - Creating gil_scoped_release BEFORE acquiring the mutex
            //  - Creating the unique_lock AFTER gil_scoped_release within the same scope
            //  - Letting the unique_lock be destroyed BEFORE gil_scoped_release at scope exit
            while (true) {
                // Check termination condition
                now = std::chrono::time_point_cast<std::chrono::microseconds>(engine_clock::now());
                if (now >= next_scheduled_time) {
                    break;
                }

                bool scheduled = false;
                auto sleep_time = std::chrono::duration_cast<engine_time_delta_t>(next_scheduled_time - now); {
                    nb::gil_scoped_release gil; // release GIL before taking the mutex
                    std::unique_lock<std::mutex> lock(_condition_mutex);
                    scheduled = _push_node_requires_scheduling;
                    if (!scheduled) {
                        _push_node_requires_scheduling_condition.wait_for(
                            lock, std::min(sleep_time, duration_cast<engine_time_delta_t>(std::chrono::seconds(10))));
                        scheduled = _push_node_requires_scheduling;
                    }
                } // lock released here, then GIL is reacquired — no inversion

                if (scheduled) {
                    break;
                }
            }
        }
        set_evaluation_time(std::min(next_scheduled_time, std::max(next_cycle_evaluation_time(), now)));

        // Process alarms again after updating evaluation_time
        while (!_alarms.empty()) {
            auto it = _alarms.begin();
            if (now >= it->first) {
                // Extract the alarm key before erasing to avoid redundant copy
                auto alarm_key = std::move(*it);
                _alarms.erase(it);

                auto cb = _alarm_callbacks.find(alarm_key);
                if (cb != _alarm_callbacks.end()) {
                    cb->second(evaluation_time());
                    _alarm_callbacks.erase(cb);
                }
            } else {
                break;
            }
        }
    }

    void RealTimeEvaluationClock::reset_push_node_requires_scheduling() {
        std::unique_lock<std::mutex> lock(_condition_mutex);
        _push_node_requires_scheduling = false;
    }

    void RealTimeEvaluationClock::set_alarm(engine_time_t alarm_time, const std::string &name,
                                            std::function<void(engine_time_t)> callback) {
        if (alarm_time <= evaluation_time()) { throw std::invalid_argument("Cannot set alarm in the engine's past"); }

        _alarms.emplace(alarm_time, name);
        _alarm_callbacks[{alarm_time, name}] = std::move(callback);
    }

    void RealTimeEvaluationClock::cancel_alarm(const std::string &name) {
        for (auto it = _alarms.begin(); it != _alarms.end();) {
            if (it->second == name) {
                _alarm_callbacks.erase(*it);
                it = _alarms.erase(it);
            } else {
                ++it;
            }
        }
    }

    EvaluationEngineImpl::EvaluationEngineImpl(EngineEvaluationClock::s_ptr clock, engine_time_t start_time,
                                               engine_time_t end_time,
                                               EvaluationMode run_mode)
        : _clock{std::move(clock)}, _start_time{start_time}, _end_time{end_time}, _run_mode{run_mode} {
    }

    void EvaluationEngineImpl::initialise() {
    }

    void EvaluationEngineImpl::start() {
    }

    void EvaluationEngineImpl::stop() {
    }

    void EvaluationEngineImpl::dispose() {
    }

    const EngineEvaluationClock::s_ptr& EvaluationEngineImpl::engine_evaluation_clock() { return _clock; }

    EvaluationMode EvaluationEngineImpl::evaluation_mode() const { return _run_mode; }

    engine_time_t EvaluationEngineImpl::start_time() const { return _start_time; }

    engine_time_t EvaluationEngineImpl::end_time() const { return _end_time; }

    EvaluationClock::s_ptr EvaluationEngineImpl::evaluation_clock() { return _clock; }

    void EvaluationEngineImpl::request_engine_stop() { _stop_requested = true; }

    bool EvaluationEngineImpl::is_stop_requested() { return _stop_requested; }

    void EvaluationEngineImpl::add_before_evaluation_notification(std::function<void()> &&fn) {
        _before_evaluation_notification.emplace_back(fn);
    }

    void EvaluationEngineImpl::add_after_evaluation_notification(std::function<void()> &&fn) {
        _after_evaluation_notification.emplace_back(fn);
    }

    void EvaluationEngineImpl::add_life_cycle_observer(EvaluationLifeCycleObserver::s_ptr observer) {
        _life_cycle_observers.emplace_back(std::move(observer));
    }

    void EvaluationEngineImpl::remove_life_cycle_observer(const EvaluationLifeCycleObserver::s_ptr& observer) {
        auto it{std::find(_life_cycle_observers.begin(), _life_cycle_observers.end(), observer)};
        if (it != _life_cycle_observers.end()) {
            // Since order is not important, we can swap the observer to remove with the last observer and then pop it.
            std::iter_swap(it, _life_cycle_observers.end() - 1);
            _life_cycle_observers.pop_back();
        }
    }

    void EvaluationEngineImpl::advance_engine_time() {
        if (_stop_requested) {
            _clock->set_evaluation_time(_end_time + MIN_TD);
            return;
        }
        // Ensure we don't run past the end time. So schedule to the end time + 1 tick.
        _clock->update_next_scheduled_evaluation_time(_end_time + MIN_TD);

        _clock->advance_to_next_scheduled_time();
    }

    void EvaluationEngineImpl::notify_before_evaluation() {
        // Process all notifications iteratively, including any added during callback execution
        while (!_before_evaluation_notification.empty()) {
            auto todo = std::move(_before_evaluation_notification);
            _before_evaluation_notification.clear();

            for (auto &notification_receiver: todo) {
                notification_receiver();
            }
        }
    }

    void EvaluationEngineImpl::notify_after_evaluation() {
        // Process all notifications iteratively in reverse order, including any added during callback execution
        while (!_after_evaluation_notification.empty()) {
            auto todo = std::move(_after_evaluation_notification);
            _after_evaluation_notification.clear();

            for (auto it = todo.rbegin(); it != todo.rend(); ++it) {
                (*it)();
            }
        }
    }

    void EvaluationEngineImpl::notify_before_start_graph(graph_ptr graph) {
        for (auto &life_cycle_observer: _life_cycle_observers) { life_cycle_observer->on_before_start_graph(graph); }
    }

    void EvaluationEngineImpl::notify_after_start_graph(graph_ptr graph) {
        for (auto &life_cycle_observer: _life_cycle_observers) { life_cycle_observer->on_after_start_graph(graph); }
    }

    void EvaluationEngineImpl::notify_before_start_node(node_ptr node) {
        for (auto &life_cycle_observer: _life_cycle_observers) { life_cycle_observer->on_before_start_node(node); }
    }

    void EvaluationEngineImpl::notify_after_start_node(node_ptr node) {
        for (auto &life_cycle_observer: _life_cycle_observers) { life_cycle_observer->on_after_start_node(node); }
    }

    void EvaluationEngineImpl::notify_before_graph_evaluation(graph_ptr graph) {
        for (auto &life_cycle_observer: _life_cycle_observers) {
            life_cycle_observer->on_before_graph_evaluation(graph);
        }
    }

    void EvaluationEngineImpl::notify_after_graph_evaluation(graph_ptr graph) {
        for (auto &life_cycle_observer: _life_cycle_observers) {
            life_cycle_observer->on_after_graph_evaluation(graph);
        }
    }

    void EvaluationEngineImpl::notify_after_push_nodes_evaluation(graph_ptr graph) {
        for (auto &life_cycle_observer: _life_cycle_observers) {
            life_cycle_observer->on_after_graph_push_nodes_evaluation(graph);
        }
    }

    void EvaluationEngineImpl::notify_before_node_evaluation(node_ptr node) {
        for (auto &life_cycle_observer: _life_cycle_observers) { life_cycle_observer->on_before_node_evaluation(node); }
    }

    void EvaluationEngineImpl::notify_after_node_evaluation(node_ptr node) {
        for (auto &life_cycle_observer: _life_cycle_observers) { life_cycle_observer->on_after_node_evaluation(node); }
    }

    void EvaluationEngineImpl::notify_before_stop_node(node_ptr node) {
        for (auto &life_cycle_observer: _life_cycle_observers) { life_cycle_observer->on_before_stop_node(node); }
    }

    void EvaluationEngineImpl::notify_after_stop_node(node_ptr node) {
        for (auto &life_cycle_observer: _life_cycle_observers) { life_cycle_observer->on_after_stop_node(node); }
    }

    void EvaluationEngineImpl::notify_before_stop_graph(graph_ptr graph) {
        for (auto &life_cycle_observer: _life_cycle_observers) { life_cycle_observer->on_before_stop_graph(graph); }
    }

    void EvaluationEngineImpl::notify_after_stop_graph(graph_ptr graph) {
        for (auto &life_cycle_observer: _life_cycle_observers) { life_cycle_observer->on_after_stop_graph(graph); }
    }

} // namespace hgraph