//
//  UploadTests.swift
//
//  Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//

import Alamofire
import Foundation
import XCTest

class UploadFileInitializationTestCase: BaseTestCase {
    func testUploadClassMethodWithMethodURLAndFile() {
        // Given
        let urlString = "https://httpbin.org/"
        let imageURL = url(forResource: "rainbow", withExtension: "jpg")

        // When
        let request = Alamofire.upload(imageURL, to: urlString)

        // Then
        XCTAssertNotNil(request.request, "request should not be nil")
        XCTAssertEqual(request.request?.httpMethod ?? "", "POST", "request HTTP method should be POST")
        XCTAssertEqual(request.request?.url?.absoluteString, urlString, "request URL string should be equal")
        XCTAssertNil(request.response, "response should be nil")
    }

    func testUploadClassMethodWithMethodURLHeadersAndFile() {
        // Given
        let urlString = "https://httpbin.org/"
        let headers = ["Authorization": "123456"]
        let imageURL = url(forResource: "rainbow", withExtension: "jpg")

        // When
        let request = Alamofire.upload(imageURL, to: urlString, method: .post, headers: headers)

        // Then
        XCTAssertNotNil(request.request, "request should not be nil")
        XCTAssertEqual(request.request?.httpMethod ?? "", "POST", "request HTTP method should be POST")
        XCTAssertEqual(request.request?.url?.absoluteString, urlString, "request URL string should be equal")

        let authorizationHeader = request.request?.value(forHTTPHeaderField: "Authorization") ?? ""
        XCTAssertEqual(authorizationHeader, "123456", "Authorization header is incorrect")

        XCTAssertNil(request.response, "response should be nil")
    }
}

// MARK: -

class UploadDataInitializationTestCase: BaseTestCase {
    func testUploadClassMethodWithMethodURLAndData() {
        // Given
        let urlString = "https://httpbin.org/"

        // When
        let request = Alamofire.upload(Data(), to: urlString)

        // Then
        XCTAssertNotNil(request.request, "request should not be nil")
        XCTAssertEqual(request.request?.httpMethod ?? "", "POST", "request HTTP method should be POST")
        XCTAssertEqual(request.request?.url?.absoluteString, urlString, "request URL string should be equal")
        XCTAssertNil(request.response, "response should be nil")
    }

    func testUploadClassMethodWithMethodURLHeadersAndData() {
        // Given
        let urlString = "https://httpbin.org/"
        let headers = ["Authorization": "123456"]

        // When
        let request = Alamofire.upload(Data(), to: urlString, headers: headers)

        // Then
        XCTAssertNotNil(request.request, "request should not be nil")
        XCTAssertEqual(request.request?.httpMethod ?? "", "POST", "request HTTP method should be POST")
        XCTAssertEqual(request.request?.url?.absoluteString, urlString, "request URL string should be equal")

        let authorizationHeader = request.request?.value(forHTTPHeaderField: "Authorization") ?? ""
        XCTAssertEqual(authorizationHeader, "123456", "Authorization header is incorrect")

        XCTAssertNil(request.response, "response should be nil")
    }
}

// MARK: -

class UploadStreamInitializationTestCase: BaseTestCase {
    func testUploadClassMethodWithMethodURLAndStream() {
        // Given
        let urlString = "https://httpbin.org/"
        let imageURL = url(forResource: "rainbow", withExtension: "jpg")
        let imageStream = InputStream(url: imageURL)!

        // When
        let request = Alamofire.upload(imageStream, to: urlString)

        // Then
        XCTAssertNotNil(request.request, "request should not be nil")
        XCTAssertEqual(request.request?.httpMethod ?? "", "POST", "request HTTP method should be POST")
        XCTAssertEqual(request.request?.url?.absoluteString, urlString, "request URL string should be equal")
        XCTAssertNil(request.response, "response should be nil")
    }

    func testUploadClassMethodWithMethodURLHeadersAndStream() {
        // Given
        let urlString = "https://httpbin.org/"
        let imageURL = url(forResource: "rainbow", withExtension: "jpg")
        let headers = ["Authorization": "123456"]
        let imageStream = InputStream(url: imageURL)!

        // When
        let request = Alamofire.upload(imageStream, to: urlString, headers: headers)

        // Then
        XCTAssertNotNil(request.request, "request should not be nil")
        XCTAssertEqual(request.request?.httpMethod ?? "", "POST", "request HTTP method should be POST")
        XCTAssertEqual(request.request?.url?.absoluteString, urlString, "request URL string should be equal")

        let authorizationHeader = request.request?.value(forHTTPHeaderField: "Authorization") ?? ""
        XCTAssertEqual(authorizationHeader, "123456", "Authorization header is incorrect")

        XCTAssertNil(request.response, "response should be nil")
    }
}

