//
//  AuthManager.swift
//  AppleTVE
//
//  Copyright © 2016 Apple, Inc. All rights reserved.
//

import Foundation
import UIKit
import VideoSubscriberAccount


/*#
 For the purposes of this application we have created a simple protocol
 which is used to present status messages to the user.
 */
protocol AMPresenter {
    func presentMessage(_ title: String, message: String)
    func presentViewController(_ viewController: UIViewController)
    func dismissViewController(_ viewController: UIViewController)
}

class AuthManager: NSObject, VSAccountManagerDelegate {
    
    /*#
     You can create a single instance of VSAccountManager and perform all of 
     you're requests from this object.
     */
    let accountManager = VSAccountManager()
    
    var presenter: AMPresenter
    var authorizationStatus : AuthorizationStatus = Storage.value(forKey: StorageKeys.authorizationStatus) as? AuthorizationStatus ?? .undefined
    
    var token : String? {
        get { return Storage.string(forKey: StorageKeys.token) }
        set { Storage.set(newValue, forKey: StorageKeys.token) }
    }
    
    var authenticated: Bool {
        get {
            var auth = Storage.bool(forKey: StorageKeys.loggedIn);
            
            if(auth){
                let authExpDate = (Storage.object(forKey: StorageKeys.expirationDate)) as! Date
                if (authExpDate < Date()) {
                    print("true but should be false")
                    Storage.set(false, forKey: StorageKeys.loggedIn)
                    Storage.set(nil, forKey: StorageKeys.expirationDate)
                    auth = false
                }
            }
    
            return auth ? auth : false

        }
        
        set { Storage.set(newValue, forKey: StorageKeys.loggedIn) }
    
    }
    
    init(_ presenter: AMPresenter ) {
        self.presenter = presenter
        
        super.init()
        
        IdentityProviderList.loadIdentityProviders()
        
        accountManager.delegate = self
    }
    
    
    /*#
         The first step to implementing VSA support is to check if your app has been granted access to the
         TV Provider account information. When making this request you can perform a silent check, or you
         request to prompt the user for permission.
         
         You will receive a VSAccountAccessStatus response, based on this response you can proceed with your 
         application logic. 
         
         Some best practices:
         1. You may want to check the status for your Application upon entry so that you are able to 
            personalize the app experience. It would be appropriate to prompt the user for access at
            this point rather than simply checking the current status.
         2. Users can turn off access to the TV Provider account for your Application. You should check for
            access when making calls to VideoSubscriberKit, and gracefully fallback to legacy authentication 
            if the user has turned off access.
     */

