import Foundation

/// singleton API class that handles all requests
class API: NSObject {

    enum ServiceName: String {
        case userManagement = "UserManagement"
        case organizationUnit = "OrganizationUnit"
    }
    
    static let shared = API()
    let session = URLSession.shared
    
    /// main function for API calls
    /// - Parameters:
    ///   - object: input object structure
    ///   - result: output object structure
    ///   - serviceName: endpoint
    func call(object: Codable, result: XMLParserDelegate, serviceName: ServiceName) {
        checkRefreshToken()
        request(object: object, result: result, serviceName: serviceName)
    }
    
    /// wraper function that parses the object into a XML string representation
    /// - Parameter object: input object
    /// - Returns: XML string representation of the input object
    private func getBody(object: Codable) -> String {
        let mirror = Mirror(reflecting: object)
        let structName = "\(type(of: object))"
        let structNameInput = "\("\(type(of: object))".lowercaseFirstLetter())Input"
        var structContents = ""
        for child in mirror.children  {
            structContents.append("<\(child.label ?? "")>\(child.value)</\(child.label ?? "")>")
        }
        let body = "<?xml version=\"1.0\" encoding=\"utf-8\"?><soap12:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap12=\"http://www.w3.org/2003/05/soap-envelope\"><soap12:Body><\(structName) xmlns=\"https://is.skaut.cz/\"><\(structNameInput)>\(structContents)</\(structNameInput)></\(structName)></soap12:Body></soap12:Envelope>"
        return body
    }
    
    /// function for checking validation of refresh token, if it is not valit it will force a refresh
    private func checkRefreshToken() {
        guard let date = UserDefaults.dateLoggout else {
            requestRefresh()
            return
        }
        let logoutDate = Date.getDateFromString(string: date).timeIntervalSince1970
        let now = Date().timeIntervalSince1970
        if (now + 1000 > logoutDate) {
            requestRefresh()
        }
    }
    
    ///  requuesting function that sends the data to coresponding URL and calles a parses when recieves a valid response
    /// - Parameters:
    ///   - object: input object structure
    ///   - result: output object structure
    ///   - serviceName: endpoint
    private func request(object: Codable, result: XMLParserDelegate, serviceName: ServiceName) {
        let body = getBody(object: object)
        let url = URL(string: "https://test-is.skaut.cz/JunakWebservice/\(serviceName.rawValue).asmx")
        var request = URLRequest(url: url!)
        request.httpMethod = "POST"
        request.httpBody = body.data(using: .utf8)
        let structName = "\(type(of: object))"
        request.addValue("https://is.skaut.cz/\(structName)", forHTTPHeaderField: "SoapAction")
        request.addValue("text/xml; charset=utf-8", forHTTPHeaderField: "Content-Type")
        session.dataTask(with: request) {(data, response, error) in
            if let error = error {
                print("Error took place \(error)")
                return
            }
            if let data = data, let dataString = String(data: data, encoding: .utf8) {
                print("Response data string:\n \(dataString)")
            }
            if let data = data {
                let parser = XMLParser(data: data)
                parser.delegate = result
                parser.parse()
            }
        }.resume()
    }
    
    /// request for refreshing a user token
    private func requestRefresh() {
        guard let token = UserDefaults.userToken else { return }
        let result = LoginUpdateRefreshResult(completion: { result in
            guard let res = result as? LoginUpdateRefreshResult else { return }
            UserDefaults.dateLoggout = res.DateLogout
        })
        request(object: LoginUpdateRefresh(ID: token), result: result, serviceName: .userManagement)
    }
}

