//
// Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// A copy of the License is located at
//
// http://aws.amazon.com/apache2.0
//
// or in the "license" file accompanying this file. This file 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

extension AWSMobileClient {

    /// Sign up with username, password and other attrbutes like phone, email.
    ///
    /// - Parameters:
    ///   - username: username of the user.
    ///   - password: password of the user
    ///   - userAttributes: user attributes which contain attributes like phone_number, email, etc.
    ///   - validationData: validation data for the user.
    ///   - clientMetaData: A map of custom key-value pairs that you can provide as input for any
    ///   custom workflows that this action triggers.
    ///   - completionHandler: completionHandler which will be called when a sign up result is available.
    public func signUp(username: String,
                       password: String,
                       userAttributes: [String: String] = [:],
                       validationData: [String: String] = [:],
                       clientMetaData: [String:String] = [:],
                       completionHandler: @escaping ((SignUpResult?, Error?) -> Void)) {

        if (self.userPoolClient == nil) {
            let message = """
            Cognito User Pools is not configured in `awsconfiguration.json`.
            Please add Cognito User Pools before using this API.
            """
            let error = AWSMobileClientError.userPoolNotConfigured(message: message)
            completionHandler(nil, error)
            return
        }

        let userAttributesTransformed = userAttributes.map {
            AWSCognitoIdentityUserAttributeType.init(name: $0, value: $1)
        }
        let validationDataTransformed = validationData.map {
            AWSCognitoIdentityUserAttributeType.init(name: $0, value: $1)
        }

        guard let userpoolOpsHelper = self.userpoolOpsHelper else {
            completionHandler(nil, Self.missingUserpoolOpsHelperError())
            return
        }

        self.userPoolClient?.signUp(
            username,
            password: password,
            userAttributes: userAttributesTransformed.count == 0 ? nil : userAttributesTransformed,
            validationData: validationDataTransformed.count == 0 ? nil : validationDataTransformed,
            clientMetaData: clientMetaData).continueWith { (task) -> Any? in
                if let error = task.error {
                    completionHandler(nil, AWSMobileClientError.makeMobileClientError(from: error))
                } else if let result = task.result {
                    userpoolOpsHelper.signUpUser = task.result?.user
                    var confirmedStatus: SignUpConfirmationState?
                    if(result.userConfirmed!.intValue == 1) {
                        confirmedStatus = .confirmed
                    } else {
                        confirmedStatus = .unconfirmed
                    }

                    var codeDeliveryDetails: UserCodeDeliveryDetails? = nil
                    if let deliveryDetails = result.codeDeliveryDetails {
                        switch(deliveryDetails.deliveryMedium) {
                        case .email:
                            codeDeliveryDetails = UserCodeDeliveryDetails(
                                deliveryMedium: .email,
                                destination: deliveryDetails.destination,
                                attributeName: deliveryDetails.attributeName)
                        case .sms:
                            codeDeliveryDetails = UserCodeDeliveryDetails(
                                deliveryMedium: .sms,
                                destination: deliveryDetails.destination,
                                attributeName: deliveryDetails.attributeName)
                        case .unknown:
                            codeDeliveryDetails = UserCodeDeliveryDetails(
                                deliveryMedium: .unknown,
                                destination: deliveryDetails.destination,
                                attributeName: deliveryDetails.attributeName)
                        @unknown default:
                            codeDeliveryDetails = UserCodeDeliveryDetails(
                                deliveryMedium: .unknown,
                                destination: deliveryDetails.destination,
                                attributeName: deliveryDetails.attributeName)
                        }
                    }
                    completionHandler(
                        SignUpResult(
                            signUpState: confirmedStatus!,
                            codeDeliveryDetails: codeDeliveryDetails,
                            userSub: result.userSub
                        ),
                        nil
                    )
                }
                return nil
            }
    }

