//
//  CloudConvert.swift
//  Padel Tournament
//
//  Created by Razmig Sarkissian on 14/09/2023.
//

import Foundation

class CloudConvert {
    
    enum CloudConvertionError: LocalizedError {
        case unknownError
        case serviceError(ErrorResponse)
        case urlNotFound(String)
        
        var errorDescription: String? {
            switch self {
            case .unknownError:
                return "Erreur"
            case .serviceError(let errorResponse):
                return errorResponse.error
            case .urlNotFound(let url):
                return "L'URL [\(url)] n'est pas valide"
            }
        }
    }
    
    static let manager = CloudConvert()
    
    func uploadFile(_ url: URL) async throws -> String {
        let taskResponse = try await createJob(url)
        let uploadResponse = try await uploadFile(taskResponse, url: url)
        var fileReady = false
        while fileReady == false {
            try await Task.sleep(nanoseconds: 3_000_000_000)
            let progressResponse = try await checkFile(id: uploadResponse.data.id)
            if progressResponse.data.step == "finish" && progressResponse.data.stepPercent == 100 {
                fileReady = true
                print("progressResponse.data.minutes", progressResponse.data.minutes)
            }
        }
        
        let convertedFile = try await downloadConvertedFile(id: uploadResponse.data.id)
        return convertedFile
    }
    
    func createJob(_ url: URL) async throws -> TaskResponse {
        guard let taskURL = URL(string: "https://api.convertio.co/convert") else {
            throw CloudConvertionError.urlNotFound("https://api.convertio.co/convert")
        }
        var request: URLRequest = URLRequest(url: taskURL)
        let parameters = """
        {"apikey":"d97cf13ef6d163e5e386c381fc8d256f","input":"upload","file":"","filename":"","outputformat":"csv","options":""}
        """
        
        
        let postData = parameters.data(using: .utf8)
        request.httpMethod = "POST"
        request.httpBody = postData
        
        let task = try await URLSession.shared.data(for: request)
        //print("tried: \(request.url)")
        if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: task.0) {
            print("errorResponse.error", errorResponse.error)
            throw CloudConvertionError.serviceError(errorResponse)
        }

        return try JSONDecoder().decode(TaskResponse.self, from: task.0)
    }
        
    func uploadFile(_ response: TaskResponse, url: URL) async throws -> UploadResponse {
        guard let uploadTaskURL = URL(string: "https://api.convertio.co/convert/\(response.data.id)/\(url.encodedLastPathComponent)") else {
            throw CloudConvertionError.urlNotFound("https://api.convertio.co/convert/\(response.data.id)/\(url.encodedLastPathComponent)")
        }
        var uploadRequest: URLRequest = URLRequest(url: uploadTaskURL)
        uploadRequest.httpMethod = "PUT"
        let uploadTask = try await URLSession.shared.upload(for: uploadRequest, fromFile: url)
        
        //print("tried: \(uploadRequest.url)")
        if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: uploadTask.0) {
            print("errorResponse.error", errorResponse.error)
            throw CloudConvertionError.serviceError(errorResponse)
        }

        return try JSONDecoder().decode(UploadResponse.self, from: uploadTask.0)
    }
    
    func checkFile(id: String) async throws -> ProgressResponse {
        guard let taskURL = URL(string: "https://api.convertio.co/convert/\(id)/status") else {
            throw CloudConvertionError.urlNotFound("https://api.convertio.co/convert/\(id)/status")
        }
        var request: URLRequest = URLRequest(url: taskURL)
        request.httpMethod = "GET"
        let task = try await URLSession.shared.data(for: request)
        
        //print("tried: \(request.url)")
        if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: task.0) {
            print("errorResponse.error", errorResponse.error)
            throw CloudConvertionError.serviceError(errorResponse)
        }

        return try JSONDecoder().decode(ProgressResponse.self, from: task.0)
    }

    func downloadConvertedFile(id: String) async throws -> String {
//        try await Task.sleep(nanoseconds: 3_000_000_000)

        guard let downloadTaskURL = URL(string: "https://api.convertio.co/convert/\(id)/dl/base64") else {
            throw CloudConvertionError.urlNotFound("https://api.convertio.co/convert/\(id)/dl/base64")
        }
        var downloadRequest: URLRequest = URLRequest(url: downloadTaskURL)
        downloadRequest.httpMethod = "GET"
        let downloadTask = try await URLSession.shared.data(for: downloadRequest)
        if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: downloadTask.0) {
            print("errorResponse.error", errorResponse.error)
            throw CloudConvertionError.serviceError(errorResponse)
        }

        //print("tried: \(downloadRequest.url)")
        let dataResponse = try JSONDecoder().decode(DataResponse.self, from: downloadTask.0)
        if let decodedData = Data(base64Encoded: dataResponse.data.content), let string = String(data: decodedData, encoding: .utf8) {
            return string
        }
        
        throw CloudConvertionError.unknownError
    }
}

// MARK: - DataResponse
struct DataResponse: Decodable {
    let code: Int
    let status: String
    let data: DataDownloadClass
}

// MARK: - DataClass
struct DataDownloadClass: Decodable {
    let id, encode, content: String
}


// MARK: - ErrorResponse
struct ErrorResponse: Decodable {
    let code: Int
    let status, error: String
}


// MARK: - TaskResponse
struct TaskResponse: Decodable {
    let code: Int
    let status: String
    let data: DataClass
}

// MARK: - DataClass
struct DataClass: Decodable {
    let id: String
}

// MARK: - ProgressResponse
struct ProgressResponse: Decodable {
    let code: Int
    let status: String
    let data: ProgressDataClass
}

// MARK: - DataClass
struct ProgressDataClass: Decodable {
    let id, step: String
    let stepPercent: Int
    let minutes: String
    
    enum CodingKeys: String, CodingKey {
        case id, step
        case stepPercent = "step_percent"
        case minutes
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        step = try container.decode(String.self, forKey: .step)
        minutes = try container.decode(String.self, forKey: .minutes)
        if let value = try? container.decode(String.self, forKey: .stepPercent) {
            print(value)
            stepPercent = Int(value) ?? 0
        } else {
            stepPercent = try container.decode(Int.self, forKey: .stepPercent)
        }
    }

}

// MARK: - Output
struct Output: Decodable {
    let url: String
    let size: String
}

// MARK: - UploadResponse
struct UploadResponse: Decodable {
    let code: Int
    let status: String
    let data: UploadDataClass
}

// MARK: - DataClass
struct UploadDataClass: Decodable {
    let id, file: String
    let size: Int
}

extension URL {
    var encodedLastPathComponent: String {
        if #available(iOS 17.0, *) {
            lastPathComponent
        } else {
            lastPathComponent.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? lastPathComponent
        }

    }
}
