BareGit
#pragma once

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include <inja/inja.hpp>
#include <mw/crypto.hpp>
#include <mw/http_server.hpp>

#include "authentication.h"
#include "card_service.h"
#include "clock.h"
#include "collection.h"
#include "config.h"
#include "data.h"
#include "email_sender.h"
#include "game_service.h"
#include "non_secret_random.h"
#include "series_service.h"
#include "url_builder.h"
#include "user_service.h"

struct CardUpload;
struct GameFieldValidationError;

/// Card Collection HTTP application and named-route owner.
class App : public mw::HTTPServer
{
public:
    /// HTTP request type supplied by libmw.
    using Request = mw::HTTPServer::Request;

    /// HTTP response type supplied by libmw.
    using Response = mw::HTTPServer::Response;

    /// Streaming request-body reader supplied by cpp-httplib.
    using ContentReader = httplib::ContentReader;

    /// Disable construction without validated configuration.
    App() = delete;

    /// Construct the application from configuration and a data source.
    App(
        const Config& config,
        std::unique_ptr<DataSourceInterface> data_source,
        std::unique_ptr<NonSecretRandom> random);

    /// Return the absolute URL for a named application route.
    std::string urlFor(
        const std::string& name,
        const std::vector<std::string>& arguments = {},
        const QueryParameters& query = {}) const;

    /// Render the card index.
    void handleCardIndex(const Request& request, Response& response);

    /// Redirect an authenticated visitor or render the public welcome page.
    void handleWelcome(const Request& request, Response& response);

    /// Render the passwordless email request form.
    void handleAuthentication(const Request& request, Response& response);

    /// Reserve and send a passwordless authentication link.
    void handleAuthenticationEmail(
        const Request& request, Response& response);

    /// Render the neutral authentication-email acknowledgement.
    void handleAuthenticationSent(
        const Request& request, Response& response);

    /// Render a non-consuming authentication confirmation form.
    void handleAuthenticationConfirm(
        const Request& request, Response& response);

    /// Consume an authentication challenge and establish a session.
    void handleAuthenticationConfirmPost(
        const Request& request, Response& response);

    /// Revoke the current session.
    void handleLogout(const Request& request, Response& response);

    /// Render the required initial-username form.
    void handleOnboarding(const Request& request, Response& response);

    /// Persist the required initial username.
    void handleOnboardingPost(const Request& request, Response& response);

    /// Render the current account summary.
    void handleAccount(const Request& request, Response& response);

    /// Render the username-change form.
    void handleAccountUsername(const Request& request, Response& response);

    /// Persist a username change.
    void handleAccountUsernamePost(
        const Request& request, Response& response);

    /// Render the current user's card collection and pull balance.
    void handleCollection(const Request& request, Response& response);

    /// Perform one atomic collection pull.
    void handleCollectionPull(const Request& request, Response& response);

    /// Render the administrator's user table.
    void handleAdminUsers(const Request& request, Response& response);

    /// Render the administrator's installed-game inventory.
    void handleAdminGames(const Request& request, Response& response);

    /// Render the create-game form.
    void handleGameNew(const Request& request, Response& response);

    /// Create a database-defined game.
    void handleGameCreate(const Request& request, Response& response);

    /// Render one game and its field-definition editor.
    void handleGameEdit(const Request& request, Response& response);

    /// Update one game's mutable metadata.
    void handleGameUpdate(const Request& request, Response& response);

    /// Render a game-delete confirmation.
    void handleGameDeleteConfirm(const Request& request, Response& response);

    /// Delete an unused game.
    void handleGameDelete(const Request& request, Response& response);

    /// Render the add-field form for one game.
    void handleGameFieldNew(const Request& request, Response& response);

    /// Add one custom field to a game.
    void handleGameFieldCreate(const Request& request, Response& response);

    /// Render one custom-field editor.
    void handleGameFieldEdit(const Request& request, Response& response);

    /// Update one custom field and its choices.
    void handleGameFieldUpdate(const Request& request, Response& response);

    /// Render a custom-field delete confirmation.
    void handleGameFieldDeleteConfirm(
        const Request& request, Response& response);

    /// Delete one unused custom field.
    void handleGameFieldDelete(const Request& request, Response& response);

    /// Save the complete custom-field ordering for one game.
    void handleGameFieldOrder(const Request& request, Response& response);

    /// Permanently promote one player to creator.
    void handleAdminPromote(const Request& request, Response& response);

    /// Render the create-card form.
    void handleCardNew(const Request& request, Response& response);