// MARK: -

class UploadDataTestCase: BaseTestCase {
    func testUploadDataRequest() {
        // Given
        let urlString = "https://httpbin.org/post"
        let data = "Lorem ipsum dolor sit amet".data(using: .utf8, allowLossyConversion: false)!

        let expectation = self.expectation(description: "Upload request should succeed: \(urlString)")
        var response: DefaultDataResponse?

        // When
        Alamofire.upload(data, to: urlString)
            .response { resp in
                response = resp
                expectation.fulfill()
            }

        waitForExpectations(timeout: timeout, handler: nil)

        // Then
        XCTAssertNotNil(response?.request)
        XCTAssertNotNil(response?.response)
        XCTAssertNil(response?.error)
    }

    func testUploadDataRequestWithProgress() {
        // Given
        let urlString = "https://httpbin.org/post"
        let data: Data = {
            var text = ""
            for _ in 1...3_000 {
                text += "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
            }

            return text.data(using: .utf8, allowLossyConversion: false)!
        }()

        let expectation = self.expectation(description: "Bytes upload progress should be reported: \(urlString)")

        var uploadProgressValues: [Double] = []
        var downloadProgressValues: [Double] = []

        var response: DefaultDataResponse?

        // When
        Alamofire.upload(data, to: urlString)
            .uploadProgress { progress in
                uploadProgressValues.append(progress.fractionCompleted)
            }
            .downloadProgress { progress in
                downloadProgressValues.append(progress.fractionCompleted)
            }
            .response { resp in
                response = resp
                expectation.fulfill()
            }

        waitForExpectations(timeout: timeout, handler: nil)

        // Then
        XCTAssertNotNil(response?.request)
        XCTAssertNotNil(response?.response)
        XCTAssertNotNil(response?.data)
        XCTAssertNil(response?.error)

        var previousUploadProgress: Double = uploadProgressValues.first ?? 0.0

        for progress in uploadProgressValues {
            XCTAssertGreaterThanOrEqual(progress, previousUploadProgress)
            previousUploadProgress = progress
        }

        if let lastProgressValue = uploadProgressValues.last {
            XCTAssertEqual(lastProgressValue, 1.0)
        } else {
            XCTFail("last item in uploadProgressValues should not be nil")
        }

        var previousDownloadProgress: Double = downloadProgressValues.first ?? 0.0

        for progress in downloadProgressValues {
            XCTAssertGreaterThanOrEqual(progress, previousDownloadProgress)
            previousDownloadProgress = progress
        }

        if let lastProgressValue = downloadProgressValues.last {
            XCTAssertEqual(lastProgressValue, 1.0)
        } else {
            XCTFail("last item in downloadProgressValues should not be nil")
        }
    }
}

// MARK: -

class UploadMultipartFormDataTestCase: BaseTestCase {

    // MARK: Tests

    func testThatUploadingMultipartFormDataSetsContentTypeHeader() {
        // Given
        let urlString = "https://httpbin.org/post"
        let uploadData = "upload_data".data(using: .utf8, allowLossyConversion: false)!

        let expectation = self.expectation(description: "multipart form data upload should succeed")

        var formData: MultipartFormData?
        var response: DefaultDataResponse?

        // When
        Alamofire.upload(
            multipartFormData: { multipartFormData in
                multipartFormData.append(uploadData, withName: "upload_data")
                formData = multipartFormData
            },
            to: urlString,
            encodingCompletion: { result in
                switch result {
                case .success(let upload, _, _):
                    upload.response { resp in
                        response = resp
                        expectation.fulfill()
                    }
                case .failure:
                    expectation.fulfill()
                }
            }
        )

        waitForExpectations(timeout: timeout, handler: nil)

        // Then
        XCTAssertNotNil(response?.request)
        XCTAssertNotNil(response?.response)
        XCTAssertNotNil(response?.data)
        XCTAssertNil(response?.error)

        if
            let request = response?.request,
            let multipartFormData = formData,
            let contentType = request.value(forHTTPHeaderField: "Content-Type")
        {
            XCTAssertEqual(contentType, multipartFormData.contentType)
        } else {
            XCTFail("Content-Type header value should not be nil")
        }
    }

