#include <cstdio>
#include <httplib/httplib.h>
#include <nlohmann/json.hpp>

#include "config.h"
#include "pixivclient.h"
#include "servehelper.h"
#include "routes/routes.h"

int main(int argc, char** argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <path/to/config/file.json>\n", argc > 0 ? argv[0] : "pixwhile");
        return 1;
    }

    Config config;
    try {
        config = load_config(argv[1]);
    } catch (const std::exception& e) {
        fprintf(stderr, "Failed to load config: %s\n", e.what());
        return 1;
    }

    PixivClient pixiv_client;
    httplib::Server server;
    server.Get("/", [&](const httplib::Request& req, httplib::Response& res) {
        home_route(req, res, config);
    });
    server.Get("/style\\.css", css_route);

    server.Get("/users/(\\d+)", [&](const httplib::Request& req, httplib::Response& res) {
        serve_redirect(req, res, config, get_origin(req, config) + "/users/" + req.matches.str(1) + "/illustrations");
    });
    server.Get("/users/(\\d+)/illustrations", [&](const httplib::Request& req, httplib::Response& res) {
        user_illustrations_route(req, res, config, pixiv_client);
    });
    server.Get("/artworks/(\\d+)", [&](const httplib::Request& req, httplib::Response& res) {
        artworks_route(req, res, config, pixiv_client);
    });
    server.Get("/tags/([^/]+)", [&](const httplib::Request& req, httplib::Response& res) {
        serve_redirect(req, res, config, get_origin(req, config) + "/tags/" + req.matches.str(1) + "/illustrations");
    });
    server.Get("/tags/([^/]+)/illustrations", [&](const httplib::Request& req, httplib::Response& res) {
        tags_route(req, res, config, pixiv_client);
    });

    server.Get("/member\\.php", [&](const httplib::Request& req, httplib::Response& res) {
        std::string id = req.get_param_value("id");
        if (id.empty() || id.find_first_not_of("1234567890") != std::string::npos) {
            res.status = 400;
            serve_error(req, res, config, "400: Bad Request", "Missing or invalid user ID");
            return;
        }
        serve_redirect(req, res, config, get_origin(req, config) + "/users/" + id);
    });
    server.Get("/member_illust\\.php", [&](const httplib::Request& req, httplib::Response& res) {
        std::string illust_id = req.get_param_value("illust_id");
        if (illust_id.empty() || illust_id.find_first_not_of("1234567890") != std::string::npos) {
            res.status = 400;
            serve_error(req, res, config, "400: Bad Request", "Missing or invalid illust ID");
            return;
        }
        serve_redirect(req, res, config, get_origin(req, config) + "/artworks/" + illust_id);
    });
    server.Get("/search", [&](const httplib::Request& req, httplib::Response& res) {
        std::string q = req.get_param_value("q");
        if (q.empty()) {
            res.status = 400;
            serve_error(req, res, config, "400: Bad Request", "Missing or empty search query");
            return;
        }
        serve_redirect(req, res, config, get_origin(req, config) + "/tags/" + blankie::murl::escape(std::move(q)));
    });

#ifndef NDEBUG
    server.Get("/debug/exception/known", [](const httplib::Request& req, httplib::Response& res) {
        throw std::runtime_error("awoo");
    });
    server.Get("/debug/exception/unknown", [](const httplib::Request& req, httplib::Response& res) {
        throw "cope";
    });
#endif
    server.Get(".*", [&](const httplib::Request& req, httplib::Response& res) {
        res.status = 404;
        serve_error(req, res, config, "404: Page not found");
    });
    server.set_exception_handler([&](const httplib::Request& req, httplib::Response& res, std::exception_ptr ep) {
        res.status = 500;
        try {
            std::rethrow_exception(ep);
        } catch (const std::exception& e) {
            fprintf(stderr, "Exception thrown on %s: %s\n", req.path.c_str(), e.what());
            serve_error(req, res, config, "500: Internal server error", std::nullopt, e.what());
        } catch (...) {
            fprintf(stderr, "Exception thrown on %s: Unknown exception\n", req.path.c_str());
            serve_error(req, res, config, "500: Internal server error", std::nullopt, "Unknown exception");
        }
    });

    if (config.bind_port != 0) {
        if (!server.bind_to_port(config.bind_host, config.bind_port)) {
            fprintf(stderr, "Failed to bind to %s:%d\n", config.bind_host.c_str(), config.bind_port);
            return 1;
        }
    } else {
        int port = server.bind_to_any_port(config.bind_host);
        if (port == -1) {
            fprintf(stderr, "Failed to bind to %s:<any>\n", config.bind_host.c_str());
            return 1;
        }
        config.bind_port = port;
    }
    printf("Listening on %s:%d...\n", config.bind_host.c_str(), config.bind_port);

    if (!server.listen_after_bind()) {
        fprintf(stderr, "Failed to listen on %s:%d\n", config.bind_host.c_str(), config.bind_port);
        return 1;
    }
}