/** @file

  Config reload execution logic - schedules async reload work on ET_TASK.

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 */

#include "mgmt/config/ConfigReloadExecutor.h"
#include "mgmt/config/FileManager.h"
#include "mgmt/config/ReloadCoordinator.h"

#include "iocore/eventsystem/Continuation.h"
#include "iocore/eventsystem/Tasks.h"
#include "iocore/eventsystem/EventProcessor.h"

#include "records/RecCore.h"

#include "tscore/Diags.h"

namespace
{
DbgCtl dbg_ctl_config{"config.reload"};

/**
 * Continuation that executes the actual config reload work.
 * This runs on ET_TASK thread to avoid blocking the main RPC thread.
 */
struct ReloadWorkContinuation : public Continuation {
  int
  handleEvent(int /* etype */, void * /* data */)
  {
    bool failed{false};
    auto current_task = ReloadCoordinator::Get_Instance().get_current_task();

    Dbg(dbg_ctl_config, "Executing config reload work");

    if (current_task) {
      Dbg(dbg_ctl_config, "Reload task token: %s", current_task->get_token().c_str());
    }

    // This will tell each changed file to reread itself. If some module is waiting
    // for a record to be reloaded, it will be notified and the file update will happen
    // at each module's logic.
    // Each module will get a ConfigContext object which will be used to track the reload progress.
    if (auto err = FileManager::instance().rereadConfig(); !err.empty()) {
      Dbg(dbg_ctl_config, "rereadConfig failed");
      failed = true;
    }

    // Force-flush pending record callbacks. rereadConfig() marks records as
    // sync-required via RecSetSyncRequired(), but the actual on_record_change
    // callbacks normally wait for the next config_update_cont tick (~3s).
    // Flushing here fires those callbacks immediately so record-triggered
    // handlers can reserve their subtasks before handleEvent() returns.
    // RecExecConfigUpdateCbs clears the sync-required flag so the next
    // config_update_cont tick is a no-op for these records.
    RecFlushConfigUpdateCbs();
    Dbg(dbg_ctl_config, "Flushed pending record update callbacks");

    Dbg(dbg_ctl_config, "Invoking plugin callbacks");
    // If any callback was registered (TSMgmtUpdateRegister) for config notifications,
    // then it will eventually be notified.
    FileManager::instance().invokeConfigPluginCallbacks();

    Dbg(dbg_ctl_config, "Reload work completed, failed=%s", failed ? "true" : "false");

    delete this;
    return failed ? EVENT_ERROR : EVENT_DONE;
  }

  ReloadWorkContinuation() : Continuation(new_ProxyMutex()) { SET_HANDLER(&ReloadWorkContinuation::handleEvent); }
};

} // namespace

namespace config
{

void
schedule_reload_work(std::chrono::milliseconds delay)
{
  Dbg(dbg_ctl_config, "Scheduling reload work with %lldms delay", static_cast<long long>(delay.count()));
  eventProcessor.schedule_in(new ReloadWorkContinuation(), HRTIME_MSECONDS(delay.count()), ET_TASK);
}

} // namespace config