    func testThatUploadingMultipartFormDataSucceedsWithDefaultParameters() {
        // Given
        let urlString = "https://httpbin.org/post"
        let frenchData = "français".data(using: .utf8, allowLossyConversion: false)!
        let japaneseData = "日本語".data(using: .utf8, allowLossyConversion: false)!

        let expectation = self.expectation(description: "multipart form data upload should succeed")
        var response: DefaultDataResponse?

        // When
        Alamofire.upload(
            multipartFormData: { multipartFormData in
                multipartFormData.append(frenchData, withName: "french")
                multipartFormData.append(japaneseData, withName: "japanese")
            },
            to: urlString,
            encodingCompletion: { result in
                switch result {
                case .success(let upload, _, _):
                    upload.response { resp in
                        response = resp
                        expectation.fulfill()
                    }
                case .failure:
                    expectation.fulfill()
                }
            }
        )

        waitForExpectations(timeout: timeout, handler: nil)

        // Then
        XCTAssertNotNil(response?.request)
        XCTAssertNotNil(response?.response)
        XCTAssertNotNil(response?.data)
        XCTAssertNil(response?.error)
    }

    func testThatUploadingMultipartFormDataWhileStreamingFromMemoryMonitorsProgress() {
        executeMultipartFormDataUploadRequestWithProgress(streamFromDisk: false)
    }

    func testThatUploadingMultipartFormDataWhileStreamingFromDiskMonitorsProgress() {
        executeMultipartFormDataUploadRequestWithProgress(streamFromDisk: true)
    }

    func testThatUploadingMultipartFormDataBelowMemoryThresholdStreamsFromMemory() {
        // Given
        let urlString = "https://httpbin.org/post"
        let frenchData = "français".data(using: .utf8, allowLossyConversion: false)!
        let japaneseData = "日本語".data(using: .utf8, allowLossyConversion: false)!

        let expectation = self.expectation(description: "multipart form data upload should succeed")

        var streamingFromDisk: Bool?
        var streamFileURL: URL?

        // When
        Alamofire.upload(
            multipartFormData: { multipartFormData in
                multipartFormData.append(frenchData, withName: "french")
                multipartFormData.append(japaneseData, withName: "japanese")
            },
            to: urlString,
            encodingCompletion: { result in
                switch result {
                case let .success(upload, uploadStreamingFromDisk, uploadStreamFileURL):
                    streamingFromDisk = uploadStreamingFromDisk
                    streamFileURL = uploadStreamFileURL

                    upload.response { _ in
                        expectation.fulfill()
                    }
                case .failure:
                    expectation.fulfill()
                }
            }
        )

        waitForExpectations(timeout: timeout, handler: nil)

        // Then
        XCTAssertNotNil(streamingFromDisk, "streaming from disk should not be nil")
        XCTAssertNil(streamFileURL, "stream file URL should be nil")

        if let streamingFromDisk = streamingFromDisk {
            XCTAssertFalse(streamingFromDisk, "streaming from disk should be false")
        }
    }

    func testThatUploadingMultipartFormDataBelowMemoryThresholdSetsContentTypeHeader() {
        // Given
        let urlString = "https://httpbin.org/post"
        let uploadData = "upload data".data(using: .utf8, allowLossyConversion: false)!

        let expectation = self.expectation(description: "multipart form data upload should succeed")

        var formData: MultipartFormData?
        var request: URLRequest?
        var streamingFromDisk: Bool?

        // When
        Alamofire.upload(
            multipartFormData: { multipartFormData in
                multipartFormData.append(uploadData, withName: "upload_data")
                formData = multipartFormData
            },
            to: urlString,
            encodingCompletion: { result in
                switch result {
                case let .success(upload, uploadStreamingFromDisk, _):
                    streamingFromDisk = uploadStreamingFromDisk

                    upload.response { resp in
                        request = resp.request
                        expectation.fulfill()
                    }
                case .failure:
                    expectation.fulfill()
                }
            }
        )

        waitForExpectations(timeout: timeout, handler: nil)

        // Then
        XCTAssertNotNil(streamingFromDisk, "streaming from disk should not be nil")

        if let streamingFromDisk = streamingFromDisk {
            XCTAssertFalse(streamingFromDisk, "streaming from disk should be false")
        }

        if
            let request = request,
            let multipartFormData = formData,
            let contentType = request.value(forHTTPHeaderField: "Content-Type")
        {
            XCTAssertEqual(contentType, multipartFormData.contentType, "Content-Type header value should match")
        } else {
            XCTFail("Content-Type header value should not be nil")
        }
    }

