networkmanager

mail@pastecode.io avatar
unknown
swift
2 years ago
105 kB
1
Indexable
Never
//
//  NetworkManager.swift
//  eToto
//
//  Created by Hristo Hristov on 4.09.20.
//  Copyright © 2020 Mobile Wave Solutions. All rights reserved.
//

import UIKit

enum SBBErrorCodes: Int {
    case LogoutUser = 421
    case SingleSlipToAcceptForTrader = 30
    case MultiSlipToAcceptForTrader = 31
}

extension Notification.Name {
    static let authenticate = Self.init("authenticate")
}

class NetworkManager: NSObject, NetworkManagerType {

    private let cache  = ImageCache()
    static var shared = NetworkManager()
    private var session: URLSession { URLSession.shared }
    private lazy var temporarySession = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
    private lazy var logInCompletions: [GetUserCompletion] = []
    private(set) lazy var user = Observer<User?>(nil)
    private(set) lazy var fastBettingImage = Observer<UIImage?>(nil)
    private(set) var lastUser: User? {
        didSet {
            Storage.etoto.userID = lastUser?.id
        }
    }
    private var keepAliveTimer: Timer?

    private var updateBalanceTimer: Timer?
    private var updateEventsCacheTimer: Timer?
    private var footballEventsCache: [Event]?
    private var refreshChatOnChangeTask: URLSessionDataTask?
    private var refreshChatTask: URLSessionDataTask?
    private(set) var sessionID: String? //TODO: Can I create user property during registration process?

    private override init() {
        super.init()
        NotificationCenter.default.addObserver(self, selector: #selector(sentEvent(_:)),
                                               name: .sentEvent, object: UIApplication.shared)
        
        NotificationCenter.default.addObserver(self, selector: #selector(invalidateFootballCache),
                                               name: UIApplication.willResignActiveNotification, object: nil)
        
        startUpdateEventsCacheTimer()
        
        user.observe { [weak self] (new, _) in
            self?.startKeepAliveTimer()
            self?.startUpdateBalanceTimer()
            if new != nil { self?.lastUser = new }
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    @objc private func sentEvent(_ notification: Notification) {
        guard user.value != nil else {
            return
        }
        startKeepAliveTimer()
    }

    func invalidateTimers() {
        if updateEventsCacheTimer != nil {
            updateEventsCacheTimer?.invalidate()
            updateEventsCacheTimer = nil
        }
    }

    func initializeTimers() {
        startUpdateEventsCacheTimer()
    }

    private func startKeepAliveTimer() {
        DispatchQueue.main.async { [weak self] in
            if self?.keepAliveTimer != nil {
                self?.keepAliveTimer?.invalidate()
                self?.keepAliveTimer = nil
            }
            guard self?.user.value != nil else {
                return
            }
            self?.keepAliveTimer = Timer.scheduledTimer(withTimeInterval: 60 * 3, repeats: false)
            { (_) in
                self?.keepAlive() {
                    self?.startKeepAliveTimer()
                }
            }
        }
    }
    
    private func startUpdateBalanceTimer() {
        DispatchQueue.main.async { [weak self] in
            if self?.updateBalanceTimer != nil {
                self?.updateBalanceTimer?.invalidate()
                self?.updateBalanceTimer = nil
            }
            guard self?.user.value != nil else {
                return
            }
            self?.updateBalanceTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false)
            { (_) in
                self?.updateBalance() {
                    self?.startUpdateBalanceTimer()
                }
            }
        }
    }
    
    private func startUpdateEventsCacheTimer() {
        DispatchQueue.main.async { [weak self] in
            if self?.updateEventsCacheTimer != nil {
                self?.updateEventsCacheTimer?.invalidate()
                self?.updateEventsCacheTimer = nil
            }
            self?.updateEventsCacheTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true)
            { (_) in
                self?.cacheFootballEvents()
            }
            self?.updateEventsCacheTimer?.fire()
        }
    }
    
    typealias ResponseBody = (data: Data, json: Any?)

    private func handle(request: URLRequest,
                        data: Data?,
                        response: URLResponse?,
                        error: Error?) -> Result<ResponseBody, Error>? {

        let successRange = 200...299

        if request.allHTTPHeaderFields?.contains(where: { $0.key == User.Key.sessionID.rawValue }) == true {
            startKeepAliveTimer()
        }

        if let code = (error as? URLError)?.code,
           case .cancelled = code {
            return nil
        }

        guard let statusCode = (response as? HTTPURLResponse)?.statusCode else {
            return .failure(error ?? URLError(.badServerResponse))
        }

        guard let data = data else {
            return .failure(URLError(.badServerResponse))
        }

        let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]