    /// Confirms a sign up for a user using a verification code.
    ///
    /// - Parameters:
    ///   - username: username of the user.
    ///   - confirmationCode: confirmation code sent to the user.
    ///   - clientMetaData: A map of custom key-value pairs that you can provide as input for any
    ///   custom workflows that this action triggers.
    ///   - completionHandler: completionHandler which will be called when a result is available.
    public func confirmSignUp(username: String,
                              confirmationCode: String,
                              clientMetaData: [String:String] = [:],
                              completionHandler: @escaping ((SignUpResult?, Error?) -> Void)) {
        guard let userpoolOpsHelper = self.userpoolOpsHelper else {
            completionHandler(nil, Self.missingUserpoolOpsHelperError())
            return
        }
        if let user = userpoolOpsHelper.signUpUser, let uname = user.username, uname == username {
            confirmSignUp(user: user,
                          confirmationCode: confirmationCode,
                          clientMetaData: clientMetaData,
                          completionHandler: completionHandler)
        } else {
            let user = self.userPoolClient?.getUser(username)
            confirmSignUp(user: user!,
                          confirmationCode: confirmationCode,
                          clientMetaData: clientMetaData,
                          completionHandler: completionHandler)
        }
    }

    internal func confirmSignUp(user: AWSCognitoIdentityUser,
                                confirmationCode: String,
                                clientMetaData: [String:String] = [:],
                                completionHandler: @escaping ((SignUpResult?, Error?) -> Void)) {
        user.confirmSignUp(confirmationCode, clientMetaData: clientMetaData).continueWith { (task) -> Any? in
            if let error = task.error {
                completionHandler(nil, AWSMobileClientError.makeMobileClientError(from: error))
            } else if let _ = task.result {
                let signUpResult = SignUpResult(signUpState: .confirmed, codeDeliveryDetails: nil, userSub: nil)
                completionHandler(signUpResult, nil)
            }
            return nil
        }
    }

    /// Re-sends the sign up code to the user via email or phone_number.
    ///
    /// - Parameters:
    ///   - username: username of the user who wants a new registration code.
    ///   - clientMetaData: A map of custom key-value pairs that you can provide as input for any
    ///   custom workflows that this action triggers.
    ///   - completionHandler: completionHandler which will be called when a result is available.
    public func resendSignUpCode(username: String,
                                 clientMetaData: [String:String] = [:],
                                 completionHandler: @escaping ((SignUpResult?, Error?) -> Void)) {
        guard let userpoolOpsHelper = self.userpoolOpsHelper else {
            completionHandler(nil, Self.missingUserpoolOpsHelperError())
            return
        }
        if let user = userpoolOpsHelper.signUpUser, let uname = user.username, uname == username {
            resendSignUpCode(user: user, clientMetaData: clientMetaData, completionHandler: completionHandler)
        } else {
            let user = self.userPoolClient?.getUser(username)
            resendSignUpCode(user: user!, clientMetaData: clientMetaData, completionHandler: completionHandler)
        }
    }

    internal func resendSignUpCode(user: AWSCognitoIdentityUser,
                                   clientMetaData: [String:String] = [:],
                                   completionHandler: @escaping ((SignUpResult?, Error?) -> Void)) {
        user.resendConfirmationCode(clientMetaData).continueWith(block: { (task) -> Any? in
            if let error = task.error {
                completionHandler(nil, AWSMobileClientError.makeMobileClientError(from: error))
            } else if let result = task.result {
                let confirmedStatus: SignUpConfirmationState = .unconfirmed
                var codeDeliveryDetails: UserCodeDeliveryDetails? = nil
                if let deliveryDetails = result.codeDeliveryDetails {
                    codeDeliveryDetails = UserCodeDeliveryDetails.getUserCodeDeliveryDetails(deliveryDetails)
                }
                completionHandler(SignUpResult(signUpState: confirmedStatus, codeDeliveryDetails: codeDeliveryDetails, userSub: nil), nil)
            }
            return nil
        })
    }
}