    func testThatUploadingMultipartFormDataAboveMemoryThresholdStreamsFromDisk() {
        // Given
        let urlString = "https://httpbin.org/post"
        let frenchData = "français".data(using: .utf8, allowLossyConversion: false)!
        let japaneseData = "日本語".data(using: .utf8, allowLossyConversion: false)!

        let expectation = self.expectation(description: "multipart form data upload should succeed")

        var streamingFromDisk: Bool?
        var streamFileURL: URL?

        // When
        Alamofire.upload(
            multipartFormData: { multipartFormData in
                multipartFormData.append(frenchData, withName: "french")
                multipartFormData.append(japaneseData, withName: "japanese")
            },
            usingThreshold: 0,
            to: urlString,
            encodingCompletion: { result in
                switch result {
                case let .success(upload, uploadStreamingFromDisk, uploadStreamFileURL):
                    streamingFromDisk = uploadStreamingFromDisk
                    streamFileURL = uploadStreamFileURL

                    upload.response { _ in
                        expectation.fulfill()
                    }
                case .failure:
                    expectation.fulfill()
                }
            }
        )

        waitForExpectations(timeout: timeout, handler: nil)

        // Then
        XCTAssertNotNil(streamingFromDisk, "streaming from disk should not be nil")
        XCTAssertNotNil(streamFileURL, "stream file URL should not be nil")

        if let streamingFromDisk = streamingFromDisk, let streamFilePath = streamFileURL?.path {
            XCTAssertTrue(streamingFromDisk, "streaming from disk should be true")
            XCTAssertFalse(FileManager.default.fileExists(atPath: streamFilePath), "stream file path should not exist")
        }
    }

    func testThatUploadingMultipartFormDataAboveMemoryThresholdSetsContentTypeHeader() {
        // Given
        let urlString = "https://httpbin.org/post"
        let uploadData = "upload data".data(using: .utf8, allowLossyConversion: false)!

        let expectation = self.expectation(description: "multipart form data upload should succeed")

        var formData: MultipartFormData?
        var request: URLRequest?
        var streamingFromDisk: Bool?

        // When
        Alamofire.upload(
            multipartFormData: { multipartFormData in
                multipartFormData.append(uploadData, withName: "upload_data")
                formData = multipartFormData
            },
            usingThreshold: 0,
            to: urlString,
            encodingCompletion: { result in
                switch result {
                case let .success(upload, uploadStreamingFromDisk, _):
                    streamingFromDisk = uploadStreamingFromDisk

                    upload.response { resp in
                        request = resp.request
                        expectation.fulfill()
                    }
                case .failure:
                    expectation.fulfill()
                }
            }
        )

        waitForExpectations(timeout: timeout, handler: nil)

        // Then
        XCTAssertNotNil(streamingFromDisk, "streaming from disk should not be nil")

        if let streamingFromDisk = streamingFromDisk {
            XCTAssertTrue(streamingFromDisk, "streaming from disk should be true")
        }

        if
            let request = request,
            let multipartFormData = formData,
            let contentType = request.value(forHTTPHeaderField: "Content-Type")
        {
            XCTAssertEqual(contentType, multipartFormData.contentType, "Content-Type header value should match")
        } else {
            XCTFail("Content-Type header value should not be nil")
        }
    }

#if os(macOS)
    func testThatUploadingMultipartFormDataOnBackgroundSessionWritesDataToFileToAvoidCrash() {
        // Given
        let manager: SessionManager = {
            let identifier = "org.alamofire.uploadtests.\(UUID().uuidString)"
            let configuration = URLSessionConfiguration.background(withIdentifier: identifier)

            return SessionManager(configuration: configuration, serverTrustPolicyManager: nil)
        }()

        let urlString = "https://httpbin.org/post"
        let french = "français".data(using: .utf8, allowLossyConversion: false)!
        let japanese = "日本語".data(using: .utf8, allowLossyConversion: false)!

        let expectation = self.expectation(description: "multipart form data upload should succeed")

        var request: URLRequest?
        var response: HTTPURLResponse?
        var data: Data?
        var error: Error?
        var streamingFromDisk: Bool?

        // When
        manager.upload(
            multipartFormData: { multipartFormData in
                multipartFormData.append(french, withName: "french")
                multipartFormData.append(japanese, withName: "japanese")
            },
            to: urlString,
            encodingCompletion: { result in
                switch result {
                case let .success(upload, uploadStreamingFromDisk, _):
                    streamingFromDisk = uploadStreamingFromDisk

                    upload.response { defaultResponse in
                        request = defaultResponse.request
                        response = defaultResponse.response
                        data = defaultResponse.data
                        error = defaultResponse.error

                        expectation.fulfill()
                    }
                case .failure:
                    expectation.fulfill()
                }
            }
        )

        waitForExpectations(timeout: timeout, handler: nil)

        // Then
        XCTAssertNotNil(request, "request should not be nil")
        XCTAssertNotNil(response, "response should not be nil")
        XCTAssertNotNil(data, "data should not be nil")
        XCTAssertNil(error, "error should be nil")

        if let streamingFromDisk = streamingFromDisk {
            XCTAssertTrue(streamingFromDisk, "streaming from disk should be true")
        } else {
            XCTFail("streaming from disk should not be nil")
        }
    }
#endif

