//
/*
 * Copyright (c) 2022 BWI GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import Foundation
import SwiftJWT

struct ServerTokenClaims: Claims {
    let issuer: String
    let sub: String
    let exp: Date?
    let iat: Date?
    let jti: String
    let version: Int
}

// verifies if a selected homeserver is valid to be used with bundesmessenger
struct ServerTokenVerificator {
      
    // takes a token and the selected server url, returns true if token is valid for at least one public key in the subfolder publickeys. Additionally the token needs to be valid (inside the valid timestamp) and contain a matching homeserver
    func verifyToken( baseURL: String, tokens: [String] ) -> Bool {
        let publicKeys = publicKeys(folder: Bundle.main.resourcePath! + "/publickeys" )
 
        let homeServerURL = baseURL.replacingOccurrences(of: "https://", with: "")
        
        for key in publicKeys {
            guard let keyData = key.data(using: .utf8) else {
                continue
            }
            
            // only one public key needs to be fine
            let jwtVerifier = JWTVerifier.ps512(publicKey: keyData)
            do {
                for token in tokens {
                    let verified = JWT<ServerTokenClaims>.verify(token, using: jwtVerifier)
                    let verifiedJWT = try JWT<ServerTokenClaims>(jwtString: token, verifier: jwtVerifier)
                    let validated = verifiedJWT.validateClaims()
                    let matchingHomeServer = verifiedJWT.claims.sub == homeServerURL
                    if verified && (validated == .success) && matchingHomeServer {
                        return true
                    }
                }
            } catch {
                // counts like an unverified Token
            }
        }
        
        // no public key was able to verify the token
        return false
    }
    
    // fetch the current token from the new endpoint,
    func fetchToken( baseURL: String ) async -> [String]? {
        let path = "/_bum/client/v1/verify"
        
        guard let url = URL(string: baseURL + path), let safeURL = url.ensureHTTPS() else {
            return nil
        }
        
        do {
            let config = URLSessionConfiguration.default
            config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
            let session : URLSession = URLSession(configuration: config)
            
            let (data, _) = try await session.data(from: safeURL)
            
            let fetchedStrings = try JSONDecoder().decode([String].self, from: data)
            
            return fetchedStrings
        } catch (let error) {
            if let error = error as? URLError, error.code == .notConnectedToInternet {
                return nil
            }
            return [String]()
        }
    }
    
    // reads the current public key folder into a string array of public keys
    private func publicKeys(folder: String) -> [String] {
        var keys: [String] = []
        
        let fm = FileManager.default
        
        do {
            let items = try fm.contentsOfDirectory(atPath: folder)
            
            for item in items {
                let fileUrl = URL(fileURLWithPath: folder + "/" + item)
                
                do {
                    let key = try String(contentsOf: fileUrl, encoding: .utf8)
                    keys.append(key)
                } catch {
                    // invalid files do not count as public keys and are just not inserted into the array
                }
            }
        } catch {
            // should not happen but in this case there are no public keys == no verification
        }

        return keys
    }
}
