// clang-format off

/*

 * pooler | MatterLinux pool server
 * MatterLinux 2023-2024 (https://matterlinux.xyz)

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.

*/

// clang-format on

#include <arpa/inet.h>
#include <errno.h>
#include <libmp/all.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "config.h"
#include "intl.h"
#include "log.h"
#include "util.h"

void sockaddr_to_str(struct sockaddr *addr, char *str) {
  struct sockaddr_in  *ipv4;
  struct sockaddr_in6 *ipv6;

  switch (addr->sa_family) {
  case AF_INET:
    ipv4 = (struct sockaddr_in *)addr;
    inet_ntop(AF_INET, &ipv4->sin_addr, str, INET_ADDRSTRLEN);
    break;

  case AF_INET6:
    ipv6 = (struct sockaddr_in6 *)addr;
    inet_ntop(AF_INET6, &ipv6->sin6_addr, str, INET6_ADDRSTRLEN);
    break;
  }
}

bool sync_callback(
    lm_ctx_t *ctx, lm_pool_t *pool, lm_ctx_sync_state_t state, size_t current, size_t total, void *data) {
  switch (state) {
  case SYNC_INFO_SUCCESS:
    info(_("%s: successfuly loaded the pool info"), pool->name);
    break;

  case SYNC_INFO_FAIL:
    info(_("%s: failed to load the pool info (%s)"), pool->name, lm_strerror());
    return false;

  case SYNC_LIST_SUCCESS:
    info(_("%s: successfuly loaded the package list"), pool->name);
    break;

  case SYNC_LIST_FAIL:
    info(_("%s: failed to load the package list (%s)"), pool->name, lm_strerror());
    return false;

  default:
    // ignore other callbacks
    break;
  }

  return true;
}

bool serve_callback(lm_pool_t *pool, lm_mptp_t *packet, struct sockaddr *addr, void *data) {
  char ipaddr[INET6_ADDRSTRLEN];
  bzero(ipaddr, sizeof(ipaddr));
  sockaddr_to_str(addr, ipaddr);

  switch (MPTP_FLAGS_CODE(packet)) {
  case MPTP_C2S_PING:
    info(_("Request from %s: PING (%s)"), ipaddr, pool->name);
    break;

  case MPTP_C2S_INFO:
    info(_("Request from %s: INFO (%s)"), ipaddr, pool->name);
    break;

  case MPTP_C2S_LIST:
    info(_("Request from %s: LIST (%s)"), ipaddr, pool->name);
    break;

  case MPTP_C2S_PULL:
    info(_("Request from %s: PULL (%s)"), ipaddr, pool->name);
    break;
  }

  return true;
}

int main(int argc, char *argv[]) {
  info(_("Starting pooler %s (libmp %s)"), VERSION, LM_VERSION);

  if (argc != 2) {
    error(_("Configuration file not specified"));
    return EXIT_FAILURE;
  }

  char          *addr = NULL, *logfile = NULL, *tmpdir = NULL;
  int            ret        = EXIT_FAILURE;
  pool_config_t *pool       = NULL;
  size_t         pool_count = 0;
  lm_ctx_t       ctx;

  if (!config_load(argv[1]))
    goto end_config;

  info(_("Loaded the configuration"));

  if ((tmpdir = config_get_string("tmpdir")) == NULL) {
    error(_("Failed to get the temp directory configuration option (tmpdir)"));
    goto end_config;
  }

  lm_ctx_new(&ctx, NULL, tmpdir, NULL);
  addr = config_get_string("addr");

  if (config_get_integer("threads") <= 0 || config_get_integer("threads") > 1000) {
    error(_("Please specify a valid thread count (1-1000)"));
    goto end_ctx;
  }

  if ((pool = config.pools) == NULL) {
    error(_("Please specify at least one pool in the configuration"));
    goto end_ctx;
  }

  while (NULL != pool) {
    info(_("%s: loading the pool"), pool->name);

    if (NULL == pool->host) {
      error(_("%s: hostname not specified for pool, skipping"), pool->name);
      goto end_ctx;
    }

    char url[strlen(pool->host) + 20];
    sprintf(url, "mptp://%s", pool->host);

    if (!file_canread(pool->dir)) {
      error(_("%s: failed access the pool directory (%s)"), pool->name, pool->dir);
      goto end_ctx;
    }

    if (NULL == lm_ctx_pool_add(&ctx, pool->name, url, pool->dir)) {
      error(_("%s: failed to add pool to the list (%s)"), pool->name, lm_strerror());
      goto end_ctx;
    }

    pool = pool->next;
  }

  if ((pool_count = lm_ctx_sync(&ctx, false, sync_callback, NULL)) < 0) {
    error(_("Failed to sync the pools: %s"), lm_strerror());
    goto end_ctx;
  }

  if (pool_count == 0) {
    error(_("None of the pools is available for serving"));
    goto end_ctx;
  }

  info(pool_count == 1 ? _("Serving %lu pool on %s") : _("Serving %lu pools on %s"), pool_count, addr);

  if (!lm_ctx_serve(&ctx, addr, config_get_integer("threads"), serve_callback, NULL)) {
    error(_("Failed to start the server: %s"), lm_strerror());
    goto end_ctx;
  }

  ret = EXIT_SUCCESS;

end_ctx:
  lm_ctx_free(&ctx);

end_config:
  config_free();
  return ret;
}