//
//  MessageAPI.swift
//  CovidSafe
//
//  Copyright © 2020 Australian Government. All rights reserved.
//

import Foundation
import Alamofire

class MessageAPI: CovidSafeAuthenticatedAPI {
    
    static let keyLastApiUpdate = "keyLastApiUpdate"
    static let keyLastVersionChecked = "keyLastVersionChecked"
    
    static func getMessagesIfNeeded(completion: @escaping (MessageResponse?, CovidSafeAPIError?) -> Void) {
        if shouldGetMessages() {
            getMessages(completion: completion)
        }
    }
    
    static func getMessages(completion: @escaping (MessageResponse?, CovidSafeAPIError?) -> Void) {
        guard let token = UserDefaults.standard.string(forKey: "deviceTokenForAPN") else {
            completion(nil, .RequestError)
            return
        }
        //Get relevat encounter data
        guard let persistentContainer =
            EncounterDB.shared.persistentContainer else {
                completion(nil, .RequestError)
                return
        }
        let managedContext = persistentContainer.newBackgroundContext()
        guard let encounterLastWeekRequest = Encounter.fetchEncountersInLast(days: 7) else {
            completion(nil, .RequestError)
            return
        }
        
        do {
            //fetch last week encounters count
            let weekEncounters = try managedContext.count(for: encounterLastWeekRequest)
            let healthcheck = BluetraceManager.shared.isBluetoothOn() && BluetraceManager.shared.isBluetoothAuthorized() ?
                healthCheckParamValue.OK :
                healthCheckParamValue.POSSIBLE_ERROR
            let encounterCheck = weekEncounters > 0 ? healthCheckParamValue.OK : healthCheckParamValue.POSSIBLE_ERROR
            
            // Make API call to get messages
            let messageRequest = MessageRequest(remotePushToken: token, healthcheck: healthcheck, encountershealth: encounterCheck)
            getMessages(msgRequest: messageRequest, completion: completion)
            
        } catch let error as NSError {
            completion(nil, .RequestError)
            DLog("Could not fetch encounter(s) from db. \(error), \(error.userInfo)")
        }
    }
    
    private static func shouldGetMessages() -> Bool {
        guard isBusy == false else {
            return false
        }
        
        let lastChecked = UserDefaults.standard.double(forKey: keyLastApiUpdate)
        let versionChecked = UserDefaults.standard.integer(forKey: keyLastVersionChecked)
        
        var shouldGetMessages = true
        
        let calendar = NSCalendar.current
        let currentDate = Date()
        
        // if the current version is newer than the last version checked, allow messages call
        if let currVersionStr = Bundle.main.version, let currVersion = Int(currVersionStr), currVersion > versionChecked {
            return true
        }
        
        if lastChecked > 0 {
            let lastCheckedDate = Date(timeIntervalSince1970: lastChecked)
            let components = calendar.dateComponents([.hour], from: lastCheckedDate, to: currentDate)
            
            if let numHours = components.hour {
                shouldGetMessages = numHours >= 4
            }
        }
        
        return shouldGetMessages
    }
    
    private static func getMessages(msgRequest: MessageRequest,
                                    completion: @escaping (MessageResponse?, CovidSafeAPIError?) -> Void) {
        guard let apiHost = PlistHelper.getvalueFromInfoPlist(withKey: "API_Host", plistName: "CovidSafe-config") else {
            completion(nil, .RequestError)
            return
        }
        
        let preferredLanguages = Locale.preferredLanguages.count > 5 ? Locale.preferredLanguages[0...5].joined(separator: ",") : Locale.preferredLanguages.joined(separator: ",")
        
        var params: [String : Any] = [
            "os" : "ios-\(UIDevice.current.systemVersion)",
            "healthcheck" : msgRequest.healthcheck.rawValue,
            "encountershealth" : msgRequest.encountershealth.rawValue,
            "preferredlanguages": preferredLanguages
        ]

        if let buildString = Bundle.main.version {
            params["appversion"] = "\(buildString)"
        }
        if let remoteToken = msgRequest.remotePushToken {
            params["token"] = remoteToken
        }
        
        isBusy = true
        
        guard let authHeaders = try? authenticatedHeaders() else {
            completion(nil, .RequestError)
            return
        }
        
        CovidNetworking.shared.session.request("\(apiHost)/messages",
            method: .get,
            parameters: params,
            headers: authHeaders,
            interceptor: CovidRequestRetrier(retries: 3)
        ).validate().responseDecodable(of: MessageResponse.self) { (response) in
            switch response.result {
            case .success:
                guard let messageResponse = response.value else { return }
                
                // save successful timestamp
                let minutesToDefer = Int.random(in: 0..<30)
                let calendar = NSCalendar.current
                let currentDate = Date()
                if let deferredDate = calendar.date(byAdding: .minute, value: minutesToDefer, to: currentDate) {
                    UserDefaults.standard.set(deferredDate.timeIntervalSince1970, forKey: keyLastApiUpdate)
                } else {
                    UserDefaults.standard.set(currentDate.timeIntervalSince1970, forKey: keyLastApiUpdate)
                }
                UserDefaults.standard.set(Bundle.main.version, forKey: keyLastVersionChecked)
                
                isBusy = false
                completion(messageResponse, nil)
            case .failure(_):
                guard let statusCode = response.response?.statusCode else {
                    completion(nil, .UnknownError)
                    return
                }
                
                if (statusCode == 200) {
                    completion(nil, .ResponseError)
                    return
                }
                
                if statusCode == 401, let respData = response.data {
                    completion(nil, processUnauthorizedError(respData))
                    return
                }
                if (statusCode >= 400 && statusCode < 500) {
                    completion(nil, .RequestError)
                    return
                }
                
                isBusy = false
                completion(nil, .ServerError)
            }
        }
    }
}

enum healthCheckParamValue: String {
    case OK = "OK"
    case POSSIBLE_ERROR = "POSSIBLE_ERROR"
    case ERROR = "ERROR"
}

struct MessageRequest {
    var remotePushToken: String?
    var healthcheck: healthCheckParamValue
    var encountershealth: healthCheckParamValue
}

struct MessageResponse: Decodable {
    let messages: [Message]?
    let forceappupgrade: Bool
    
    enum CodingKeys: String, CodingKey {
        case messages
        case forceappupgrade
    }
}

struct Message: Decodable {
    let title: String?
    let body: String?
    let destination: String?
    
    enum CodingKeys: String, CodingKey {
        case title
        case body
        case destination
    }
}
