BareGit
#include <array>
#include <chrono>
#include <csignal>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <utility>

#include <Magick++.h>
#include <spdlog/spdlog.h>
#include <uni_algo/version.h>

#include "app.h"
#include "clock.h"
#include "startup.h"

namespace
{

/// Signal-safe flag requesting shutdown from the main loop.
volatile std::sig_atomic_t STOP_REQUESTED = 0;

void handleSignal([[maybe_unused]] int signal)
{
    STOP_REQUESTED = 1;
}

mw::E<void> prepareImageMagick()
{
    const std::array<std::string, 4> coders = {
        "JPEG", "PNG", "WEBP", "AVIF"};
    try
    {
        for(const std::string& name : coders)
        {
            const Magick::CoderInfo coder(name);
            if(!coder.isReadable())
            {
                return std::unexpected(mw::runtimeError(
                    "ImageMagick coder " + name + " is not readable"));
            }
            if(name == "AVIF" && !coder.isWritable())
            {
                return std::unexpected(mw::runtimeError(
                    "ImageMagick coder AVIF is not writable"));
            }
        }
    }
    catch(const Magick::Exception& error)
    {
        return std::unexpected(mw::runtimeError(
            "Failed to inspect ImageMagick capabilities: " +
            std::string(error.what())));
    }

    Magick::ResourceLimits::listLength(2);
    Magick::ResourceLimits::width(2048);
    Magick::ResourceLimits::height(2048);
    return {};
}

void printUsage(std::ostream& output, const char* program)
{
    output << "Usage: " << program << " <config.toml>\n";
}

} // namespace

/// Start Card Collection from one validated configuration file.
int main(int argc, char** argv)
{
    const char* program = argc > 0 ? argv[0] : "card_collection";
    if(argc == 2 && std::string(argv[1]) == "--help")
    {
        printUsage(std::cout, program);
        return 0;
    }
    if(argc != 2)
    {
        printUsage(std::cerr, program);
        return 2;
    }

    auto config = loadConfig(argv[1]);
    if(!config)
    {
        std::cerr << "Failed to load configuration: "
                  << config.error().msg() << '\n';
        return 1;
    }

    Magick::InitializeMagick(argc > 0 ? argv[0] : nullptr);
    auto image_magick = prepareImageMagick();
    if(!image_magick)
    {
        spdlog::error(
            "Failed to initialize image processing: {}",
            image_magick.error().msg());
        return 1;
    }
    spdlog::info(
        "Username normalization uses uni-algo {}.{}.{} and Unicode {}.{}.{}",
        una::version::library.major(),
        una::version::library.minor(),
        una::version::library.patch(),
        una::version::unicode.major(),
        una::version::unicode.minor(),
        una::version::unicode.update());

    const auto startup_time = std::chrono::system_clock::now();
    const std::int64_t startup_seconds =
        std::chrono::duration_cast<std::chrono::seconds>(
            startup_time.time_since_epoch()).count();
    auto data_source = prepareDataSource(
        config->database_path,
        config->administrator_email,
        config->administrator_email_key,
        startup_seconds,
        utcDay(startup_time));
    if(!data_source)
    {
        spdlog::error(
            "Failed to prepare the card database: {}",
            data_source.error().msg());
        return 1;
    }

    AssetStore assets(config->card_storage_root);
    auto reconciled = assets.reconcile(**data_source);
    if(!reconciled)
    {
        spdlog::error(
            "Failed to reconcile card storage: {}",
            reconciled.error().msg());
        return 1;
    }

    try
    {
        App app(
            *config,
            std::move(*data_source),
            std::make_unique<NonSecretRandom>());

        std::signal(SIGINT, handleSignal);
        std::signal(SIGTERM, handleSignal);

        auto start_result = app.start();
        if(!start_result)
        {
            spdlog::error(
                "Failed to start the server: {}",
                start_result.error().msg());
            return 1;
        }

        spdlog::info("Server listening at {}", config->base_url.str());
        while(STOP_REQUESTED == 0)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }

        app.stop();
        app.wait();
    }
    catch(const std::exception& error)
    {
        spdlog::error(
            "Failed to construct application services: {}", error.what());
        return 1;
    }
    return 0;
}