using FabAccessAPI.Schema;
using S22.Sasl;
using System.Collections.Generic;
using System.Threading.Tasks;
using FabAccessAPI.Exceptions;
using System.Linq;

namespace FabAccessAPI
{
    /// <summary>
    /// Authenticate with SASL
    /// </summary>
    public class Auth
    {
        #region Private Fields
        private readonly IAuthentication _AuthCap;
        #endregion

        #region Constructors
        public Auth(IAuthentication authCap)
        {
            _AuthCap = authCap;
        }
        #endregion

        #region Methods
        public async Task<Session> Authenticate(string mech, Dictionary<string, object> properties)
        {
            SaslMechanism? saslMechanism = SaslFactory.Create(mech);
            foreach (KeyValuePair<string, object> entry in properties)
            {
                saslMechanism.Properties.Add(entry.Key, entry.Value);
            }

            byte[] data = new byte[0];

            if (saslMechanism.HasInitial)
            {
                data = saslMechanism.GetResponse(new byte[0]);
            }

            Response? response = await _AuthCap.Step(data);
            while (!saslMechanism.IsCompleted)
            {
                if(response.Failed != null)
                {
                    switch (response.Failed.Code)
                    {
                        case Response.Error.badMechanism:
                            throw new BadMechanismException();
                        case Response.Error.invalidCredentials:
                            throw new InvalidCredentialsException();
                        case Response.Error.aborted:
                        case Response.Error.failed:
                        default:
                            throw new AuthenticationFailedException(response.Failed.AdditionalData.ToArray());
                    }
                }
                if(response.Challenge != null)
                {
                    byte[]? additional = saslMechanism.GetResponse(response.Challenge.ToArray());
                    response = await _AuthCap.Step(additional);
                }
                else
                {
                    throw new AuthenticationFailedException();
                }
            }

            if (response.Successful != null)
            {
                return response.Successful.Session;
            }
            else
            {
                throw new AuthenticationFailedException();
            }
        }
        #endregion
    }
}