        switch statusCode {
        case 200...299:
            if let codeInBody = json?["code"] as? Int, !successRange.contains(codeInBody) {
                switch codeInBody {
                case SBBErrorCodes.MultiSlipToAcceptForTrader.rawValue,
                     SBBErrorCodes.SingleSlipToAcceptForTrader.rawValue:
                    let error = NSError(domain: "", code: codeInBody, userInfo: nil) as Error
                     return .failure(error)
                case SBBErrorCodes.LogoutUser.rawValue:
                    // Do not logout if there is no valid user
                    // Otherwise the sessionID after mobile/user-validation/registrations is cleared
                    // and mobile/user-validation/details fails
                    if self.user.value?.id != nil {
                        logoutUser { (_) in }
                    }
                    return nil
                case 423:
                    // Password reset returns 423 when the email is invalid or isn't in the system.
                    // When the email is invalid we get a description: Provided data is invalid. (email)
                    // When the email isn't in the system we get a description: Provided data is invalid. (customer-not-found)
                    // Presenting an error for email that isn't in the database would cause a security bridge so we have to handle this error as 'success'
                    guard let desctiption = json?["description"] as? String else {
                        return .failure(NSError(domain: "", code: codeInBody, userInfo: nil) as Error)
                    }
                    let error = EtotoAPIError(description: desctiption)
                    return .failure(error)
                case 600:
                    if let jsonData = (json?["data"] as? [[String: Any]]),
                       let betslipErrorInBody = jsonData[safe: 0]?["betSlipErrorCode"] as? Int {
                        if betslipErrorInBody == 46 {
                            let mtsErrorDictionary = (jsonData[safe: 0]?["errorDetails"] as? [String: Any])
                            let mtsError = mtsErrorDictionary?["mtsErrorCode"] as? String
                            let error = NSError(domain: "", code: Int(mtsError ?? "0") ?? 0, userInfo: ["type":"MTS"]) as Error
                             return .failure(error)
                        }
                       let error = NSError(domain: "", code: betslipErrorInBody, userInfo: nil) as Error
                        return .failure(error)
                    } else {
                        return .failure(URLError(.badServerResponse))
                    }
                case 200...299:
                    return .success((data, json?["data"]))
                default:
                    let apiError = try? JSONDecoder().decode(APIError.self, from: data)
                    return .failure(apiError ?? URLError(.badServerResponse))
                }
            }
            return .success((data, json?["data"]))
        case 600:
            return .failure(error ?? URLError(.badServerResponse))
        case 421:
            user.value = nil
            return nil
        case 401:
            return .failure(URLError(.userAuthenticationRequired))
        case 400...599:
            let apiError = try? JSONDecoder().decode(APIError.self, from: data)
            return .failure(apiError ?? URLError(.badServerResponse))
        default:
            return .failure(error ?? URLError(.badServerResponse))
        }
    }

    //MARK: - Betting

    @discardableResult
    func checkTradersDecision(completion: @escaping CheckTradersDecision) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.approvedSlips(sessionID: user.value?.sessionID)
        ) else {
            return nil
        }
        return checkTradersDecision(request: request, completion: completion)
    }

    private func checkTradersDecision(request: URLRequest, completion: @escaping CheckTradersDecision) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                if let json = body.json as? [String : Any] {
                    completion(.success(json))
                }
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func approveTraderChange(approved: Bool, completion: @escaping ApproveTraderChange) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.approveTraderChange(decision: approved,
                                                                                   sessionID: user.value?.sessionID)
        ) else {
            return nil
        }
        return approveTraderChange(request: request, completion: completion)
    }

    private func approveTraderChange(request: URLRequest, completion: @escaping ApproveTraderChange) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(_ ):
                completion(.success("newOdds"))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func getLiveOdds(odds: [String], completion: @escaping GetLiveOddsCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoLiveBettingAPI(),
                                       endpoint: ETotoLiveBettingAPI.Path.refreshOdds(odds: odds)
        ) else {
            return nil
        }
        return getLiveOdds(request: request, completion: completion)
    }

    private func getLiveOdds(request: URLRequest, completion: @escaping GetLiveOddsCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                if let newOdds = body.json as? [[String : Any]] {
                    completion(.success(newOdds))
                } else {
                    completion(.failure(URLError(.cannotParseResponse)))
                }
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func virtualsHistory(from: Date,
                         to: Date,
                         completion: @escaping (Result<[PlacedBet]?, Error>) -> Void) -> URLSessionWork? {
        guard let request =
                URLRequest(api: ETotoBetHistoryAPI(),
                           endpoint: ETotoBetHistoryAPI.Path.virtualsHistory(from: from,
                                                                             to: to,
                                                                             sessionID: user.value?.sessionID)) else {
            return nil
        }
        let work = URLSessionWork()
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let json = try? JSONSerialization.jsonObject(with: body.data, options: []) as? [String: Any],
                      let bodyJson = json["body"] else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }

                let bets = PlacedBet.virtualPlacedBets(bettingHistory: bodyJson as? [String: [String: Any]])
                guard let ids = bets?.compactMap({ $0.id }) else { return completion(.success(bets)) }
                work.append(self?.cashoutAmounts(bet: ids) { result in
                    if case .success(let amounts) = result {
                        amounts?.forEach({ (id, amount) in
                            bets?.first(where: { $0.id == id })?.cashout = amount
                        })
                    }
                    completion(.success(bets))
                })
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        work.append(task)
        task.resume()
        return work
    }
    
    @discardableResult
    func resetPassword(email: String, completion: @escaping ResetPasswordCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.resetPassword(email: email)) else {
            return nil
        }
        return resetPassword(request: request, completion: completion)
    }
    
    private func resetPassword(request: URLRequest, completion: @escaping ResetPasswordCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(_):
                completion(.success(()))
            case .failure(let error):
                guard let error = error as? EtotoAPIError else {
                    completion(.failure(.unknown))
                    return
                }
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func notifyTraderForBetWith(body: [String: Any], completion: @escaping PlaceBetCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoLiveBettingAPI.Path.notifyTraderForBetWith(body: body,
                                                                                                 sessionID: user.value?.sessionID)
        ) else {
            return nil
        }
        return notifyTraderForBetWith(request: request, completion: completion)
    }

    private func notifyTraderForBetWith(request: URLRequest, completion: @escaping PlaceBetCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                completion(.success(body.json as? [String: Any] ?? [String: Any]()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func placeBet(body: [String: Any], completion: @escaping PlaceBetCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoLiveBettingAPI.Path.placeBet(body: body,
                                                                                   sessionID: user.value?.sessionID)) else {
            return nil
        }
        return placeBet(request: request, completion: completion)
    }

    private func placeBet(request: URLRequest, completion: @escaping PlaceBetCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                completion(.success(body.json as? [String: Any] ?? [String: Any]()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func getFreebets(completion: @escaping FreebetCompletion) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID else { return nil }
        guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.freebets(sessionID: sessionID)) else { return nil }
        return getFreebets(request: request, completion: completion)
    }

    private func getFreebets(request: URLRequest, completion: @escaping FreebetCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                if let bodyJson = body.json as? [String: Any],
                   let freebetsJson = bodyJson["active"] as? [[String: Any]] {
                    let freebets = freebetsJson.compactMap { (input: [String: Any] ) in
                        return Freebet(with: input)
                    }
                    completion( .success(freebets) )
                } else {
                    completion(.failure(URLError(.cannotParseResponse)))
                }
            case .failure(let error):
                completion(.failure(error))

            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func cancelAllCreditBonus(completion: @escaping CancelAllCreditBonusCompletion) -> URLSessionDataTask?
    {
        guard let sessionID = user.value?.sessionID else { return nil }
        guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.cancelAllCreditBonus(sessionID: sessionID)) else { return nil }
        return cancelAllCreditBonus(request: request, completion: completion)
    }
    
    private func cancelAllCreditBonus(request: URLRequest, completion: @escaping CancelAllCreditBonusCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                if let bodyJson = body.json as? [String: Any] {
                  
                   completion( .success(bodyJson) )
                } else {
                    completion(.failure(URLError(.cannotParseResponse)))
                }
            case .failure(let error):
                completion(.failure(error))
        
           default:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func getCarouselSegments(completion: @escaping GetCarouselSegmentsCompletion) -> URLSessionDataTask?
    {
        guard let sessionID = user.value?.sessionID else { return nil }
        guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.carouselSegments(sessionID: sessionID)) else { return nil }
        return getCarouselSegments(request: request, completion: completion)
    }
    
    private func getCarouselSegments(request: URLRequest, completion: @escaping GetCarouselSegmentsCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                guard let model = try? JSONDecoder().decode(GetCarouselSegmentsModel.self, from: body.data) else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }
                completion(.success(model))
            case .failure(let error):
                completion(.failure(error))
        
           default:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func getTaxFactor(body: [String: Any], completion: @escaping GetTaxFactorCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoLiveBettingAPI.Path.calculateTaxFactor(body: body, sessionID: user.value?.sessionID)) else {
            return nil
        }
        return getTaxFactor(request: request, completion: completion)
    }

    private func getTaxFactor(request: URLRequest, completion: @escaping GetTaxFactorCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                if let taxFactor = body.json as? Double {
                    completion(.success(taxFactor))
                } else {
                    completion(.failure(URLError(.cannotParseResponse)))
                }
                
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func getPotentialWinnings(body: [String: Any], completion: @escaping GetPotentialWinningCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoLiveBettingAPI.Path.calculateWinnings(body: body)) else {
            return nil
        }
        return getPotentialWinnings(request: request, completion: completion)
    }

    private func getPotentialWinnings(request: URLRequest, completion: @escaping GetPotentialWinningCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                completion(.success(body.json as? [String: Any] ?? [String: Any]()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    //MARK: - Categories

    @discardableResult
    func getCategories(completion: @escaping GetCategoriesCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.categories) else {
            return nil
        }
        return getCategories(request: request, completion: completion)
    }
    
    @discardableResult
    func getSubCategories(category: Int16, completion: @escaping GetCategoriesCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.subCategories(category: category)) else {
            return nil
        }
        return getCategories(request: request, completion: completion)
    }

    private func getCategories(request: URLRequest, completion: @escaping GetCategoriesCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                let categories = SportCategory.categories(dictionaries: body.json as? [[String : Any]])
                completion(.success(categories))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func getEvent(eventID: Int,
                  completion: @escaping GetEventCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.event(eventID: eventID)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let json = body.json as? [String: Any] else {
                    return completion(.failure(URLError(.cannotParseResponse)))
                }
                completion(.success(Event(json)))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func getEvents(category: Int16, completion: @escaping GetEventsCompletion<Event>) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.eventsForCategory(category: category)) else {
            return nil
        }
        return getEvents(request: request, completion: completion)
    }
    
    @discardableResult
    func getEvents(categories: [Int16], completion: @escaping GetEventsCompletion<Event>) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.eventsForCategories(categories: categories)) else {
            return nil
        }
        return getEvents(request: request, completion: completion)
    }
    
    private func getEvents(request: URLRequest,
                           completion: @escaping GetEventsCompletion<Event>) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                let events = Event.events(dictionaries: body.json as? [[String: Any]])
                completion(.success(Filters.filterOutShouldBeHidden(events)))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    func getImage(categoryID: String, settings: Settings = Settings.etoto, completion: @escaping (UIImage?) -> ()) -> URLSessionDataTask? {
        guard let url = settings.categoryIcons[categoryID]?.url else {
            completion(UIImage(named: "sportIconsUniversalSport"))
            return nil
        }
        if let image = cache.image(for: url) {
            completion(image)
            return nil
        }
        let request = URLRequest(url: url)
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                if let image = UIImage(data: body.data) {
                    self?.cache.insertImage(image, for: url)
                    DispatchQueue.main.async {
                        completion(image)
                    }
                }
            case .failure:
                DispatchQueue.main.async {
                    completion(nil)
                }
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func getEvents(withSearchPattern pattern: String,
                                  completion: @escaping GetOfferEventsCompletion<Event,LiveEvent>) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.quickSearchEvents(searchPattern: pattern)) else {
            completion(.failure(CocoaError(.userCancelled)))
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                let events = Event.events(searchResult: body.json as? [[String : Any]])
                completion(.success(events))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func getPopularCategories(completion: @escaping GetPopularCategoriesCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.popularCategories) else {
            return nil
        }
        return getPopularCategories(request: request, completion: completion)
    }

    private func getPopularCategories(request: URLRequest, completion: @escaping GetPopularCategoriesCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let model = try? JSONDecoder().decode(PopularCategoryResponseModel.self, from: body.data) else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }
                completion(.success(model.data))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    

    //MARK: - Live -

    @discardableResult
    func getLiveSports(completion: @escaping GetCategoriesCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoLiveBettingAPI(),
                                       endpoint: ETotoLiveBettingAPI.Path.liveBettingCategories) else {
            return nil
        }
        return getLiveSports(request: request, completion: completion)
    }

    private func getLiveSports(request: URLRequest,
                               completion: @escaping GetCategoriesCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                let categories = SportCategory.categories(dictionaries: body.json as? [[String : Any]])?
                    .filter{ $0.level == 1 && ($0.eventsCount ?? 0) > 0}
                    .sorted(by: { $0.sortOrder < $1.sortOrder })
                completion(.success(categories))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func getLiveEvents(sportID: Int16, completion: @escaping GetEventsCompletion<LiveEvent>) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoLiveBettingAPI(),
                                       endpoint: ETotoLiveBettingAPI.Path.eventsForCategory(category: sportID)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                completion(.success(LiveEvent.events(result: body.json as? [[String : Any]])))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func getLiveEventsForDate(dateString: String, completion: @escaping GetEventsCompletion<Event>) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.eventWithDate(date: dateString)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let events = Event.events(dictionaries: body.json as? [[String : Any]]) else { return }
                completion(.success(events))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func getLiveEvent(eventID: Int, completion: @escaping GetEventCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoLiveBettingAPI(),
                                       endpoint: ETotoLiveBettingAPI.Path.event(event: eventID)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let eventDic = (body.json as? [Any])?.first as? [String : AnyObject] else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }
                completion(.success(LiveEvent(eventDic)))
            case .failure(let error):
                completion(.failure(error))
                
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    // MARK: - StatScore -
    
    @discardableResult
    func getStatScoreEventMapping(completion: @escaping GetStatScoreEventMappingCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.eventsStatScore) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let dictionaries = body.json as? StatScoreMapping else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }
                
                completion(.success(dictionaries))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    //MARK: - User -
    // All, when completed, should reset the keep alive timer
    private lazy var isUserLoggingIn = false
    @discardableResult
    func loginUser(name: String?, password: String?,
                   completion: GetUserCompletion?) -> URLSessionDataTask? {
        logInCompletions.appendOptional(completion)
        guard !isUserLoggingIn,
              let name = name, let password = password,
              let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.loginUser(name: name, password: password)) else {
            return nil
        }
        isUserLoggingIn = true
        let task = session.dataTask(with: request)
        { [weak self] (data, response, error) in
            self?.isUserLoggingIn = false
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                var dictionary = body.json as? [String : Any]
                dictionary?[User.Key.sessionID.rawValue] =
                    (response as? HTTPURLResponse)?.allHeaderFields[User.Key.sessionID.rawValue]
                guard let user = User(dictionary: dictionary) else {
                    self?.logInCompletions.forEach{ $0(.failure(URLError(.cannotParseResponse))) }
                    self?.logInCompletions.removeAll()
                    return
                }
                self?.logInCompletions.forEach{ $0(.success(user)) }
                self?.logInCompletions.removeAll()
                self?.forceUpdateBalance(force: false, completion: nil)
            case .failure(let error):
                log.error(error.localizedDescription)
                self?.logInCompletions.forEach{ $0(.failure(error)) }
                self?.logInCompletions.removeAll()
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func loggedInUser(completion: GetUserCompletion?) -> URLSessionDataTask? {
        if let user = user.value {
            completion?(.success(user))
            return nil
        }
        //TODO: ("Uncomment for autologin")
        return nil
//        guard !isUserLoggingIn else {
//            logInCompletions.appendOptional(completion)
//            return nil
//        }
//        guard let credentials = KeyChain.rememberedUserCredentials else {
//            completion?(.failure(URLError(.userAuthenticationRequired)))
//            return nil
//        }
//        return loginUser(name: credentials.name,
//                         password: credentials.password,
//                         completion: completion)
    }
    
    @discardableResult
    func loggedInUser() -> URLSessionDataTask? {
        return loggedInUser(completion: nil)
    }
    
    func keepAlive(completion: @escaping () -> ()) {
        guard let sessionID = user.value?.sessionID,
              let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.keepAlive(sessionID: sessionID)) else {
            return
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                if let user = User(dictionary: body.json as? [String : Any]) {
                    user.sessionID = self?.user.value?.sessionID
                    self?.user.value = user
                }
                completion()
            case .failure(let error):
                print(error.localizedDescription)
            case .none:
                break
            }
        }
        task.resume()
    }
    
    private func updateBalance(completion: @escaping () -> ()) {
        updateBalance(force: false, immediately: false, completion: completion)
    }
    
    @discardableResult
    func forceUpdateBalance(force: Bool, completion: (() -> ())? = nil) -> URLSessionDataTask? {
        return updateBalance(force: force, immediately: true, completion: completion)
    }
    
    @discardableResult
    private func updateBalance(force: Bool, immediately: Bool, completion: (() -> ())? = nil) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID else {
            return nil
        }
        
        let endpoint: EndPointType
        if immediately {
            endpoint = ETotoAPI.Path.forceUpdateBalance(force: force, sessionID: sessionID)
        } else {
            endpoint = ETotoAPI.Path.updateBalance(sessionID: sessionID)
        }
        
        guard let request = URLRequest(api: ETotoAPI(), endpoint: endpoint) else {
            return nil
        }
        
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                if let data = body.json as? [String : Any],
                   let bonusData = data["creditBonusBalance"] as? [String : Any],
                   let balance = data["balance"] as? Double
                {
                    if let bonus = bonusData["amount"] as? Double {
                        self?.user.value?.bonusAmount = bonus
                    } else {
                        self?.user.value?.bonusAmount = 0
                    }
                    self?.user.value?.balance.value = balance
                }
                completion?()
            case .failure(let error):
                print(error.localizedDescription)
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func logoutUser(completion: @escaping (Result<Bool, Error>) -> ()) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID,
              let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.logoutUser(sessionID: sessionID)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            //TODO: ("Move to the success case once error handling is decided upon")
            self?.user.value = nil
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success:
                completion(.success(true))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func getUserDetails(completion: @escaping GetUserDetailsCompletion) -> URLSessionDataTask? {
        guard
            let sessionID = user.value?.sessionID,
            let request = URLRequest(api: UserProfileAPI(),
                                     endpoint: UserProfileAPI.Path.getUserDetails(sessionID: sessionID)) else {
            completion(.failure(CustomError(type: .genericError)))
            return nil
        }

        #if RELEASE
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success((let data, _)):
                let response = try? JSONDecoder().decode(UserDetails.self, from: data)
                completion(.success(response))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #elseif APPSTORE
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success((let data, _)):
                let response = try? JSONDecoder().decode(UserDetails.self, from: data)
                completion(.success(response))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #elseif DEBUG
        let task = temporarySession.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success((let data, _)):
                let response = try? JSONDecoder().decode(UserDetails.self, from: data)
                completion(.success(response))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #endif
        task.resume()
        return task
    }

    func postUserDetails(userDetails: UserDetails.User, completion: @escaping GetEmptyCompletion) -> URLSessionDataTask? {
        guard
            let sessionID = user.value?.sessionID,
            let request = URLRequest(api: UserProfileAPI(),
                                     endpoint: UserProfileAPI.Path.postUserDetails(userDetails: userDetails, sessionID: sessionID)) else {
            completion(.failure(CustomError(type: .genericError)))
            return nil
        }

        #if RELEASE
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success:
                completion(.success(()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #elseif APPSTORE
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success:
                completion(.success(()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #elseif DEBUG
        let task = temporarySession.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success:
                completion(.success(()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #endif
        task.resume()
        return task
    }
    
    func getDocuments(completion: @escaping GetDocumentsCompletion) -> URLSessionDataTask? {
        guard
            let sessionID = user.value?.sessionID,
            let request = URLRequest(api: UserProfileAPI(), endpoint: UserProfileAPI.Path.getDocuments(sessionID: sessionID)) else {
            completion(.failure(CustomError(type: .genericError)))
            return nil
        }

        #if RELEASE
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                do {
                    let documentList = try JSONDecoder().decode(DocumentList.self, from: body.data)
                    completion(.success(documentList.receivedUserDocuments))
                } catch {
                    completion(.failure(error))
                }
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #elseif APPSTORE
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                do {
                    let documentList = try JSONDecoder().decode(DocumentList.self, from: body.data)
                    completion(.success(documentList.receivedUserDocuments))
                } catch {
                    completion(.failure(error))
                }
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #elseif DEBUG
        let task = temporarySession.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                do {
                    let documentList = try JSONDecoder().decode(DocumentList.self, from: body.data)
                    completion(.success(documentList.receivedUserDocuments))
                } catch {
                    completion(.failure(error))
                }
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #endif
        task.resume()
        return task
    }

    func manulalUserConversion(userManualConversion: UserManualConversion, completion: @escaping GetEmptyCompletion) -> URLSessionDataTask? {
        guard
            let sessionID = user.value?.sessionID,
            let request = URLRequest(api: UserProfileAPI(),
                                     endpoint: UserProfileAPI.Path.manualConversion(userManualConversion: userManualConversion, sessionID: sessionID)) else {
            completion(.failure(CustomError(type: .genericError)))
            return nil
        }

        #if RELEASE
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success:
                completion(.success(()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #elseif APPSTORE
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success:
                completion(.success(()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #elseif DEBUG
        let task = temporarySession.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success:
                completion(.success(()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        #endif
        task.resume()
        return task
    }

    @discardableResult
    func changePassword(new: String,
                        old: String,
                        completion: @escaping (Result<Bool, Error>) -> ()) -> URLSessionDataTask? {
        guard let userID = user.value?.id, let sessionID = user.value?.sessionID,
            let request = URLRequest(api: ETotoAPI(),
                                     endpoint: ETotoAPI.Path.changePassword(new: new, old: old, userID: userID, sessionID: sessionID)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success:
                completion(.success(true))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    func transactions(from: Date, to: Date,
                      pageSize size: Int?, offset: Int?,
                      completion: @escaping (Result<[Transaction]?, Error>) -> Void) -> URLSessionDataTask? {
        guard let request =
                URLRequest(api: ETotoAPI(),
                           endpoint: ETotoAPI.Path.transactions(from: from, to: to, pageSize: size, offset: offset, sessionID: user.value?.sessionID)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                completion(.success(Transaction.transactions(dictionaries: body.json as? [[String: Any]])))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    func transactionsWithdrawStatus(pageSize size: Int?, offset: Int?,
                      completion: @escaping (Result<[Transaction]?, Error>) -> Void) -> URLSessionDataTask? {
        guard let request =
                URLRequest(api: ETotoAPI(),
                           endpoint: ETotoAPI.Path.transactionsWithdrawStatus(pageSize: size, offset: offset, sessionID: user.value?.sessionID)) else {
            return nil
        }
        
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                completion(.success(Transaction.transactions(dictionaries: body.json as? [[String: Any]])))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    func cancelTransactionWithdrawal(transactionID: Int?, completion: @escaping (Result<Void, Error>) -> ()) -> URLSessionDataTask? {
        guard let request =
                URLRequest(api: ETotoAPI(),
                           endpoint: ETotoAPI.Path.cancelTransactionWithdrawal(transactionID: transactionID, sessionID: user.value?.sessionID)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success:
                completion(.success(()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func placedBets(from: Date, to: Date, size: Int, status: PlacedBet.Status?, offset: Int,
                    completion: @escaping (Result<([PlacedBet]?, Int?), Error>) -> Void) -> URLSessionWork? {
        guard let request =
                URLRequest(api: ETotoAPI(),
                           endpoint: ETotoAPI.Path.bettingHistory(from: from, to: to, size: size, type: status,
                                                                  offset: offset, sessionID: user.value?.sessionID)) else {
            return nil
        }
        let work = URLSessionWork()
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                let betsAndTheirIDs = PlacedBet.sAndTheirIDs(from: body.json as? [[String: Any]])
                let bets = betsAndTheirIDs.bets
                var count: Int? = nil
                if let responseBody = body.json as? [[String: Any]], responseBody.count > 0, let totalCount = responseBody[0]["transactionsCount"] as? Int {
                    count = totalCount
                }
                guard let ids = betsAndTheirIDs.ids else {
                    return completion(.success((bets,count)))
                }
                work.append(self?.cashoutAmounts(bet: ids) { result in
                    if case .success(let amounts) = result {
                        amounts?.forEach({ (id, amount) in
                            bets?.first(where: { $0.id == id })?.cashout = amount
                        })
                    }
                    completion(.success((bets,count)))
                })
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        work.append(task)
        task.resume()
        return work
    }
    
    @discardableResult
    func cashoutAmounts(bet ids: [Int], completion: @escaping (Result<[Int: Double]?, Error>) -> ()) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.cashoutAmounts(betIDs: ids, sessionID: user.value?.sessionID)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                var amounts: [Int: Double]?
                (body.json as? [String: [String: Any]])?.forEach {
                    if let value = $0.value["currentCashoutAmount"] as? Double,
                       let key = Int($0.key) {
                        if amounts == nil { amounts = [:]}
                        amounts?[key] = value
                    }
                }
                completion(.success(amounts))
            return
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func checkAndCashout(betWith id: Int, amount: Double, acceptedChanges: Bool,
                    completion: @escaping (Result<Double, CashoutError>) -> ()) -> URLSessionWork? {
        let endpoint = ETotoAPI.Path.cashoutAmountAndCountdownRequired(
            betID: id,
            sessionID: user.value?.sessionID
        )
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: endpoint) else {
            return nil
        }
        let work = URLSessionWork()
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                if let dict = body.json as? [AnyHashable: Any] {
                    if !acceptedChanges,
                       let current = dict["amount"] as? Double, current != amount {
                        return completion(.failure(.changed(amount: current)))
                    }
                    else if (dict["countDownRequired"] as? Bool) == true {
                        work.append(self?.cashoutCountdown(betID: id, amount: amount, acceptedChanges: acceptedChanges) { result in
                            switch result {
                            case .success(let countdown):
                                if let miliseconds = countdown {
                                    DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + miliseconds / 1000) {
                                        guard !work.canceled else { return }
                                        work.append(self?.cashout(betWith: id, amount: amount,
                                                                      acceptedChanges: acceptedChanges, completion: completion))
                                    }
                                }
                                else {
                                    work.append(self?.cashout(betWith: id, amount: amount,
                                                                  acceptedChanges: acceptedChanges, completion: completion))
                                }
                            case .failure(let countdownError):
                                completion(.failure(countdownError))
                            }
                        })
                    }
                    else {
                        work.append(self?.cashout(betWith: id, amount: amount,
                                                      acceptedChanges: acceptedChanges, completion: completion))
                    }
                }
            case .failure(let error):
                completion(.failure(.other(error: error)))
            case .none:
                break
            }
        }
        task.resume()
        work.append(task)
        return work
    }
    
    @discardableResult
    private func cashoutCountdown(
        betID: Int, amount: Double, acceptedChanges: Bool,
        completion: @escaping (Result<Double?, CashoutError>) -> ()) -> URLSessionDataTask? {
        let endpoint = ETotoAPI.Path.cashoutCountdown(
            betID: betID, amount: amount, acceptedChanges: acceptedChanges,
            sessionID: user.value?.sessionID)
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: endpoint) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                if let dict = body.json as? [AnyHashable: Any] {
                    if let miliseconds = dict["value"] as? String {
                        return completion(.success(Double(miliseconds)))
                    } else if let changed = dict["amount"] as? Double ??
                                            dict["currentCashoutAmount"] as? Double {
                        return completion(.failure(.changed(amount: changed)))
                    }
                }
                completion(.success(nil))
            return
            case .failure(let error):
                completion(.failure(.other(error: error)))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func cashout(betWith id: Int, amount: Double, acceptedChanges: Bool,
                    completion: @escaping (Result<Double, CashoutError>) -> ()) -> URLSessionDataTask? {
        let endpoint = ETotoAPI.Path.cashoutBet(
            id: id, amount: amount, acceptedChanges: acceptedChanges,
            sessionID: user.value?.sessionID)
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: endpoint) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                if let dict = body.json as? [AnyHashable: Any] {
                    if dict["success"] as? Bool == true {
                        return completion(.success(dict["currentCashoutAmount"] as? Double ?? amount))
                    } else {
                        if let changed = dict["currentCashoutAmount"] as? Double {
                            return completion(.failure(.changed(amount: changed)))
                        }
                        let error = URLError(
                            .unknown,
                            userInfo: [NSLocalizedDescriptionKey: dict["errorMessage"] as? String ?? ""])
                        return completion(.failure(.other(error: error)))
                    }
                }
                completion(.success(amount))
            return
            case .failure(let error):
                completion(.failure(.other(error: error)))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func getActivePaymentProviders(completion: @escaping GetActivePaymentProvidersCompletion) -> URLSessionDataTask? {
        guard let sessionId = user.value?.sessionID,
              let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.activePaymentProviders(sessionID:sessionId )) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let dictionaries = body.json as? [[String : Any]] else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }
                
                completion(.success(PaymentProvider.providers(dictionaries)))
            return
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func initializePayment(providerId: Int,
                           transactionAmount: Decimal,
                           transactionCurrency:String,
                           completion: @escaping PostInitializePaymentCompletion) -> URLSessionDataTask? {
        
        guard let sessionID = user.value?.sessionID else { return nil }
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.initializePayment(providerId: providerId,
                                                                                 transactionAmount: transactionAmount,
                                                                                 transactionCurrency: transactionCurrency,
                                                                                 sessionID: sessionID)) else { return nil }
       
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let dictionaries = body.json as? [String:Any],
                      let success = dictionaries["success"] as? Bool else {
                    completion(.failure(WithdrawError.processFail))
                    return
                }
                
                if !success {
                    let code = dictionaries["responseCode"] as? Int
                    let error = self?.getMappedErrorForPayment(with: code) ?? WithdrawError.processFail
                    return completion(.failure(error))
                }

                completion(.success(()))
            case .none:
                return
            case .some(.failure(let error)):
                completion(.failure(error))
            }
        }
        task.resume()
        return task
    }
    
    private func getMappedErrorForPayment(with code: Int?) -> Error {
        guard let code = code else {
            return WithdrawError.processFail
        }
        guard let mappedError = Settings.etoto.paymentErrorMessages.first(where: {$0.code == code}) else {
            if code == 47 {
                return WithdrawError.activeCreditBonus
            }
            let error: Error
            if let firebaseError = Settings.etoto.paymentErrorMessages.first(where: { $0.code == 0 }) {
                error = firebaseError
            } else {
                error = WithdrawError.processFail
            }
            return error
        }
        return mappedError
    }

    //MARK: - Register -

    @discardableResult

    func baseRegistration(baseRegistration: BaseRegistration, completion: @escaping GetEmptyCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoRegisterAPI(),
                                       endpoint: ETotoRegisterAPI.Path.baseRegistration(baseRegistration: baseRegistration)) else {
            return nil
        }

        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(_):
                if let httpResponse = response as? HTTPURLResponse {
                    print(httpResponse.allHeaderFields.keys)
                    if let xOddsSession = httpResponse.allHeaderFields[User.Key.sessionID.rawValue] as? String {
                        let user = User(dictionary: [:])
                        user?.sessionID = xOddsSession
                        self?.user.value = user
                    }
                }
                completion(.success(()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    func detailsRegistration(detailsRegistration: DetailsRegistration, completion: @escaping GetEmptyCompletion) -> URLSessionDataTask? {
        guard
            let sessionID = user.value?.sessionID,
            let request = URLRequest(api: ETotoRegisterAPI(),
                                     endpoint: ETotoRegisterAPI.Path.detailRegistation(detailsRegistration: detailsRegistration,
                                                                                       sessionID: sessionID)) else {
            return nil
        }

        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success:
                completion(.success(()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func processPayment(providerId: Int,
                        transactionAmount: Double,
                        transactionCurrency: String,
                        callbackUrl: String,
                        bankDetail: BankDetail? = nil,
                        completion: @escaping PostProcessPaymentCompletion) -> URLSessionDataTask? {
        
        guard let sessionID = user.value?.sessionID,
              let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.processPayment(providerId: providerId,
                                                  transactionAmount: transactionAmount,
                                                  transactionCurrency: transactionCurrency,
                                                  callback: callbackUrl,
                                                  bankDetail: bankDetail,
                                                  sessionID: sessionID)) else {
            return nil
        }
       
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                
                guard let dictionaries = body.json as? [String:Any],
                      let success = dictionaries["success"] as? Bool else {
                    completion(.failure(WithdrawError.processFail))
                    return
                }
                
                if !success {
                    let code = dictionaries["responseCode"] as? Int
                    let error = self?.getMappedErrorForPayment(with: code) ?? WithdrawError.processFail
                    completion(.failure(error))
                }

                completion(.success(dictionaries["externalRequestUrl"] as? String ?? ""))
            case .none:
                return
            case .some(.failure(let error)):
                completion(.failure(error))
            }
        }
        task.resume()
        return task
    }

    @discardableResult
    func customerBanksDetail(_ completion: @escaping GetCustomerBanksDetailCompletion) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID,
              let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.customerBanksDetail(sessionID: sessionID)) else {
            return nil
        }
       
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let dictionaries = body.json as? [[String : Any]]else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }
                
                completion(.success(BankDetail.banksDeail(dictionaries)))
                    return
            case .none:
                return
            case .some(.failure(let error)):
                completion(.failure(error))
            }
        }
        task.resume()
        return task
    }

    //TODO:  ("Uncomment sessionID after tests")
    func dateValidation(date: String, completion: @escaping GetValidationCompletion) -> URLSessionDataTask? {
        guard
            let sessionID = user.value?.sessionID,
            let request = URLRequest(api: ETotoRegisterAPI(baseURL: Settings.etoto.registrationValidationURL),
                                     endpoint: ETotoRegisterAPI.Path.dateValidation(date: date, sessionID: sessionID)) else {
            return nil
        }

        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let data):
                //TODO: ("Remove force in error casting")
                guard let responseArray = try? JSONSerialization.jsonObject(with: data.data, options: []) as? [String] else { return }
                if let httpResponse = response as? HTTPURLResponse {
                    if let xOddsSession = httpResponse.allHeaderFields[User.Key.sessionID.rawValue] as? String {
                        print(xOddsSession)
                    }
                }
                completion(.success((responseArray)))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    func iDNumberValidation(iDNumber: String, completion: @escaping GetValidationCompletion) -> URLSessionDataTask? {
        guard
            let sessionID = user.value?.sessionID,
            let request = URLRequest(api: ETotoRegisterAPI(baseURL: Settings.etoto.baseRegistrationsURL),
                                     endpoint: ETotoRegisterAPI.Path.iDNumberValidation(iDNumber: iDNumber, sessionID: sessionID)) else {
            return nil
        }

        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let data):
                guard let responseArray = try? JSONSerialization.jsonObject(with: data.data, options: []) as? [String] else { return }
                completion(.success((responseArray)))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    func personalIDValidation(personalID: String, completion: @escaping GetValidationCompletion) -> URLSessionDataTask? {
        guard
            let sessionID = user.value?.sessionID,
            let request = URLRequest(api: ETotoRegisterAPI(baseURL: Settings.etoto.baseRegistrationsURL),
                                     endpoint: ETotoRegisterAPI.Path.personalIDValidation(personalID: personalID, sessionID: sessionID)) else {
            return nil
        }

        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let data):
                guard let responseArray = try? JSONSerialization.jsonObject(with: data.data, options: []) as? [String] else { return }
                completion(.success((responseArray)))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    func bonusValidation(completion: @escaping GetValidationCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoRegisterAPI(baseURL: Settings.etoto.baseRegistrationsURL),
                                       endpoint: ETotoRegisterAPI.Path.bonusValidation) else {
            return nil
        }

        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let data):
                guard let responseArray = try? JSONSerialization.jsonObject(with: data.data, options: []) as? [String: Any],
                      let codes = responseArray["codes"] as? [String] else {
                    return
                }
                completion(.success((codes)))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func test( _ request: NSMutableURLRequest, completion: @escaping (Result<String?, Error>) -> ()) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID else {
            return nil
        }
        request.addValue(sessionID, forHTTPHeaderField: "X-ODDS-SESSION")
        let task = session.dataTask(with: request as URLRequest) { [weak self] (data, response, error) in
            
            switch self?.handle(request: request as URLRequest, data: data, response: response, error: error) {
            case .success(let body):
                completion(.success(String(data: body.data, encoding: .utf8)))
                    return
            case .none:
                return
            case .some(.failure(let error)):
                completion(.failure(error))
            return
            }
        }
        task.resume()
        return task
    }

    // MARK: - Virtuals
    @discardableResult
    func getVirtualsWebURL(query: [URLQueryItem]?,completion: @escaping GetVirtualsWebURL) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoVirtualsBettingAPI(),
                                       endpoint: ETotoVirtualsBettingAPI.Path.launchGame(sessionID: user.value?.sessionID, query: query)) else {
            return nil
        }
        
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let json = try? JSONSerialization.jsonObject(with: body.data, options: []) as? [String: Any] else {
                    completion(.failure(.launchGame(errorMessage: URLError(.cannotParseResponse).localizedDescription)))
                    return
                }

                if let errorMessage = json["ErrorMSg"] as? String {
                    completion(.failure(ETotoVirtualsBettingAPIError.launchGame(errorMessage: errorMessage)))
                    return
                }

                guard let urlString = json["url"] as? String else {
                    completion(.failure(.launchGame(errorMessage: URLError(.cannotParseResponse).localizedDescription)))
                    return
                }

                let url = URL(string: urlString)
                completion(.success(url))
            case .failure(let error):
                let virtualsError: ETotoVirtualsBettingAPIError
                if let urlError = error as? URLError, urlError.code == .userAuthenticationRequired {
                    virtualsError = .userAuthenticationRequired(errorMessage: urlError.localizedDescription)
                } else {
                    virtualsError = .launchGame(errorMessage: error.localizedDescription)
                }
                completion(.failure(virtualsError))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func setLimits(limits: [LimitRequestBody], completion: @escaping LimitCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.setLimits(sessionId: user.value?.sessionID, limits: limits)) else {
            return nil
        }
        return limit(request: request, completion: completion)
    }
    
    private func limit(request: URLRequest, completion: @escaping LimitCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                guard let body = body.json as? [[String: Any]] else {
                    completion(.failure(.cannotParseResponse))
                    return
                }
                completion(.success(body.compactMap { LimitRequestResponse($0) }))
            case .failure:
                completion(.failure(.unknown))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func acceptRegulations(completion: @escaping AcceptRegulationsCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ETotoAPI(),
                                       endpoint: ETotoAPI.Path.acceptRegulations(sessionID: user.value?.sessionID)) else {
            return nil
        }
        return acceptRegulations(request: request, completion: completion)
    }
    
    private func acceptRegulations(request: URLRequest, completion: @escaping AcceptRegulationsCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success:
                completion(.success(()))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func limits(completion: @escaping (Result<[Limits]?, Error>) -> Void) -> URLSessionDataTask? {
        guard let request = URLRequest(
                api: ETotoAPI(),
                endpoint: ETotoAPI.Path.limits(sessionID: user.value?.sessionID))
        else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                completion(.success(Limits.limits(from: body.json as? [[String: Any]])))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    
    @discardableResult
    func activate(
        _ limitation: Limits.Limitation,
        completion: @escaping (Result<[Limits]?, Error>) -> Void) -> URLSessionDataTask?
    {
        let endpoint = ETotoAPI.Path.activate(
            limitation: limitation,
            sessionID: user.value?.sessionID)
        guard let request = URLRequest(
                api: ETotoAPI(),
                endpoint: endpoint)
        else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                completion(.success(Limits.limits(from: body.json as? [[String: Any]])))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    // MARK: - Football events cache

    func cachedFootballEvents() -> [Event]? {
        return footballEventsCache
    }
    
    func cacheFootballEvents() {
        getEvents(category: 1) { result in
            switch result {
            case .success(let events):
                self.footballEventsCache = events
            case .failure(_):
                break
            }
        }
    }
    
    @objc private func invalidateFootballCache() {
        footballEventsCache = nil
    }
    
    // MARK: - Notifications Settings
    
    @discardableResult
    func getNotificationsSettings(completion: @escaping GetNotificationsSettingsCompletion) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID,
            let request = URLRequest(api: UserProfileAPI(), endpoint: UserProfileAPI.Path.getNotificationsSettings(sessionID: sessionID)) else {
            return nil
        }
        return getNotificationsSettings(request: request, completion: completion)
    }

    private func getNotificationsSettings(request: URLRequest, completion: @escaping GetNotificationsSettingsCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                var string = String(data: body.data, encoding: .utf8)
                log.debug(string)
                guard let model = try? JSONDecoder().decode(NotificationConfigRequestModel.self, from: body.data) else {
                    completion(.failure(.cannotParseResponse))
                    return
                }
                completion(.success(model))
            case .failure:
                completion(.failure(.unknown))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func postNotificationsSettings(model: NotificationConfigRequestModel,completion: @escaping PostNotificationsSettingsCompletion) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID,
              let request = URLRequest(api: UserProfileAPI(), endpoint: UserProfileAPI.Path.postNotificationsSettings(model: model, sessionID: sessionID)) else {
            return nil
        }
        return postNotificationsSettings(request: request, completion: completion)
    }

    private func postNotificationsSettings(request: URLRequest, completion: @escaping PostNotificationsSettingsCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success:
                completion(.success(()))
            case .failure:
                completion(.failure(.unknown))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }

    // MARK: - Chat

    @discardableResult
    func refreshChat(channelId: Int, completion: @escaping GetChatMessages) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ChatAPI(),
                                       endpoint: ChatAPI.Path.refreshChat(channelId: channelId)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            guard let data = data else { return }
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                guard let model = try? JSONDecoder().decode([ChatMessage].self, from: body.data) else {
                    completion(.failure(.cannotParseResponse))
                    return
                }
                completion(.success(model))
            case .failure:
                completion(.failure(.unknown))
            case .none:
                completion(.success([]))
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func saveMessage(channelId: Int, completion: @escaping GetChatMessages) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ChatAPI(),
                                       endpoint: ChatAPI.Path.refreshChat(channelId: channelId)) else {
            return nil
        }
        refreshChatTask = session.dataTask(with: request) { [weak self] (data, response, error) in
            guard let data = data else { return }
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                guard let model = try? JSONDecoder().decode([ChatMessage].self, from: body.data) else {
                    completion(.failure(.cannotParseResponse))
                    return
                }
                completion(.success(model))
            case .failure:
                completion(.failure(.unknown))
            case .none:
                break
            }
        }
        refreshChatTask?.resume()
        return refreshChatTask
    }

    @discardableResult
    func refreshChatOnChange(channelId: Int, lastId: Int, completion: @escaping GetChatMessages) -> URLSessionDataTask? {
        guard let request = URLRequest(api: ChatAPI(),
                                       endpoint: ChatAPI.Path.refreshOnChange(channelId: channelId, lastId: lastId)) else {
            return nil
        }

        refreshChatOnChangeTask = session.dataTask(with: request) { [weak self] (data, response, error) in
            guard let data = data else { return }
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                guard let model = try? JSONDecoder().decode([ChatMessage].self, from: body.data) else {
                    completion(.failure(.cannotParseResponse))
                    return
                }
                completion(.success(model))
            case .failure:
                completion(.failure(.unknown))
            case .none:
                break
            }
        }
        refreshChatOnChangeTask?.resume()
        return refreshChatOnChangeTask
    }

    @discardableResult
    func postChatMessage(messageBody: MessageBody, completion: @escaping GetEmptyCompletion) -> URLSessionDataTask? {
        var cleanMessageBody = messageBody

        let messageText = messageBody.messageText.literalized()

        cleanMessageBody.messageText = messageText
        guard
            let sessionId = user.value?.sessionID,
            let request = URLRequest(api: ChatAPI(),
                                       endpoint: ChatAPI.Path.postMessage(messageBody: cleanMessageBody, sessionId: sessionId)) else {
            return nil
        }
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(_):
                completion(.success(()))
            case .failure(let error):
                completion(.failure(CustomError(type: .genericError)))
            case .none:
                break
            }
        }
        
        task.resume()
        return task
    }
    
    @discardableResult
    func getEventsValidForSubscription(completion: @escaping GetEventsValidForSubscriptionCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: SubscriptionAPI(), endpoint: SubscriptionAPI.Path.allEvents) else {
            return nil
        }
        return getEventsValidForSubscription(request: request, completion: completion)
    }

    private func getEventsValidForSubscription(request: URLRequest, completion: @escaping GetEventsValidForSubscriptionCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let model = try? JSONDecoder().decode(EventsValidForSubscriptionModel.self, from: body.data) else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }
                completion(.success(model))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func getSubscribedEvents(completion: @escaping GetSubscribedEventsCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: SubscriptionAPI(), endpoint: SubscriptionAPI.Path.customerEvents(sessionId: user.value?.sessionID ?? "")) else {
            return nil
        }
        return getSubscribedEvents(request: request, completion: completion)
    }

    private func getSubscribedEvents(request: URLRequest, completion: @escaping GetSubscribedEventsCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let model = try? JSONDecoder().decode(SubscribedEventsModel.self, from: body.data) else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }
                completion(.success(model))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func subscribeToEvent(eventId:Int,completion: @escaping SubscribeToEventCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: SubscriptionAPI(), endpoint: SubscriptionAPI.Path.subscribe(eventId: eventId, sessionId: user.value?.sessionID ?? "")) else {
            return nil
        }
        return subscribeToEvent(request: request, completion: completion)
    }

    private func subscribeToEvent(request: URLRequest, completion: @escaping SubscribeToEventCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let model = try? JSONDecoder().decode(SubscribeToEventModel.self, from: body.data) else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }
                completion(.success(model))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func unsubscribeFromEvent(eventId:Int,completion: @escaping SubscribeToEventCompletion) -> URLSessionDataTask? {
        guard let request = URLRequest(api: SubscriptionAPI(), endpoint: SubscriptionAPI.Path.unsubscribe(eventId: eventId, sessionId: user.value?.sessionID ?? "")) else {
            return nil
        }
        return unsubscribeFromEvent(request: request, completion: completion)
    }

    private func unsubscribeFromEvent(request: URLRequest, completion: @escaping SubscribeToEventCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            switch self?.handle(request: request, data: data, response: response, error: error) {
            case .success(let body):
                guard let model = try? JSONDecoder().decode(SubscribeToEventModel.self, from: body.data) else {
                    completion(.failure(URLError(.cannotParseResponse)))
                    return
                }
                completion(.success(model))
            case .failure(let error):
                completion(.failure(error))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    func cancelRunningChatViewTasks() {
        refreshChatOnChangeTask?.cancel()
        refreshChatTask?.cancel()
    }

//MARK: Deposit
    
    @discardableResult
    func getPaymentMethods(completion: @escaping GetPaymentMethodsCompletion) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID,
            let request = URLRequest(api: PaymentsAPI(), endpoint: PaymentsAPI.Path.getPaymentMethods(sessionId: sessionID)) else {
            return nil
        }
        return getPaymentMethods(request: request, completion: completion)
    }

    private func getPaymentMethods(request: URLRequest, completion: @escaping GetPaymentMethodsCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                let str = String(decoding: body.data, as: UTF8.self)
                print(str)
                guard var model = try? JSONDecoder().decode(GetPaymentMethodsResponse.self, from: body.data) else {
                    completion(.failure(.cannotParseResponse))
                    return
                }
                model.methods.sort { $0.sortOrder < $1.sortOrder }
                completion(.success(model))
            case .failure:
                completion(.failure(.unknown))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func startPayment(requestModel: PostStartPaymentRequest,completion: @escaping StartPaymentCompletion) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID,
            let request = URLRequest(api: PaymentsAPI(), endpoint: PaymentsAPI.Path.startPayment(sessionId: sessionID, requestModel: requestModel)) else {
            return nil
        }
        return startPayment(request: request, completion: completion)
    }

    private func startPayment(request: URLRequest, completion: @escaping StartPaymentCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                let str = String(decoding: body.data, as: UTF8.self)
                print(str)
                guard let model = try? JSONDecoder().decode(PostStartPaymentResponse.self, from: body.data) else {
                    completion(.failure(.cannotParseResponse))
                    return
                }
                completion(.success(model))
            case .failure:
                completion(.failure(.unknown))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func removeCardToken(token: String,completion: @escaping RemoveCardTokenCompletion) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID,
              let request = URLRequest(api: PaymentsAPI(), endpoint: PaymentsAPI.Path.removeCardToken(sessionId: sessionID, token: token)) else {
            return nil
        }
        return removeCardToken(request: request, completion: completion)
    }

    private func removeCardToken(request: URLRequest, completion: @escaping RemoveCardTokenCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                let str = String(decoding: body.data, as: UTF8.self)
                print(str)
//                guard let model = try? JSONDecoder().decode(PostStartPaymentResponse.self, from: body.data) else {
//                    completion(.failure(.cannotParseResponse))
//                    return
//                }
                completion(.success(()))
            case .failure:
                completion(.failure(.unknown))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
    
    @discardableResult
    func getStatus(orderID: String,completion: @escaping GetStatusCompletion) -> URLSessionDataTask? {
        guard let sessionID = user.value?.sessionID,
              let request = URLRequest(api: PaymentsAPI(), endpoint: PaymentsAPI.Path.getStatus(orderID: orderID)) else {
            return nil
        }
        return getStatus(request: request, completion: completion)
    }

    private func getStatus(request: URLRequest, completion: @escaping GetStatusCompletion) -> URLSessionDataTask? {
        let task = session.dataTask(with: request) { [weak self] (data, response, error) in
            let result = self?.handle(request: request, data: data, response: response, error: error)
            switch result {
            case .success(let body):
                let str = String(decoding: body.data, as: UTF8.self)
                print(str)
                guard let model = try? JSONDecoder().decode(GetPaymentStatusModel.self, from: body.data) else {
                    completion(.failure(.cannotParseResponse))
                    return
                }
                completion(.success(model))
            case .failure:
                completion(.failure(.unknown))
            case .none:
                break
            }
        }
        task.resume()
        return task
    }
}

extension NetworkManager: URLSessionDelegate {
    public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
        completionHandler(.useCredential, urlCredential)
    }
}