#include "FilesystemAbstraction.h"
#include "WebDAV.h"
#include "Logger.h"

namespace webdav_constants
{
    constexpr const char *MULTISTATUS_START = "<?xml version=\"1.0\" ?><D:multistatus xmlns:D=\"DAV:\">";
    constexpr const char *MULTISTATUS_END = "</D:multistatus>";
    constexpr const char *RESPONSE_START = "<D:response>";
    constexpr const char *RESPONSE_END = "</D:response>\n";
    constexpr const char *HREF_START = "<D:href>";
    constexpr const char *HREF_END = "</D:href>";
    constexpr const char *PROPSTAT_START = "<D:propstat>";
    constexpr const char *PROPSTAT_END = "</D:propstat>";
    constexpr const char *PROP_START = "<D:prop>";
    constexpr const char *PROP_END = "</D:prop>";
    constexpr const char *RESOURCETYPE_START = "<D:resourcetype>";
    constexpr const char *RESOURCETYPE_END = "</D:resourcetype>";
    constexpr const char *RESOURCE_COLLECTION = "<D:collection/>";
    constexpr const char *HTTP_204_NO_CONTENT = "HTTP/1.1 204 No Content";

    constexpr const char *CONTENTLEN_START = "<D:getcontentlength>";
    constexpr const char *CONTENTLEN_END = "</D:getcontentlength>";
    constexpr const char *CREATEDATE_START = "<D:creationdate>";
    constexpr const char *CREATEDATE_END = "</D:creationdate>";
    constexpr const char *MODDATE_START = "<D:getlastmodified>";
    constexpr const char *MODDATE_END = "</D:getlastmodified>";
    constexpr const char *STATUS_OK = "<D:status>HTTP/1.1 200 OK</D:status>";
    constexpr const char *USED_BYTES_START = "<D:quota-used-bytes>";
    constexpr const char *USED_BYTES_END = "</D:quota-used-bytes>";
} // namespace webdav_constants

size_t webdavFileListingSpiffs(char *buffer, size_t maxLength,
                               const char *spiffsPath)
{
    using namespace webdav_constants;

    size_t bytesWritten = 0;

    auto toBuffer = [&buffer, &bytesWritten](const char *text) {
        auto len = strlen(text);
        memcpy(buffer, text, len);
        buffer += len;
        bytesWritten += len;
    };

    // crude upper bound on how much space a file entry + footer needs
    constexpr size_t sizePerFile = 512;
    assert(maxLength >= sizePerFile);

    int fileIdx = 0;

    auto dir = portablefs::openDir(spiffsPath);

    toBuffer(MULTISTATUS_START);

    bool incomplete = false;
    while (dir.next())
    {
        const auto freeSpace = maxLength - bytesWritten;
        if (freeSpace < sizePerFile)
        {
            incomplete = true;
            break;
        }
        fileIdx += 1;
        toBuffer(RESPONSE_START);
        toBuffer(HREF_START);
        const auto fileName = dir.fileName();
        const auto fileNameWithoutDir = fileName.substring(fileName.lastIndexOf("/") + 1);
        toBuffer((fileNameWithoutDir + ".st").c_str());
        toBuffer(HREF_END);
        toBuffer(PROPSTAT_START);
        toBuffer(PROP_START);
        if (dir.isDirectory())
        {
            toBuffer(RESOURCETYPE_START);
            toBuffer(RESOURCE_COLLECTION);
            toBuffer(RESOURCETYPE_END);
        }
        else
        {
            toBuffer(CONTENTLEN_START);
            String fileSizeStr(dir.fileSize());
            toBuffer(fileSizeStr.c_str());
            toBuffer(CONTENTLEN_END);
        }

        toBuffer(PROP_END);
        toBuffer(STATUS_OK);
        toBuffer(PROPSTAT_END);
        toBuffer(webdav_constants::RESPONSE_END);
    }
    toBuffer(MULTISTATUS_END);

    if (incomplete)
        LOG_WARNING("WebDAV listing response is incomplete, because buffer was too small");
    return bytesWritten;
}

String uriToFileName(const String &uriStr, const char *spiffsFolder)
{
    String filename;
    if (uriStr.endsWith(".st"))
        filename = uriStr.substring(0, uriStr.length() - strlen(".st"));

    filename = spiffsFolder + String("/") + filename;
    return filename;
}

std::function<void(httpd_req_t *)> webdavHandler(const char *uriPrefix,
                                                 const char *spiffsFolder)
{
    return [=](httpd_req_t *req) {
        String uri = String(req->uri).substring(strlen(uriPrefix));
        constexpr size_t WEBDAV_BUFF_LEN = 1024 * 256;
        static char *webdavBuffer = (char *)heap_caps_malloc(WEBDAV_BUFF_LEN, MALLOC_CAP_SPIRAM);

        httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");

        switch (req->method)
        {
        case HTTP_GET:
        {
            auto filename = uriToFileName(uri, spiffsFolder);
            LOG_INFO("GET filename %s", filename.c_str());
            auto file = portablefs::open(filename.c_str(), "r");
            if (file.available())
            {
                uint8_t *buffer = (uint8_t *)heap_caps_malloc(file.size(), MALLOC_CAP_SPIRAM);
                file.read(buffer, file.size());
                httpd_resp_set_hdr(req, "Content-Type", "application/x-msgpack");
                httpd_resp_send(req, (char *)buffer, file.size());
                free(buffer);
            }
            else
                httpd_resp_send_404(req);
            break;
        }
        case HTTP_PROPFIND:
        {
            size_t bytesWritten = webdavFileListingSpiffs(webdavBuffer, WEBDAV_BUFF_LEN, spiffsFolder);
            httpd_resp_send(req, webdavBuffer, bytesWritten);
            break;
        }
        case HTTP_DELETE:
        {
            httpd_resp_set_hdr(req, "Content-Type", "text/plain");

            if (portablefs::remove(uriToFileName(uri, spiffsFolder).c_str()))
                httpd_resp_send(req, "Deleted file", -1);
            else
                httpd_resp_send_404(req);
            break;
        }
        case HTTP_OPTIONS:
        {
            LOG_INFO("Options request");
            httpd_resp_set_status(req, "204 No Content");
            httpd_resp_set_hdr(req, "Access-Control-Allow-Methods", "GET, PROPFIND, DELETE, OPTIONS");
            httpd_resp_send(req, "", 0);
            break;
        }
        default:
            LOG_INFO("Sending 404 %d uri %s", req->method, req->uri);
            httpd_resp_send_404(req);
        }
    };
}