    func accessCheck(_ shouldPrompt: Bool) {

        accountManager.checkAccessStatus(options: [VSCheckAccessOption.prompt: shouldPrompt], completionHandler: { (status, error) in
            let message: String = AccessMessage[status]!
            self.presenter.presentMessage(VSAAccessTitle, message: message)

            print("error code: ", (error as? VSError)?.errorCode ?? "none", " status code: ", status.rawValue)

        })
    }
    
    
    /*#
     To check the current authentication status and/or retrieve the account information
     specific to your app for the current account, you will create a VSAccountMetadataRequest.
     Several options are available on this request, including the ability to silently check for 
     an existing TV provider.
     
     If you create a VSAccountMetadataRequest and only request the Provider Identifier and
     Expiration Date, the system will return this information (if it exists).
     */
    func authenticationCheck(_ allowInterruption : Bool, completionHandler: @escaping (Bool) -> Swift.Void) {
        
        let request = VSAccountMetadataRequest()
        request.includeAccountProviderIdentifier = true
        request.includeAuthenticationExpirationDate = true
        request.isInterruptionAllowed = allowInterruption

        if #available(tvOS 13.0, iOS 13.0, *) {
            request.supportedAuthenticationSchemes = [VSAccountProviderAuthenticationScheme.api]
        } else if #available(tvOS 10.1, iOS 10.2, *) {
            request.supportedAuthenticationSchemes = [VSAccountProviderAuthenticationScheme(rawValue: "API")]
        }
        
        send(request, completionHandler: completionHandler)
    }
    
    func accountMetadata() {
        
        if let providerId = Storage.string(forKey: StorageKeys.accountIdentifier) , let currentProvider = IdentityProviderList.identityProviderForId(providerId) {
            
            //Setup session to get verification token
            getVerificationToken(currentProvider, completionHandler: { (status, token) in
                                
                if status && (token != nil) {

                    //Create metadata request with verification token
                    let request = VSAccountMetadataRequest()
                    request.verificationToken = token
                    request.channelIdentifier = RequestProperties.identifier
                    request.includeAccountProviderIdentifier = true
                    request.includeAuthenticationExpirationDate = true
                    request.isInterruptionAllowed = true
                    request.attributeNames = RequestProperties.attributes

                    if #available(tvOS 13.0, iOS 13.0, *) {
                        request.supportedAuthenticationSchemes = [VSAccountProviderAuthenticationScheme.api]
                    } else if #available(tvOS 10.1, iOS 10.2, *) {
                        request.supportedAuthenticationSchemes = [VSAccountProviderAuthenticationScheme(rawValue: "API")]
                    }
                    
                    self.send(request, completionHandler: { (status) in
                        if status {
                            self.serviceProviderToken()
                        }
                    })
                }

            })
            
        } else if let providerId = Storage.string(forKey: StorageKeys.accountIdentifier) , IdentityProviderList.identityProviderForId(providerId) == nil{
            self.presenter.presentMessage(Messages.unsupportedProviderTitle, message: Messages.unsupportedProviderMessage.format(providerId))
        }else{
            authenticationCheck(true, completionHandler: { (status) in
                if status {
                    self.accountMetadata()
                }
            })
        }
        
    }
    
    func send(_ request: VSAccountMetadataRequest, completionHandler: @escaping (Bool) -> Swift.Void) {
        
        DispatchQueue.main.async {
            self.accountManager.enqueue(request) { (userMetadata, error) in
                var status : Bool = false
                var message = Messages.notAuthenticated
                
                if let vsError = error as? VSError {
                    message = self.createErrorString(vsError: vsError)
                }
                
                if let provider = userMetadata?.accountProviderIdentifier {
                    Storage.set(provider, forKey: StorageKeys.accountIdentifier)

                    var dateString:String = Messages.authenticationExpired;
                    
                    if let date = userMetadata?.authenticationExpirationDate {
                        dateString = DateFormatter.localizedString(from: date, dateStyle: .full, timeStyle: .short)
                        Storage.set(date, forKey:StorageKeys.expirationDate)
                    }
                    
                    if let response : String = userMetadata?.samlAttributeQueryResponse{
                        Storage.set(response, forKey: StorageKeys.userMetadata)
                    }

                    if #available(tvOS 10.1, iOS 10.2, *) {
                        if let responseOAuth : String = userMetadata?.accountProviderResponse?.body{
                            Storage.set(responseOAuth, forKey: StorageKeys.userMetadata)
                        }
                    } else {
                            // Fallback on earlier versions
                    }
                    
                    message = Messages.authenticationCheck.format(provider, dateString)
                    status = true
                    
                }
                
                self.presenter.presentMessage(Messages.authenticationTitle.format("SSO"), message: message)
                completionHandler(status)
            }
        }
    }

    func getVerificationToken(_ provider : IdentityProvider, completionHandler : @escaping (Bool,String?) -> Swift.Void) {
        let provider = provider.name.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
        let urlString = Endpoints.verificationToken + provider!
        let url = URL(string: urlString)
        
        let sessionTask = URLSession.shared.dataTask(with: url!, completionHandler: { [unowned self] (data, response, error) in
            var token : String?
            var status : Bool = false
            var message : String
            
            if let error = error {
                message = error.localizedDescription
            }
            
            let httpResponse = response as? HTTPURLResponse
            
            if httpResponse?.statusCode == 200 {
                token = String(bytes: data!, encoding: String.Encoding.utf8)
                status = true
                message = Messages.verificationTokenMessage.format(token!)
            }else{
                if((response) != nil){
                    message = "HTTP Error Code: " + (httpResponse?.statusCode.description)! + " " + HTTPURLResponse.localizedString(forStatusCode: (httpResponse?.statusCode)!)
                }else{
                    message = "SP Server is unreachable"
                }
            }
            
            self.presenter.presentMessage(Messages.verificationTokenTitle, message: message)
            completionHandler(status, token)
            })
        
        sessionTask.resume()
    }
    
    func serviceProviderToken() {
        
        let postString : String = (Storage.string(forKey: StorageKeys.userMetadata))!
        let urlString = Endpoints.authentication
        let url = URL(string: urlString)
        var request = URLRequest(url: url!)
        request.httpMethod = "POST"
        request.httpBody = "response=\(postString)".data(using: String.Encoding.utf8)
        
        let tokenSession = URLSession.shared.dataTask(with: request) { data, response, error in
            var message : String = Messages.authenticationTokenMessage
            
            if(error != nil){
                if let localizedDescription = error?.localizedDescription {
                    message = localizedDescription
                } else {
                    message = "no localizedDescription"
                }
            }else{
                let jsonResponse : Dictionary<String,AnyObject>
                do {
                    jsonResponse = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! Dictionary
                    if let authToken = jsonResponse["authN"] as? String {
                        self.token = authToken
                        self.authenticated = true
                    }
                } catch {
                    print("Error parsing JSON Response")
                }
            }
            
            self.presenter.presentMessage(Messages.authenticationTokenTitle, message: message)
            
        }
        
        tokenSession.resume()
    }
    
    func localAuthCheck() {
        var  message : String = Messages.authenticationUnset
        
        if authenticated {
            
            if let date = Storage.object(forKey: StorageKeys.expirationDate) {
                let dateString = DateFormatter.localizedString(from: date as! Date, dateStyle: .full, timeStyle: .short)
                let provider = Storage.string(forKey: StorageKeys.accountIdentifier)!
                message = Messages.authenticationCheck.format(provider, dateString)
            }
            
        }
        
        self.presenter.presentMessage(Messages.authenticationTitle.format("App"), message: message)
    }
    
    func clearAppStorage() {
        let  message = Messages.clearAppStorageMessage
        
        authenticated = false
        authorizationStatus = .undefined
        Storage.removeObject(forKey: StorageKeys.accountIdentifier)
        Storage.removeObject(forKey: StorageKeys.authorizationStatus)
        Storage.removeObject(forKey: StorageKeys.expirationDate)
        Storage.removeObject(forKey: StorageKeys.loggedIn)
        Storage.removeObject(forKey: StorageKeys.token)
        Storage.removeObject(forKey: StorageKeys.userMetadata)

        presenter.presentMessage(Messages.clearAppStorageTitle, message: message)
    }
    
    
    func authorizationCheck() {
        presenter.presentMessage(Messages.authorizationStatusTitle, message: authorizationStatus.rawValue)
    }
    
    func authorizationRequest() {
        
        if authenticated {
            
            if let authToken = token?.addingPercentEncoding(withAllowedCharacters: AuthTokenCharacterSet), let url = URL(string: Endpoints.authorization) {
                let postBody = ("authToken=\(authToken)&channelID=SSO_Tester")
                
                var urlRequest = URLRequest(url: url)
                urlRequest.httpMethod = "POST"
                urlRequest.httpBody = postBody.data(using: String.Encoding.utf8)
                
                let sessionTask = URLSession.shared.dataTask(with:urlRequest, completionHandler: { (data, response, error) in
                    if let err = error {
                        print("Error: " + err.localizedDescription)
                    }
                    
                    var isAuthed : String? = "false"
                    let jsonResponse : Dictionary<String,AnyObject>
                    do {
                        jsonResponse = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! Dictionary
                        if let authed = jsonResponse["authZ"] as? String {
                            isAuthed = authed
                        }
                    } catch {
                        print("Error parsing JSON Response")
                    }
                    
                    if(isAuthed! == "true"){
                        self.authorizationStatus = .granted
                        Storage.set(self.authorizationStatus.rawValue , forKey: StorageKeys.authorizationStatus)
                        self.presenter.presentMessage("Request Authorization", message: Messages.authorizationSuccess)
                    }else{
                        self.authorizationStatus = .denied
                        Storage.set(self.authorizationStatus.rawValue , forKey: StorageKeys.authorizationStatus)
                        self.presenter.presentMessage("Request Authorization", message: Messages.authorizationFail)
                    }
                })
                
                sessionTask.resume()
            }
            
        }else{
            presenter.presentMessage("Request Authorization", message: "User Not Logged In to App")
        }
    }
    
    func authenticationShare(completionHandler: @escaping (Bool) -> Swift.Void) {
        IdentityProviderList.loadIdentityProviders()
        guard let accountIdentifier = IdentityProviderList.identityProviders.first?.identifier else {
            print("Missing Account Identifier")
            completionHandler(false)
            return
        }
        // Actual existing app-level authentication tokens should be set on the VSAccountMetadataRequest
        let userAuthToken = "FakeTokenForThisSampleApp"
        
        let request = VSAccountMetadataRequest()
        request.supportedAccountProviderIdentifiers = [accountIdentifier]
        request.isInterruptionAllowed = true
        request.includeAccountProviderIdentifier = true
        request.includeAuthenticationExpirationDate = true
        
        if #available(iOS 13.0, tvOS 13.0, *) {
            request.supportedAuthenticationSchemes = [.api]
            request.accountProviderAuthenticationToken = userAuthToken
        } else {
            fatalError("Authentication Share is not available below iOS & tvOS 13.0")
        }
        
        print("Starting Authentication Share")
        send(request, completionHandler: completionHandler)
    }
    
    func openSettings() {
        let settingsURL = URL(string: UIApplication.openSettingsURLString)
        UIApplication.shared.open(settingsURL!, options: [:]) { (complete) in
            print("Open settings: \(complete)")
        }
    }
    
    func createErrorString(vsError error: VSError) -> String {
        
        var errorString: String = ("Error Code: " + String(error.errorCode) + " ")
        
        switch error.errorCode {
        case 0: // The user has not granted the app access to their subscription information. VSErrorCodeAccessNotGranted
            
            errorString += error.localizedDescription
            return errorString

        case 1: // The system does not currently support the user's subscription provider. VSErrorCodeUnsupportedProvider
            
            if let providerID = error.userInfo["VSErrorInfoKeyUnsupportedProviderIdentifier"] as? String {
                errorString += providerID
            }else{
                errorString += "Unknown Provider"
            }
            errorString += " : "
            errorString += error.userInfo["NSLocalizedDescription"] as! String
            
            return errorString
            
        case 2: // The request was cancelled by the user. VSErrorCodeUserCancelled
            
            errorString += error.localizedDescription
            return errorString
            
        case 3: // The request failed, but a subsequent attempt might succeed. VSErrorCodeServiceTemporarilyUnavailable
            
            // let underlyingErrorCode = error.userInfo["NSUnderlyingError"] as? NSError)?.code
            errorString += error.localizedDescription
            return errorString
            
        case 4: // The user's subscription provider did not allow the request to proceed, Interactive reauthentication is required but the request does not allow interruption. VSErrorCodeProviderRejected
            
            errorString += error.localizedDescription
            return errorString
            
        case 5: // The request's verification token was rejected by the user's subscription provider. VSErrorCodeInvalidVerificationToken
            
            errorString += error.localizedDescription
            return errorString
            
        default:
            return "this is for VSErrors"
        }
    }
    
    
    // MARK: VSAccountManagerDelegate
    
    func accountManager(_ accountManager: VSAccountManager, dismiss viewController: UIViewController) {
        presenter.dismissViewController(viewController)
    }
    
    func accountManager(_ accountManager: VSAccountManager, present viewController: UIViewController) {
        presenter.presentViewController(viewController)
    }
    
}