    // MARK: Combined Test Execution

    private func executeMultipartFormDataUploadRequestWithProgress(streamFromDisk: Bool) {
        // Given
        let urlString = "https://httpbin.org/post"
        let loremData1: Data = {
            var loremValues: [String] = []
            for _ in 1...1_500 {
                loremValues.append("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
            }

            return loremValues.joined(separator: " ").data(using: .utf8, allowLossyConversion: false)!
        }()
        let loremData2: Data = {
            var loremValues: [String] = []
            for _ in 1...1_500 {
                loremValues.append("Lorem ipsum dolor sit amet, nam no graeco recusabo appellantur.")
            }

            return loremValues.joined(separator: " ").data(using: .utf8, allowLossyConversion: false)!
        }()

        let expectation = self.expectation(description: "multipart form data upload should succeed")

        var uploadProgressValues: [Double] = []
        var downloadProgressValues: [Double] = []

        var response: DefaultDataResponse?

        // When
        Alamofire.upload(
            multipartFormData: { multipartFormData in
                multipartFormData.append(loremData1, withName: "lorem1")
                multipartFormData.append(loremData2, withName: "lorem2")
            },
            usingThreshold: streamFromDisk ? 0 : 100_000_000,
            to: urlString,
            encodingCompletion: { result in
                switch result {
                case .success(let upload, _, _):
                    upload
                        .uploadProgress { progress in
                            uploadProgressValues.append(progress.fractionCompleted)
                        }
                        .downloadProgress { progress in
                            downloadProgressValues.append(progress.fractionCompleted)
                        }
                        .response { resp in
                            response = resp
                            expectation.fulfill()
                        }
                case .failure:
                    expectation.fulfill()
                }
            }
        )

        waitForExpectations(timeout: timeout, handler: nil)

        // Then
        XCTAssertNotNil(response?.request)
        XCTAssertNotNil(response?.response)
        XCTAssertNotNil(response?.data)
        XCTAssertNil(response?.error)

        var previousUploadProgress: Double = uploadProgressValues.first ?? 0.0

        for progress in uploadProgressValues {
            XCTAssertGreaterThanOrEqual(progress, previousUploadProgress)
            previousUploadProgress = progress
        }

        if let lastProgressValue = uploadProgressValues.last {
            XCTAssertEqual(lastProgressValue, 1.0)
        } else {
            XCTFail("last item in uploadProgressValues should not be nil")
        }

        var previousDownloadProgress: Double = downloadProgressValues.first ?? 0.0

        for progress in downloadProgressValues {
            XCTAssertGreaterThanOrEqual(progress, previousDownloadProgress)
            previousDownloadProgress = progress
        }

        if let lastProgressValue = downloadProgressValues.last {
            XCTAssertEqual(lastProgressValue, 1.0)
        } else {
            XCTFail("last item in downloadProgressValues should not be nil")
        }
    }
}
