using System;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.InformationProtection;

namespace AipGateway.AIP
{
    public class AuthDelegateImplementation : IAuthDelegate
    {
        public int LastErrNo { get; set; }
        public string LastErrMsg { get; set; }

        private readonly AipConfig _aipConfig;


        public AuthDelegateImplementation(AipConfig aipConfig)
        {
            _aipConfig = aipConfig;
            LastErrNo = 0;
            LastErrMsg = String.Empty;
        }

        public void ResetError()
        {
            LastErrNo = 0;
            LastErrMsg = string.Empty;
        }
        private void SetError(int errNo, string errMsg1, string errMsg2 = "No Exception Message.")
        {
            LastErrNo = errNo;
            LastErrMsg = errMsg1 + "\r\n" + errMsg2;
            Console.WriteLine("AuthDelegateImplementation::SetError ==> {0}, {1}, {2}", errNo, errMsg1, errMsg2);
        }

        public string AcquireToken(Identity identity, string authority, string resource, string claim)
        {
            Console.WriteLine("AuthDelegateImplementation::AcquireToken ==> LoginType: {0}", _aipConfig.LoginType);

            if (_aipConfig.LoginType == AipAuthLoginType.authLoginPassword)
            {
                return AcquireTokenByPassword(identity, authority, resource, claim);
            }
            
            if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert)
            {
                return AcquireTokenByCert(identity, authority, resource, claim);
            }

            return AcquireTokenById(identity, authority, resource, claim);
        }

        private string AcquireTokenByPassword(Identity identity, string authority, string resource, string claims)
        {
            Console.WriteLine("AuthDelegateImplementation::AcquireTokenByPassword");

            AuthenticationResult result;
            var authorityUri = new Uri(authority);
            authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";
            
            var app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
                .WithClientSecret(_aipConfig.SecretValue)
                .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
                .Build();

            var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };

            //app.AddInMemoryTokenCache();

            try
            {
                result = app.AcquireTokenForClient(scopes)
                .WithTenantId(_aipConfig.TenantId)
                .ExecuteAsync()
                .GetAwaiter()
                .GetResult();
            }
            catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
            {
                // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
                // Mitigation: change the scope to be as expected
                SetError(1, "AcquireTokenByPassword.Scope provided is not supported.", ex.Message);
                return null;
            }

