using Test
using HTTP
import HTTP.MultiPartParsing: find_multipart_boundary, find_multipart_boundaries, find_header_boundary, parse_multipart_chunk, parse_multipart_body, parse_multipart_form

function generate_test_body()
    Vector{UInt8}(join([
        "----------------------------918073721150061572809433",
        "Content-Disposition: form-data; name=\"namevalue\"; filename=\"multipart.txt\"",
        "Content-Type: text/plain",
        "",
        "not much to say\n",
        "----------------------------918073721150061572809433",
        "Content-Disposition: form-data; name=\"key1\"",
        "",
        "1",
        "----------------------------918073721150061572809433",
        "Content-Disposition: form-data; name=\"key2\"",
        "",
        "key the second",
        "----------------------------918073721150061572809433",
        "Content-Disposition: form-data; name=\"namevalue2\"; filename=\"multipart-leading-newline.txt\"",
        "Content-Type: text/plain",
        "",
        "\nfile with leading newline\n",
        "----------------------------918073721150061572809433",
        "Content-Disposition: form-data; name=\"json_file1\"; filename=\"my-json-file-1.json\"",
        "Content-Type: application/json",
        "",
        "{\"data\": [\"this is json data\"]}",
        "----------------------------918073721150061572809433",
        "content-type: text/plain",
        "content-disposition: form-data; name=\"key3\"",
        "",
        "This file has lower-cased content- keys, and disposition comes second.",
        "----------------------------918073721150061572809433--",
        "",
    ], "\r\n"))
end

function generate_test_request()
    body = generate_test_body()
    
    headers = [
        "User-Agent" => "PostmanRuntime/7.15.2",
        "Accept" => "*/*",
        "Cache-Control" => "no-cache",
        "Postman-Token" => "288c2481-1837-4ba9-add3-f23d380fa440",
        "Host" => "localhost:8888",
        "Accept-Encoding" => "gzip, deflate",
        "Accept-Encoding" => "gzip, deflate",
        "Content-Type" => "multipart/form-data; boundary=--------------------------918073721150061572809433",
        "Content-Length" => string(length(body)),
        "Connection" => "keep-alive",
    ]

    HTTP.Request("POST", "/", headers, body)
end

function generate_non_multi_test_request()
    headers = [
        "User-Agent" => "PostmanRuntime/7.15.2",
        "Accept" => "*/*",
        "Cache-Control" => "no-cache",
        "Postman-Token" => "288c2481-1837-4ba9-add3-f23d380fa440",
        "Host" => "localhost:8888",
        "Accept-Encoding" => "gzip, deflate",
        "Accept-Encoding" => "gzip, deflate",
        "Content-Length" => "0",
        "Connection" => "keep-alive",
    ]

    HTTP.Request("POST", "/", headers, Vector{UInt8}())
end

function generate_test_response()
    body = generate_test_body()

    headers = [
        "Date" => "Fri, 25 Mar 2022 14:16:21 GMT",
        "Transfer-Encoding" => "chunked",
        "Content-Type" => "multipart/form-data; boundary=--------------------------918073721150061572809433",
        "Content-Length" => string(length(body)),
        "Connection" => "keep-alive",
    ]

    HTTP.Response(200, headers, body=body)
end


