#include "crow.h"
#include "routes.h"
#include <cstdlib>
#include <fstream>
#include <initializer_list>
#include <sstream>
#include <string>

namespace routes {

namespace {

std::string static_dir() {
    if (const char* env = std::getenv("CXWEBAPP_STATIC_DIR")) {
        return std::string(env);
    }
#ifdef CXWEBAPP_STATIC_DIR_DEFAULT
    return std::string(CXWEBAPP_STATIC_DIR_DEFAULT);
#else
    return "static";
#endif
}

std::string read_file(const std::string& path) {
    std::ifstream ifs(path, std::ios::binary);
    if (!ifs.is_open()) return "";
    std::ostringstream ss;
    ss << ifs.rdbuf();
    return ss.str();
}

std::string mime_for(const std::string& path) {
    auto ends = [&](const char* s) {
        size_t n = std::char_traits<char>::length(s);
        return path.size() >= n && path.compare(path.size() - n, n, s) == 0;
    };
    if (ends(".html")) return "text/html; charset=utf-8";
    if (ends(".css"))  return "text/css; charset=utf-8";
    if (ends(".js"))   return "application/javascript; charset=utf-8";
    if (ends(".mjs"))  return "application/javascript; charset=utf-8";
    if (ends(".worker.js")) return "application/javascript; charset=utf-8";
    if (ends(".wasm")) return "application/wasm";
    if (ends(".json")) return "application/json; charset=utf-8";
    if (ends(".data")) return "application/octet-stream";
    if (ends(".svg"))  return "image/svg+xml";
    if (ends(".png"))  return "image/png";
    if (ends(".jpg") || ends(".jpeg")) return "image/jpeg";
    if (ends(".gif"))  return "image/gif";
    if (ends(".ico"))  return "image/x-icon";
    if (ends(".woff2")) return "font/woff2";
    if (ends(".woff"))  return "font/woff";
    if (ends(".map"))  return "application/json; charset=utf-8";
    if (ends(".txt"))  return "text/plain; charset=utf-8";
    return "application/octet-stream";
}

bool is_safe(const std::string& path) {
    if (path.empty()) return false;
    if (path.front() == '/') return false;
    if (path.find("..") != std::string::npos) return false;
    if (path.find('\0') != std::string::npos) return false;
    return true;
}

bool has_segment(const std::string& path, const std::string& segment) {
    return path == segment || path.rfind(segment + "/", 0) == 0 ||
        path.find("/" + segment + "/") != std::string::npos;
}

std::string cache_for(const std::string& rel) {
    if (rel == "index.html" || rel == "service-catalog.json" || rel == "wasm/modules.json") {
        return "no-cache, max-age=0, must-revalidate";
    }
    if (has_segment(rel, "assets") || has_segment(rel, "wasm")) {
        return "public, max-age=31536000, immutable";
    }
    return "public, max-age=300";
}

std::string join_path(std::initializer_list<std::string> parts) {
    std::string joined;
    for (const auto& part : parts) {
        if (joined.empty()) {
            joined = part;
        } else {
            joined += "/" + part;
        }
    }
    return joined;
}

crow::response serve_static(const std::string& rel) {
    if (!is_safe(rel)) return crow::response(400, "bad path");
    const std::string full = static_dir() + "/" + rel;
    auto body = read_file(full);
    if (body.empty()) return crow::response(404, "not found");
    crow::response resp(body);
    resp.add_header("Content-Type", mime_for(rel));
    resp.add_header("Cache-Control", cache_for(rel));
    return resp;
}

} // anonymous namespace

void register_pages(crow::SimpleApp& app) {

    CROW_ROUTE(app, "/")
    ([]() { return serve_static("index.html"); });

    CROW_ROUTE(app, "/favicon.ico")
    ([]() { return serve_static("favicon.svg"); });

    // Crow's `<string>` matches a single segment. Register enough depth for Vite and Wasm side files.
    CROW_ROUTE(app, "/static/<string>")
    ([](const std::string& a) { return serve_static(a); });

    CROW_ROUTE(app, "/static/<string>/<string>")
    ([](const std::string& a, const std::string& b) {
        return serve_static(a + "/" + b);
    });

    CROW_ROUTE(app, "/static/<string>/<string>/<string>")
    ([](const std::string& a, const std::string& b, const std::string& c) {
        return serve_static(join_path({a, b, c}));
    });

    CROW_ROUTE(app, "/static/<string>/<string>/<string>/<string>")
    ([](const std::string& a, const std::string& b, const std::string& c, const std::string& d) {
        return serve_static(join_path({a, b, c, d}));
    });

    CROW_ROUTE(app, "/static/<string>/<string>/<string>/<string>/<string>")
    ([](const std::string& a, const std::string& b, const std::string& c, const std::string& d, const std::string& e) {
        return serve_static(join_path({a, b, c, d, e}));
    });

    CROW_ROUTE(app, "/static/<string>/<string>/<string>/<string>/<string>/<string>")
    ([](const std::string& a, const std::string& b, const std::string& c, const std::string& d, const std::string& e, const std::string& f) {
        return serve_static(join_path({a, b, c, d, e, f}));
    });
}

} // namespace routes