            return result.AccessToken;
        }

        private string AcquireTokenByCert(Identity identity, string authority, string resource, string claims)
        {
            Console.WriteLine("AuthDelegateImplementation::AcquireTokenByPassword");

            AuthenticationResult result;
            var authorityUri = new Uri(authority);
            authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";

            // Read cert from local machine
            //var certificate = ReadCertificateFromStore(certThumb);
            var myCertificate = X509Certificate2.CreateFromCertFile(_aipConfig.CertThumbPrint);
            X509Certificate2 certificate = new X509Certificate2(myCertificate);

            // Use cert to build ClientAssertionCertificate
            var app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
                .WithCertificate(certificate)
                .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
                .Build();

            // Append .default to the resource passed in to AcquireToken().
            var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };

            try
            {
                result = app.AcquireTokenForClient(scopes).ExecuteAsync().Result;
            }
            catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
            {
                // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
                // Mitigation: change the scope to be as expected
                SetError(1, "AcquireTokenByCert.Scope provided is not supported.", ex.Message);
                return null;
            }
            return result.AccessToken;
        }

        private string AcquireTokenById(Identity identity, string authority, string resource, string claims)
        {
            Console.WriteLine("AcquireTokenById::AcquireTokenByPassword");

            AuthenticationResult result;
            var authorityUri = new Uri(authority);
            authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";

            var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId)
                .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
                .WithAuthority(AzureCloudInstance.AzurePublic, "2e58414a-c6ae-43ff-aaf5-45ab8b78a404")
                .Build();

            var accounts = (app.GetAccountsAsync()).GetAwaiter().GetResult();

            // Append .default to the resource passed in to AcquireToken().
            var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
            try
            {
                result = app.AcquireTokenInteractive(scopes)
                    .WithAccount(accounts.FirstOrDefault())
                    .WithPrompt(Prompt.SelectAccount)
                    .ExecuteAsync()
                    .ConfigureAwait(false)
                    .GetAwaiter()
                    .GetResult();
            }
            catch (Exception ex)
            {
                SetError(1, "AcquireTokenById.Login Failed.", ex.Message);
                return null;
            }

            return result.AccessToken;
        }

        private static X509Certificate2 ReadCertificateFromStore(string thumbprint)
        {
            X509Certificate2 cert = null;
            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection certCollection = store.Certificates;

            // Find unexpired certificates.
            X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);

            // From the collection of unexpired certificates, find the ones with the correct name.
            X509Certificate2Collection signingCert = currentCerts.Find(X509FindType.FindByThumbprint, thumbprint, false);

            // Return the first certificate in the collection, has the right name and is current.
            cert = signingCert.OfType<X509Certificate2>().OrderByDescending(c => c.NotBefore).FirstOrDefault();
            store.Close();
            return cert;
        }

        public async Task<AuthenticationResult> AcquireTokenAsync(string authority, string resource, string claims)
        {
            AuthenticationResult result = null;

            if (authority.ToLower().Contains("common"))
            {
                var authorityUri = new Uri(authority);
                authority = String.Format("https://{0}/{1}", authorityUri.Host, _aipConfig.TenantId);
            }
            
            var app = PublicClientApplicationBuilder.Create(_aipConfig.ClientId)
                .WithAuthority(authority)
                .WithDefaultRedirectUri()
                .Build();

            var accounts = (app.GetAccountsAsync()).GetAwaiter().GetResult();

            // Append .default to the resource passed in to AcquireToken().
            string[] scopes = { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };

            try
            {
                result = await app.AcquireTokenSilent(new[] { "https://aadrm.com/user_impersonation" }, accounts.FirstOrDefault()).ExecuteAsync();

                // result = await _app.AcquireTokenSilent(scopes, 
                //         accounts.FirstOrDefault())
                //     .ExecuteAsync();
            }
            catch (MsalUiRequiredException)
            {
                result = app.AcquireTokenInteractive(scopes)
                    .WithAccount(accounts.FirstOrDefault())
                    .WithPrompt(Prompt.SelectAccount)
                    .ExecuteAsync()
                    .ConfigureAwait(false)
                    .GetAwaiter()
                    .GetResult();
            }

            // Return the token. The token is sent to the resource.                           
            return result;
        }

        // private async Task<string> GetAccessTokenOnBehalfOfUser(string authority, string resource)
        // {
        //     IConfidentialClientApplication app;
        //     
        //     if (false)
        //     {
        //         // Read X509 cert from local store and build ClientAssertionCertificate.
        //         X509Certificate2 cert = Utilities.ReadCertificateFromStore(_aipConfig.CertThumbPrint);
        //         
        //         // Create confidential client using certificate.
        //         app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
        //                                         .WithRedirectUri(resource)
        //                                         .WithAuthority(authority)
        //                                         .WithCertificate(cert)
        //                                         .Build();                               
        //     }
        //
        //     else
        //     {
        //         // Create confidential client using client secret.
        //         app = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
        //                                        .WithRedirectUri(resource)
        //                                        .WithAuthority(authority)
        //                                        .WithClientSecret(_aipConfig.SecretValue)
        //                                        .Build();                                
        //     }
        //
        //     // Store user access token of authenticated user.
        //     var ci = (ClaimsIdentity)_claimsPrincipal.Identity;
        //     string userAccessToken = (string)ci.BootstrapContext;
        //
        //
        //     // Generate a user assertion with the UPN and access token.            
        //     UserAssertion userAssertion = new UserAssertion(userAccessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer");
        //
        //     // Append .default to the resource passed in to AcquireToken().
        //     List<string> scopes = new List<string>() { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
        //
        //     AuthenticationResult result = await app.AcquireTokenOnBehalfOf(scopes, userAssertion)
        //       .ExecuteAsync();              
        //
        //     // Return the token to the API caller
        //     return (result.AccessToken);
        // }      
    }
}