@testset "parse multipart form-data" begin
    @testset "find_multipart_boundary" begin
        request = generate_test_request()

        # NOTE: this is the start of a "boundary delimiter line" and has two leading
        # '-' characters prepended to the boundary delimiter from Content-Type header
        delimiter = Vector{UInt8}("--------------------------918073721150061572809433")
        body = generate_test_body()
        # length of the delimiter, CRLF, and -1 for the end index to be the LF character
        endIndexOffset = length(delimiter) + 4 - 1

        (isTerminatingDelimiter, startIndex, endIndex) = find_multipart_boundary(body, delimiter)
        @test !isTerminatingDelimiter
        @test 1 == startIndex
        @test (startIndex + endIndexOffset) == endIndex

        # the remaining "boundary delimiter lines" will have a CRLF preceding them
        endIndexOffset += 2

        (isTerminatingDelimiter, startIndex, endIndex) = find_multipart_boundary(body, delimiter, start = startIndex + 1)
        @test !isTerminatingDelimiter
        @test 175 == startIndex
        @test (startIndex + endIndexOffset) == endIndex

        (isTerminatingDelimiter, startIndex, endIndex) = find_multipart_boundary(body, delimiter, start = startIndex + 3)
        @test !isTerminatingDelimiter
        @test 279 == startIndex
        @test (startIndex + endIndexOffset) == endIndex

        (isTerminatingDelimiter, startIndex, endIndex) = find_multipart_boundary(body, delimiter, start = startIndex + 3)
        @test !isTerminatingDelimiter
        @test 396 == startIndex
        @test (startIndex + endIndexOffset) == endIndex

        (isTerminatingDelimiter, startIndex, endIndex) = find_multipart_boundary(body, delimiter, start = startIndex + 3)
        @test !isTerminatingDelimiter
        @test 600 == startIndex
        @test (startIndex + endIndexOffset) == endIndex

        (isTerminatingDelimiter, startIndex, endIndex) = find_multipart_boundary(body, delimiter, start = startIndex + 3)
        @test !isTerminatingDelimiter
        @test 804 == startIndex
        @test (startIndex + endIndexOffset) == endIndex

        (isTerminatingDelimiter, startIndex, endIndex) = find_multipart_boundary(body, delimiter, start = startIndex + 3)
        @test isTerminatingDelimiter
        @test 1003 == startIndex
        # +2 because of the two additional '--' characters
        @test (startIndex + endIndexOffset + 2) == endIndex
    end


    @testset "parse_multipart_form request" begin
        @test HTTP.parse_multipart_form(generate_non_multi_test_request()) === nothing

        multiparts = HTTP.parse_multipart_form(generate_test_request())
        @test 6 == length(multiparts)

        @test "multipart.txt" === multiparts[1].filename
        @test "namevalue" === multiparts[1].name
        @test "text/plain" === multiparts[1].contenttype
        @test "not much to say\n" === String(read(multiparts[1].data))

        @test multiparts[2].filename === nothing
        @test "key1" === multiparts[2].name
        @test "text/plain" === multiparts[2].contenttype
        @test "1" === String(read(multiparts[2].data))

        @test multiparts[3].filename === nothing
        @test "key2" === multiparts[3].name
        @test "text/plain" === multiparts[3].contenttype
        @test "key the second" === String(read(multiparts[3].data))

        @test "multipart-leading-newline.txt" === multiparts[4].filename
        @test "namevalue2" === multiparts[4].name
        @test "text/plain" === multiparts[4].contenttype
        @test "\nfile with leading newline\n" === String(read(multiparts[4].data))

        @test "my-json-file-1.json" === multiparts[5].filename
        @test "json_file1" === multiparts[5].name
        @test "application/json" === multiparts[5].contenttype
        @test """{"data": ["this is json data"]}""" === String(read(multiparts[5].data))

        @test multiparts[6].filename === nothing
        @test "key3" === multiparts[6].name
        @test "text/plain" === multiparts[6].contenttype
        @test "This file has lower-cased content- keys, and disposition comes second." === String(read(multiparts[6].data))
    end

    @testset "parse_multipart_form response" begin
        multiparts = HTTP.parse_multipart_form(generate_test_response())
        @test 6 == length(multiparts)

        @test "multipart.txt" === multiparts[1].filename
        @test "namevalue" === multiparts[1].name
        @test "text/plain" === multiparts[1].contenttype
        @test "not much to say\n" === String(read(multiparts[1].data))

        @test multiparts[2].filename === nothing
        @test "key1" === multiparts[2].name
        @test "text/plain" === multiparts[2].contenttype
        @test "1" === String(read(multiparts[2].data))

        @test multiparts[3].filename === nothing
        @test "key2" === multiparts[3].name
        @test "text/plain" === multiparts[3].contenttype
        @test "key the second" === String(read(multiparts[3].data))

        @test "multipart-leading-newline.txt" === multiparts[4].filename
        @test "namevalue2" === multiparts[4].name
        @test "text/plain" === multiparts[4].contenttype
        @test "\nfile with leading newline\n" === String(read(multiparts[4].data))

        @test "my-json-file-1.json" === multiparts[5].filename
        @test "json_file1" === multiparts[5].name
        @test "application/json" === multiparts[5].contenttype
        @test """{"data": ["this is json data"]}""" === String(read(multiparts[5].data))

        @test multiparts[6].filename === nothing
        @test "key3" === multiparts[6].name
        @test "text/plain" === multiparts[6].contenttype
        @test "This file has lower-cased content- keys, and disposition comes second." === String(read(multiparts[6].data))
    end
end
