module util.data;

import handy_http_primitives;
import handy_http_data.multipart;
import std.typecons;

Optional!T toOptional(T)(Nullable!T value) {
    if (value.isNull) {
        return Optional!T.empty;
    } else {
        return Optional!T.of(value.get);
    }
}

Nullable!T toNullable(T)(Optional!T value) {
    if (value.isNull) {
        return Nullable!(T)();
    } else {
        return Nullable!T(value.value);
    }
}

auto serializeOptional(T)(Optional!T value) {
    if (value.isNull) {
        return Nullable!T();
    }
    return Nullable!T(value.value);
}

T getPathParamOrThrow(T)(in ServerHttpRequest req, string name) {
    import handy_http_handlers.path_handler;
    import std.conv : to, ConvException;
    foreach (param; getPathParams(req)) {
        if (param.name == name) {
            try {
                return to!T(param.value);
            } catch (ConvException e) {
                // Skip and throw if no params match.
            }
        }
    }
    // No params matched, so throw a NOT FOUND error.
    throw new HttpStatusException(HttpStatus.NOT_FOUND, "Missing required path parameter \"" ~ name ~ "\".");
}

struct MultipartFile {
    string name;
    string contentType;
    ubyte[] content;
}

struct MultipartFilesAndBody(T) {
    T payload;
    MultipartFile[] files;
}

MultipartFilesAndBody!T parseMultipartFilesAndBody(T)(
    ref ServerHttpRequest request,
    string payloadPartName = "payload",
    string filesPartName = "file"
) {
    import asdf : deserialize, SerdeException;
    import std.uni : toLower;
    import std.array;

    MultipartFormData formData;
    try {
        formData = readBodyAsMultipartFormData(request);
    } catch (MultipartFormatException e) {
        throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Invalid multipart form data.");
    }

    MultipartFilesAndBody!T result;
    RefAppender!(MultipartFile[]) app = appender(&result.files);
    foreach (element; formData.elements) {
        if (toLower(element.name) == payloadPartName) {
            try {
                result.payload = deserialize!T(element.content);
            } catch (SerdeException e) {
                throw new HttpStatusException(HttpStatus.BAD_REQUEST, e.msg);
            }
        } else if (toLower(element.name) == filesPartName) {
            app ~= parseMultipartFile(element);
        }
    }
    return result;
}

private MultipartFile parseMultipartFile(in MultipartElement e) {
    import std.algorithm : splitter, count;
    import std.array;
    import std.string : strip;
    const requiredHeaders = ["Content-Disposition", "Content-Type"];
    static foreach (headerName; requiredHeaders) {
        if (headerName !in e.headers || e.headers[headerName].length < 1) {
            throw new HttpStatusException(
                HttpStatus.BAD_REQUEST,
                "Missing required \"" ~ headerName ~ "\" header for multipart file."
            );
        }
    }
    if ("Content-Disposition" !in e.headers) {
        throw new HttpStatusException(
            HttpStatus.BAD_REQUEST,
            "Missing required \"Content-Disposition\" header for multipart file."
        );
    }
    string filename;
    foreach (part; e.headers["Content-Disposition"].splitter(";")) {
        string[] partSegments = part.splitter("=").array;
        if (count(partSegments) == 2 && partSegments[0].strip() == "filename") {
            filename = partSegments[1].strip().strip("\"");
            break;
        }
    }
    if (filename is null || filename.length < 1) {
        throw new HttpStatusException(
            HttpStatus.BAD_REQUEST,
            "Missing filename for multipart file."
        );
    }
    return MultipartFile(
        filename,
        e.headers["Content-Type"],
        cast(ubyte[]) e.content
    );
}
