#include "hosts/subscriber.h"
#include "hosts/publisher.h"
#include "core/error.h"

namespace dofs {

Subscriber::Subscriber(Simulator* sim,
                       NodeId id,
                       Publisher* publisher,
                       std::unique_ptr<SubBasePolicy> policy,
                       Time mgmt_latency,
                       Time heartbeat_period) noexcept
        : Host(sim, id)
        , _sim(sim)
        , _publisher(publisher)
        , _policy(std::move(policy))
        , _mgmt_latency(mgmt_latency)
        , _hb_period(heartbeat_period) {
        // Deterministic stagger: simple function of NodeId.
        Time::rep off = (static_cast<Time::rep>(id) % (std::max<Time::rep>(1,
                         heartbeat_period.count() / 4)));
        schedule_next_heartbeat(Time(off));
}

void Subscriber::set_status(NodeStatus s) noexcept {
        // Node::set_status(s);
        if (s == NodeStatus::DOWN) {
                if (_hb_evt != NULL_EVENT) {
                        _sim->cancel(_hb_evt);
                        _hb_evt = NULL_EVENT;
                }

                _state = State::Quiesced;
        }
}

void Subscriber::schedule_next_heartbeat(Time delay) noexcept {
        if (_state == State::Quiesced)
                return;

        _hb_evt = _sim->schedule_after(delay, [this]() {
                this->on_heartbeat_timer();
        });
}

void Subscriber::on_heartbeat_timer() noexcept {
        _hb_evt = NULL_EVENT;

        if (_state == State::Quiesced)
                return;

        if (_publisher) {
                const NodeId     sid = this->id();
                const NodeStatus st  = NodeStatus::OK;
                const Time       gen = _sim->now();
                Publisher* const pub = _publisher;
                _sim->schedule_after(_mgmt_latency, [pub, sid, st, gen]() {
                        pub->recv_mgmt_msg(std::make_unique<HeartbeatMsg>(sid, st, gen));
                });
        }

        schedule_next_heartbeat(_hb_period);
}

void Subscriber::send_job_finished(FlowId flow) noexcept {
        if (_publisher) {
                const Time fin = _sim->now();
                _sim->schedule_after(_mgmt_latency, [pub = _publisher, flow, fin]() mutable {
                        pub->recv_mgmt_msg(std::make_unique<JobFinishedMsg>(flow, fin));
                });
        }
}

void Subscriber::recv_mgmt_msg(MgmtMsgPtr msg) noexcept {
        if (!msg)
                return;

        if (msg->kind() == MgmtKind::END_SIMULATION) {
                if (_hb_evt != NULL_EVENT) {
                        _sim->cancel(_hb_evt);
                        _hb_evt = NULL_EVENT;
                }

                _state = State::Quiesced;
        }
}

void Subscriber::recv_flow(NodeId /*src*/, FlowId flow, FlowPriority /*prio*/,
                           Bytes /*flow_size*/) {
        if (_state == State::Quiesced) {
                DOFS_ERROR("subscriber", "drop flow: quiesced");
                return;
        }

        // 3-day scope: zero processing delay → immediately report completion.
        send_job_finished(flow);
        _state = State::Idle;
}

void Subscriber::recv_frame(const Packet & /*frame*/) {
        // Not used for this pub/sub workload phase.
}

} // namespace dofs
