#include "gateway.h"
#include "controller.hpp"
#include "modbus_tcp.hpp"
#include "vendor_formats.hpp"
#include "codec.hpp"
#include "logger.hpp"
#include "json.hpp"

#include <fstream>
#include <numeric>
#include <algorithm>
#include <unistd.h>
#include <grpcpp/server.h>
#include <grpcpp/server_builder.h>
#include <grpcpp/server_context.h>
#include <google/protobuf/timestamp.pb.h>

// ─── Constructor / Destructor ───

Hc900Gateway::Hc900Gateway(const std::string& host, uint16_t port,
                           const std::string& map_path, int poll_interval_ms,
                           int grpc_port)
    : host_(host), port_(port), poll_interval_ms_(poll_interval_ms),
      grpc_listen_("0.0.0.0:" + std::to_string(grpc_port))
{
    LoadRegisterMap(map_path);
    auto transport = std::make_unique<ModbusTCP>();
    controller_ = std::make_unique<Controller>(std::move(transport));
}

Hc900Gateway::~Hc900Gateway() { Stop(); }

// ─── Start / Stop ───

bool Hc900Gateway::Start()
{
    Logger::instance().log("INFO", "[Gateway] Connecting to " + host_ + ":" + std::to_string(port_));
    if (!controller_->connect(host_.c_str(), port_)) {
        // 연결 실패해도 종료하지 않음 — 폴 루프에서 재접속 재시도
        Logger::instance().log("WARN", "[Gateway] Initial connection failed. Will retry in poll loop...");
    } else {
        Logger::instance().log("INFO", "[Gateway] Connected.");
    }
    Logger::instance().log("INFO", "[Gateway] Starting poll thread (interval=" + std::to_string(poll_interval_ms_) + "ms)");

    grpc_service_ = std::make_unique<GatewayServiceImpl>(*this);
    grpc::ServerBuilder builder;
    builder.AddListeningPort(grpc_listen_, grpc::InsecureServerCredentials());
    builder.RegisterService(grpc_service_.get());
    grpc_server_ = builder.BuildAndStart();
    Logger::instance().log("INFO", "[Gateway] gRPC server listening on " + grpc_listen_);

    running_ = true;
    poll_thread_ = std::thread(&Hc900Gateway::PollLoop, this);
    return true;
}

void Hc900Gateway::Stop()
{
    running_ = false;
    if (poll_thread_.joinable()) poll_thread_.join();
    if (grpc_server_) grpc_server_->Shutdown();
    if (controller_) controller_->disconnect();
}

// ─── Register Map Loading ───

void Hc900Gateway::LoadRegisterMap(const std::string& path)
{
    std::ifstream f(path);
    if (!f.is_open()) {
        Logger::instance().log("ERROR", "[Gateway] Cannot open register map: " + path);
        return;
    }

    auto j = nlohmann::json::parse(f);
    for (const auto& item : j["registers"]) {
        RegisterEntry e;
        e.tag        = item["tag"];
        e.addr       = item["addr"];
        e.write_addr = item.value("write_addr", e.addr);  // default: write where we read
        e.count      = item.value("count", 2);
        e.type       = item.value("type", "float32");
        e.access     = item.value("access", "R");
        registers_.push_back(e);
        tag_index_[e.tag] = registers_.size() - 1;
    }

    // Build address-sorted index for batch reads
    sorted_indices_.resize(registers_.size());
    std::iota(sorted_indices_.begin(), sorted_indices_.end(), 0);
    std::sort(sorted_indices_.begin(), sorted_indices_.end(),
              [this](size_t a, size_t b){ return registers_[a].addr < registers_[b].addr; });

    Logger::instance().log("INFO", "[Gateway] Loaded " + std::to_string(registers_.size()) + " registers from " + path);
}

// ─── Poll Loop ───

void Hc900Gateway::PollLoop()
{
    while (running_) {
        auto t0 = std::chrono::steady_clock::now();

        if (controller_->is_connected()) {
            ReadAllRegisters();
            poll_count_++;
        } else {
            controller_->poll();
        }

        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::steady_clock::now() - t0);
        last_poll_duration_ = elapsed;

        auto sleep_for = std::chrono::milliseconds(poll_interval_ms_) - elapsed;
        if (sleep_for.count() > 0) {
            std::this_thread::sleep_for(sleep_for);
        }
    }
}