    /// Accept, process, and persist a new card upload.
    void handleCardCreate(
        const Request& request,
        Response& response,
        const ContentReader& content_reader);

    /// Render one read-only card page.
    void handleCardView(const Request& request, Response& response);

    /// Render the edit form for one card.
    void handleCardEdit(const Request& request, Response& response);

    /// Accept and persist one card edit.
    void handleCardUpdate(
        const Request& request,
        Response& response,
        const ContentReader& content_reader);

    /// Render the card-delete confirmation page.
    void handleCardDeleteConfirm(
        const Request& request,
        Response& response);

    /// Delete one card and redirect to the index.
    void handleCardDelete(const Request& request, Response& response);

    /// Render the series administration index.
    void handleSeriesIndex(const Request& request, Response& response);

    /// Render the create-series form.
    void handleSeriesNew(const Request& request, Response& response);

    /// Persist a new series.
    void handleSeriesCreate(const Request& request, Response& response);

    /// Render the edit-series form.
    void handleSeriesEdit(const Request& request, Response& response);

    /// Persist changes to one series.
    void handleSeriesUpdate(const Request& request, Response& response);

    /// Render the delete-series confirmation page.
    void handleSeriesDeleteConfirm(
        const Request& request,
        Response& response);

    /// Delete one series and redirect to the series index.
    void handleSeriesDelete(const Request& request, Response& response);

private:
    struct RequestIdentity
    {
        SessionContext session;
        std::string raw_token;
    };

    /// Register implemented handlers and static mounts.
    void setup() override;

    /// Return the server request path for a named dynamic route.
    std::string getPath(
        const std::string& name,
        const std::vector<std::string>& argument_names = {}) const;

    /// Return the request prefix for a named static mount.
    std::string getMountPath(const std::string& name) const;

    /// Load and authorize one browser session, writing failures to response.
    std::optional<RequestIdentity> requireIdentity(
        const Request& request,
        Response& response,
        bool safe_get,
        bool allow_onboarding = false);

    /// Verify one authenticated mutation's synchronizer token.
    bool verifyCsrf(
        const Request& request,
        const RequestIdentity& identity,
        Response& response) const;

    /// Add role-aware authenticated navigation links to template data.
    void addNavigationData(inja::json& data, const User& user) const;

    /// Render an expected mutation failure with a safe retry destination.
    void respondMutationError(
        Response& response,
        const mw::Error& error,
        std::string_view operation,
        const std::string& reload_url,
        const User& user,
        bool files_need_reselection = false);

    /// Re-render a card form after custom-field validation fails.
    void respondCardFieldError(
        Response& response,
        const GameFieldValidationError& error,
        const CardUpload& upload,
        const RequestIdentity& identity,
        const Card* card = nullptr);

    /// Re-render a game metadata form after definition validation fails.
    void respondGameDefinitionError(
        Response& response,
        const GameDefinitionValidationError& error,
        const RequestIdentity& identity,
        const std::string& short_name,
        const std::string& display_name,
        const std::string& description,
        GameVisibility visibility,
        bool editing);

    /// Re-render a field editor after definition validation fails.
    void respondGameFieldDefinitionError(
        Response& response,
        const GameDefinitionValidationError& error,
        const RequestIdentity& identity,
        const std::string& short_name,
        std::optional<std::int64_t> field_id,
        const std::string& key,
        const std::string& label,
        const std::string& type,
        const std::vector<std::string>& choices);

    /// Set or expire a host-only application cookie.
    void setCookie(
        Response& response,
        const std::string& name,
        const std::string& value,
        std::int64_t maximum_age,
        bool strict_same_site) const;

    Config config_;
    std::unique_ptr<DataSourceInterface> data_source_;
    std::unique_ptr<NonSecretRandom> random_;
    std::unique_ptr<ClockInterface> clock_;
    std::unique_ptr<mw::CryptoInterface> crypto_;
    std::unique_ptr<EmailSenderInterface> email_sender_;
    std::unique_ptr<AuthenticationService> authentication_service_;
    std::unique_ptr<UserService> user_service_;
    std::unique_ptr<CollectionService> collection_service_;
    std::unique_ptr<CardService> card_service_;
    std::unique_ptr<SeriesService> series_service_;
    std::unique_ptr<GameService> game_service_;
    UrlBuilder url_builder_;
    inja::Environment templates_;
    inja::Template card_form_template_;
    inja::Template card_delete_template_;
    inja::Template card_index_template_;
    inja::Template card_view_template_;
    inja::Template game_admin_template_;
    inja::Template series_delete_template_;
    inja::Template series_form_template_;
    inja::Template series_index_template_;
};