/*
 * Copyright 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.
 */
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using Amazon.CognitoIdentityProvider;
using Amazon.CognitoIdentityProvider.Model;
using System.Linq;
using Amazon.Extensions.CognitoAuthentication.Util;
using System.Threading;
namespace Amazon.Extensions.CognitoAuthentication
{
    public partial class CognitoUserPool
    {
        /// 
        /// The poolID associated with the user pool. PoolID can only be configured 
        /// through the constructor, and once set it cannot be changed.
        /// 
        public string PoolID { get; private set; }
        /// 
        /// The clientID associated with the user pool. ClientID can only be configured 
        /// through the constructor, and once set it cannot be changed.
        /// 
        public string ClientID { get; private set; }
        /// 
        /// The ClientConfiguration associated with the user pool and the ClientID.
        /// 
        private CognitoUserPoolClientConfiguration ClientConfiguration { get; set; }
        
        internal IAmazonCognitoIdentityProvider Provider { get; set; }
        private string ClientSecret { get; set; }
        /// 
        /// Create an instance of CognitoUserPool
        /// 
        /// PoolID of the associated user pool
        /// ClientID for the associated user pool
        /// IAmazonCognitoIdentityProvider for the specified user pool
        /// Client secret for the corresponding clientID, if exists
        public CognitoUserPool(string poolID, 
                               string clientID,
                               IAmazonCognitoIdentityProvider provider, 
                               string clientSecret = null)
        {
            if(!poolID.Contains("_"))
            {
                throw new ArgumentException("PoolID should be of the form _.", "poolID");
            }
            this.PoolID = poolID;
            this.ClientID = clientID;
            this.ClientSecret = clientSecret;
            this.Provider = provider;
            if (this.Provider is AmazonCognitoIdentityProviderClient eventProvider)
            {
                eventProvider.BeforeRequestEvent += CognitoAuthHelper.ServiceClientBeforeRequestEvent;
            }
        }
        /// 
        /// Signs up the user with the specified parameters using an asynchronous call
        /// 
        /// The userID of the user being created
        /// The password of the user being created
        /// The user attributes of the user being created
        /// The validation data of the user being created
        /// Returns the delivery details for the sign up request
        public Task SignUpAsync(string userID,
                           string password,
                           IDictionary userAttributes,
                           IDictionary validationData)
        {
            return SignUpAsync(userID, password, userAttributes, validationData, default);
        }
        /// 
        /// Signs up the user with the specified parameters using an asynchronous call
        /// 
        /// The userID of the user being created
        /// The password of the user being created
        /// The user attributes of the user being created
        /// The validation data of the user being created
        /// A cancellation token that can be used by other objects or threads to receive notice of cancellation
        /// Returns the delivery details for the sign up request
        public Task SignUpAsync(string userID,
                           string password,
                           IDictionary userAttributes,
                           IDictionary validationData,
                           CancellationToken cancellationToken)
        {
            SignUpRequest signUpUserRequest = CreateSignUpRequest(userID, password, userAttributes, validationData);
            return Provider.SignUpAsync(signUpUserRequest, cancellationToken);
        }
        /// 
        /// Internal method to aid in the sign up flow for a new user
        /// 
        /// The userID of the user being created
        /// The password of the user being created
        /// The user attributes of the user being created
        /// The validation data of the user being created
        /// Returns the SignUpResponse for the sign up API request using the provided information
        private SignUpRequest CreateSignUpRequest(string userID,
                                              string password,
                                              IDictionary userAttributes,
                                              IDictionary validationData)
        {
            List userAttributesList = null;
            if (userAttributes != null)
            {
                userAttributesList = CognitoAuthHelper.CreateAttributeList(userAttributes);
            }
            else
            {
                throw new ArgumentNullException("userAttributes", "userAttributes cannot be null.");
            }
            List validationDataList = 
                validationData != null ? CognitoAuthHelper.CreateAttributeList(validationData) : null;
            // Create User registration request
            SignUpRequest signUpUserRequest = new SignUpRequest()
            {
                Username = userID,
                Password = password,
                ClientId = ClientID,
                UserAttributes = userAttributesList,
                ValidationData = validationDataList
            };
            if (!string.IsNullOrEmpty(ClientSecret))
            {
                signUpUserRequest.SecretHash = CognitoAuthHelper.GetUserPoolSecretHash(userID, ClientID, ClientSecret);
            }
            return signUpUserRequest;
        }
        /// 
        /// Gets a CognitoUser with no userID set
        /// 
        /// Returns a user with no userID set
        public virtual CognitoUser GetUser()
        {
            return new CognitoUser(null, ClientID, this, Provider, ClientSecret);
        }
        /// 
        /// Gets a CognitoUser with the corresponding userID
        /// 
        /// The userID of the corresponding user
        /// Returns a CognitoUser with the corresponding userID
        public virtual CognitoUser GetUser(string userID)
        {
            if (string.IsNullOrEmpty(userID))
            {
                return GetUser();
            }
            return new CognitoUser(userID, ClientID, this, Provider, ClientSecret);
        }
        /// 
        /// Gets a CognitoUser with the corresponding userID, status and attributes
        /// 
        /// The userID of the corresponding user
        /// The status of the corresponding user
        /// The attributes of the corresponding user
        /// Returns a CognitoUser with the corresponding userID
        public virtual CognitoUser GetUser(string userID, string status, Dictionary attributes)
        {
            if (string.IsNullOrEmpty(userID))
            {
                return GetUser();
            }
            return new CognitoUser(userID, ClientID, this, Provider, ClientSecret, status, userID, attributes);
        }
        /// 
        /// Queries Cognito and returns the CognitoUser with the corresponding userID
        /// 
        /// The userID of the corresponding user
        /// The  that represents the asynchronous operation, containing a CognitoUser with the corresponding userID, with the Status and Attributes retrieved from Cognito.
        public virtual async Task FindByIdAsync(string userID)
        {
            return await FindByIdAsync(userID, default);
        }
        /// 
        /// Queries Cognito and returns the CognitoUser with the corresponding userID
        /// 
        /// The userID of the corresponding user
        /// A cancellation token that can be used by other objects or threads to receive notice of cancellation
        /// The  that represents the asynchronous operation, containing a CognitoUser with the corresponding userID, with the Status and Attributes retrieved from Cognito.
        public virtual async Task FindByIdAsync(string userID, CancellationToken cancellationToken)
        {
            if (string.IsNullOrEmpty(userID))
                throw new ArgumentException(nameof(userID));
            try
            {
                var response = await Provider.AdminGetUserAsync(new AdminGetUserRequest
                {
                    Username = userID,
                    UserPoolId = this.PoolID
                }, cancellationToken).ConfigureAwait(false);
                return new CognitoUser(response.Username, ClientID, this, Provider, ClientSecret,
                    response.UserStatus.Value, response.Username,
                    response.UserAttributes.ToDictionary(attribute => attribute.Name, attribute => attribute.Value));
            }
            catch (UserNotFoundException)
            {
                return null;
            }
        }
        /// 
        /// Queries Cognito and returns the PasswordPolicyType associated with the pool.
        /// 
        /// The  that represents the asynchronous operation, containing the PasswordPolicyType of the pool.
        public async Task GetPasswordPolicyTypeAsync()
        {
            return await GetPasswordPolicyTypeAsync(default);
        }
        /// 
        /// Queries Cognito and returns the PasswordPolicyType associated with the pool.
        /// 
        /// A cancellation token that can be used by other objects or threads to receive notice of cancellation
        /// The  that represents the asynchronous operation, containing the PasswordPolicyType of the pool.
        public async Task GetPasswordPolicyTypeAsync(CancellationToken cancellationToken)
        {
            var response = await Provider.DescribeUserPoolAsync(new DescribeUserPoolRequest
            {
                UserPoolId = this.PoolID
            }, cancellationToken).ConfigureAwait(false);
            return response.UserPool.Policies.PasswordPolicy;
        }
        /// 
        /// Queries Cognito and returns the CognitoUserPoolClientConfiguration associated with the current pool client.
        /// Caches the value in the ClientConfiguration private property.
        /// 
        /// The  that represents the asynchronous operation, containing the PasswordPolicyType of the pool.
        public async Task GetUserPoolClientConfiguration()
        {
            return await GetUserPoolClientConfiguration(default);
        }
        /// 
        /// Queries Cognito and returns the CognitoUserPoolClientConfiguration associated with the current pool client.
        /// Caches the value in the ClientConfiguration private property.
        /// 
        /// A cancellation token that can be used by other objects or threads to receive notice of cancellation
        /// The  that represents the asynchronous operation, containing the PasswordPolicyType of the pool.
        public async Task GetUserPoolClientConfiguration(CancellationToken cancellationToken)
        {
            if (ClientConfiguration == null)
            {
                var response = await Provider.DescribeUserPoolClientAsync(new DescribeUserPoolClientRequest
                {
                    ClientId = this.ClientID,
                    UserPoolId = this.PoolID
                }, cancellationToken).ConfigureAwait(false);
                ClientConfiguration = new CognitoUserPoolClientConfiguration(response.UserPoolClient.ReadAttributes, response.UserPoolClient.WriteAttributes);
            }
            return ClientConfiguration;
        }
        /// 
        /// Signs up the user with the specified parameters using an asynchronous call end triggers a temporary password sms or email message.
        /// 
        /// The userID of the user being created
        /// The user attributes of the user being created
        /// The validation data of the user being created
        /// Returns the delivery details for the sign up request
        public Task AdminSignupAsync(string userID,
                           IDictionary userAttributes,
                           IDictionary validationData)
        {
            return AdminSignupAsync(userID, userAttributes, validationData, default);
        }
        /// 
        /// Signs up the user with the specified parameters using an asynchronous call end triggers a temporary password sms or email message.
        /// 
        /// The userID of the user being created
        /// The user attributes of the user being created
        /// The validation data of the user being created
        /// A cancellation token that can be used by other objects or threads to receive notice of cancellation
        /// Returns the delivery details for the sign up request
        public Task AdminSignupAsync(string userID,
                           IDictionary userAttributes,
                           IDictionary validationData,
                           CancellationToken cancellationToken)
        {
            AdminCreateUserRequest signUpUserRequest = CreateAdminSignUpRequest(userID, userAttributes, validationData);
            return Provider.AdminCreateUserAsync(signUpUserRequest, cancellationToken);
        }
        /// 
        /// Internal method to aid in the admin sign up flow for a new user
        /// 
        /// The userID of the user being created
        /// The user attributes of the user being created
        /// The validation data of the user being created
        /// Returns the SignUpResponse for the sign up API request using the provided information
        private AdminCreateUserRequest CreateAdminSignUpRequest(string userID,
                                              IDictionary userAttributes,
                                              IDictionary validationData)
        {
            List userAttributesList = null;
            if (userAttributes != null)
            {
                userAttributesList = CognitoAuthHelper.CreateAttributeList(userAttributes);
            }
            else
            {
                throw new ArgumentNullException(nameof(userAttributes), "userAttributes cannot be null.");
            }
            List validationDataList =
                validationData != null ? CognitoAuthHelper.CreateAttributeList(validationData) : null;
            // Create User registration request
            return new AdminCreateUserRequest()
            {
                Username = userID,
                UserPoolId = this.PoolID,
                UserAttributes = userAttributesList,
                ValidationData = validationDataList
            };
        }
        /// 
        /// Resets the 's password to the specified  after
        /// validating the given password reset .
        /// 
        /// The user whose password should be reset.
        /// The password reset token to verify.
        /// The new password to set if reset token verification succeeds.
        /// 
        /// The  that represents the asynchronous operation, containing the 
        /// of the operation.
        /// 
        public Task ConfirmForgotPassword(string userID, string token, string newPassword, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            var request = new ConfirmForgotPasswordRequest
            {
                Username = userID,
                ClientId = ClientID,
                ConfirmationCode = token,
                Password = newPassword,
            };
            if (!string.IsNullOrEmpty(ClientSecret))
            {
                request.SecretHash = CognitoAuthHelper.GetUserPoolSecretHash(userID, ClientID, ClientSecret);
            }
            return Provider.ConfirmForgotPasswordAsync(request, cancellationToken);
        }
    }
}