void Hc900Gateway::ReadAllRegisters()
{
    constexpr uint16_t MAX_BATCH = 120;
    decltype(cache_) fresh;

    size_t i = 0;
    while (i < sorted_indices_.size()) {
        uint32_t batch_start = registers_[sorted_indices_[i]].addr;

        // Collect all registers that fit within [batch_start, batch_start + MAX_BATCH)
        size_t j = i;
        while (j < sorted_indices_.size()) {
            const auto& e = registers_[sorted_indices_[j]];
            if (e.addr + e.count - batch_start > MAX_BATCH) break;
            ++j;
        }

        // Read count = up to end of last register in this batch
        const auto& last = registers_[sorted_indices_[j - 1]];
        uint16_t read_count = static_cast<uint16_t>(last.addr + last.count - batch_start);

        std::vector<uint16_t> regs;
        bool ok;
        {
            std::lock_guard<std::mutex> lock(transport_mutex_);
            ok = controller_->read_raw(static_cast<uint16_t>(batch_start), read_count, regs);
        }

        if (ok) {
            auto now = std::chrono::system_clock::now();
            for (size_t k = i; k < j; k++) {
                const auto& entry = registers_[sorted_indices_[k]];
                size_t off = entry.addr - batch_start;

                CachedValue cv{};
                cv.timestamp = now;
                cv.quality = 192;

                if (entry.type == "uint16") {
                    cv.is_float = false;
                    cv.uint16_val = regs[off];
                } else {
                    cv.is_float = true;
                    cv.float32_val = decode_float(regs[off], regs[off + 1], VendorFormat::HC900_FLOAT);
                }
                fresh[entry.tag] = cv;
            }
        }

        i = j;
    }

    {
        std::lock_guard<std::mutex> lock(cache_mutex_);
        cache_ = std::move(fresh);
    }
}

// ─── gRPC Service Implementation ───

Hc900Gateway::GatewayServiceImpl::GatewayServiceImpl(Hc900Gateway& gateway)
    : gateway_(gateway) {}

namespace {
void TagValueFromCache(hc900::TagValue* tv, const std::string& name, const CachedValue& cv) {
    tv->set_tag_name(name);
    tv->set_quality(cv.quality);
    auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
        cv.timestamp.time_since_epoch()).count();
    tv->mutable_timestamp()->set_seconds(static_cast<int64_t>(ns / 1000000000));
    tv->mutable_timestamp()->set_nanos(static_cast<int32_t>(ns % 1000000000));
    if (cv.is_float) {
        tv->set_float32_val(cv.float32_val);
    } else {
        tv->set_uint16_val(cv.uint16_val);
    }
}
}

grpc::Status Hc900Gateway::GatewayServiceImpl::ReadTags(
    grpc::ServerContext*,
    const hc900::ReadTagsRequest* req,
    hc900::ReadTagsResponse* resp)
{
    std::lock_guard<std::mutex> lock(gateway_.cache_mutex_);

    if (req->tag_names().empty()) {
        for (const auto& [tag, cv] : gateway_.cache_) {
            TagValueFromCache(resp->add_values(), tag, cv);
        }
    } else {
        for (const auto& name : req->tag_names()) {
            auto it = gateway_.cache_.find(name);
            if (it != gateway_.cache_.end()) {
                TagValueFromCache(resp->add_values(), it->first, it->second);
            }
        }
    }
    return grpc::Status::OK;
}

grpc::Status Hc900Gateway::GatewayServiceImpl::WriteTag(
    grpc::ServerContext*,
    const hc900::WriteTagRequest* req,
    hc900::WriteTagResponse* resp)
{
    auto it = gateway_.tag_index_.find(req->tag_name());
    if (it == gateway_.tag_index_.end()) {
        resp->set_success(false);
        resp->set_error("Tag not found");
        return grpc::Status::OK;
    }

    auto& entry = gateway_.registers_[it->second];
    if (entry.access != "RW") {
        resp->set_success(false);
        resp->set_error("Read-only tag");
        return grpc::Status::OK;
    }

    bool ok = false;
    {
        std::lock_guard<std::mutex> lock(gateway_.transport_mutex_);
        if (entry.type == "uint16") {
            ok = gateway_.controller_->write_register(entry.write_addr,
                static_cast<uint16_t>(req->value()));
        } else if (entry.type == "float32") {
            ok = gateway_.controller_->write_float(entry.write_addr,
                static_cast<float>(req->value()),
                VendorFormat::HC900_FLOAT);
        }
    }

    resp->set_success(ok);
    if (!ok) resp->set_error("Modbus write failed");

    if (ok) {
        std::lock_guard<std::mutex> lock(gateway_.cache_mutex_);
        CachedValue cv;
        cv.is_float = (entry.type == "float32");
        if (cv.is_float) cv.float32_val = static_cast<float>(req->value());
        else            cv.uint16_val  = static_cast<uint16_t>(req->value());
        cv.quality = 192;
        cv.timestamp = std::chrono::system_clock::now();
        gateway_.cache_[entry.tag] = cv;
    }

    return grpc::Status::OK;
}

grpc::Status Hc900Gateway::GatewayServiceImpl::ListTags(
    grpc::ServerContext*,
    const hc900::ListTagsRequest* req,
    hc900::ListTagsResponse* resp)
{
    // ListTags 필터: 대소문자 무시 검색 (ReadTags/WriteTag는 exact-match, register-map 원형 표기 필요)
    int count = 0;
    for (const auto& entry : gateway_.registers_) {
        if (!req->filter().empty()) {
            auto lower_tag = entry.tag;
            auto lower_filter = req->filter();
            std::transform(lower_tag.begin(), lower_tag.end(), lower_tag.begin(), ::tolower);
            std::transform(lower_filter.begin(), lower_filter.end(), lower_filter.begin(), ::tolower);
            if (lower_tag.find(lower_filter) == std::string::npos) continue;
        }
        auto* meta = resp->add_tags();
        meta->set_tag_name(entry.tag);
        meta->set_address(entry.addr);
        meta->set_count(entry.count);
        meta->set_type(entry.type);
        meta->set_access(entry.access);
        count++;
        if (req->limit() > 0 && count >= req->limit()) break;
    }
    return grpc::Status::OK;
}

grpc::Status Hc900Gateway::GatewayServiceImpl::HealthCheck(
    grpc::ServerContext*,
    const hc900::HealthCheckRequest*,
    hc900::HealthCheckResponse* resp)
{
    resp->set_status(gateway_.controller_->is_connected()
        ? hc900::HealthCheckResponse::SERVING
        : hc900::HealthCheckResponse::NOT_SERVING);
    resp->set_uptime_sec(0);
    resp->set_poll_count(gateway_.poll_count_);
    resp->set_last_poll_ms(static_cast<int32_t>(gateway_.last_poll_duration_.count()));
    resp->set_controller_ip(gateway_.host_);
    resp->set_active_tags(static_cast<int32_t>(gateway_.registers_.size()));
    return grpc::Status::OK;
}

grpc::Status Hc900Gateway::GatewayServiceImpl::StreamTags(
    grpc::ServerContext* ctx,
    const hc900::StreamTagsRequest* req,
    grpc::ServerWriter<hc900::TagValue>* writer)
{
    int interval = req->interval_ms() > 0 ? req->interval_ms() : 1000;

    std::vector<std::string> names;
    if (req->tag_names().empty()) {
        std::lock_guard<std::mutex> lock(gateway_.cache_mutex_);
        for (const auto& [tag, _] : gateway_.cache_) {
            names.push_back(tag);
        }
    } else {
        names.assign(req->tag_names().begin(), req->tag_names().end());
    }

    while (!ctx->IsCancelled()) {
        auto t0 = std::chrono::steady_clock::now();

        {
            std::lock_guard<std::mutex> lock(gateway_.cache_mutex_);
            for (const auto& name : names) {
                auto it = gateway_.cache_.find(name);
                if (it == gateway_.cache_.end()) continue;
                auto& cv = it->second;

                hc900::TagValue tv;
                TagValueFromCache(&tv, name, cv);
                if (!writer->Write(tv)) return grpc::Status::CANCELLED;
            }
        }

        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::steady_clock::now() - t0);
        auto sleep_for = std::chrono::milliseconds(interval) - elapsed;
        if (sleep_for.count() > 0) {
            std::this_thread::sleep_for(sleep_for);
        }
    }
    return grpc::Status::OK;
}

// ─── main ───
//
// One process per controller. The C# ControllerProcessManager launches this with:
//   hc900_gateway <host> <register-map> <poll_ms> <grpc_port> <modbus_port>
int main(int argc, char* argv[])
{
    std::string host = "192.168.0.240";
    std::string map_path = "docs/register-map.json";
    int    poll_ms      = 1000;
    int    grpc_port    = 50051;
    uint16_t modbus_port = 502;

    if (argc > 1) host        = argv[1];
    if (argc > 2) map_path    = argv[2];
    if (argc > 3) poll_ms     = std::atoi(argv[3]);
    if (argc > 4) grpc_port   = std::atoi(argv[4]);
    if (argc > 5) modbus_port = static_cast<uint16_t>(std::atoi(argv[5]));

    Logger::instance().set_file("/tmp/hc900_gateway.log");

    Hc900Gateway gateway(host, modbus_port, map_path, poll_ms, grpc_port);
    if (!gateway.Start()) {
        return 1;
    }

    Logger::instance().log("INFO", "[Gateway] Running. Press Ctrl+C to stop.");
    pause();
    gateway.Stop();
    return 0;
}
