using System;
using System.IdentityModel.Tokens;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;
using Microsoft.InformationProtection;

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

        private readonly AipConfig _aipConfig;
        private readonly IConfidentialClientApplication _confidentialApp = null;
        private readonly IPublicClientApplication _publicApp = null;
        // [Obsolete("Obsolete")] private TokenCache _tokenCache = new TokenCache();


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

            var storageProperties =
                new StorageCreationPropertiesBuilder(
                        "AIPGateway.Cache",
                        Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
                    .Build();
            var cacheHelper = MsalCacheHelper.CreateAsync(storageProperties).GetAwaiter().GetResult();

            if (_aipConfig.LoginType == AipAuthLoginType.authLoginPassword)
            {
                // _confidentialApp = ConfidentialClientApplicationBuilder
                //     .Create(_aipConfig.ClientId)
                //     .WithClientSecret(_aipConfig.SecretValue)
                //     // .WithAuthority($"https://login.microsoftonline.com/{_aipConfig.TenantId}")
                //     // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
                //     .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
                //     .Build();

                // var authority = $"https://login.windows.net/{_aipConfig.TenantId}";
                // BrokerOptions brokerOptions = new BrokerOptions(BrokerOptions.OperatingSystems.Windows);
                // _confidentialApp = PublicClientApplicationBuilder
                //     .Create(_aipConfig.ClientId)
                //     .WithAuthority(authority)
                //     .WithDefaultRedirectUri()
                //     .WithBroker(brokerOptions)
                //     .Build();

                ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions()
                {
                    ClientSecret = _aipConfig.SecretValue,
                    ClientId = _aipConfig.ClientId,
                    TenantId = _aipConfig.TenantId,
                    RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient",
                    Instance = "https://login.microsoftonline.com/"
                };
                
                _confidentialApp = ConfidentialClientApplicationBuilder
                    .CreateWithApplicationOptions(options)
                    .WithRedirectUri(options.RedirectUri)
                    // .WithLegacyCacheCompatibility(false)
                    // .WithExperimentalFeatures() // for PoP
                    // .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
                    .Build();
                // IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.CreateWithApplicationOptions(options)
                //     .WithCertificate(certificate)
                //     .Build();

                //Console.WriteLine(" AppTokenCache: {0}", _confidentialApp.AppTokenCache);
                //Console.WriteLine("UserTokenCache: {0}", _confidentialApp.UserTokenCache);
                cacheHelper.RegisterCache(_confidentialApp.UserTokenCache);
            }
            else if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert)
            {
                var authority = $"https://login.windows.net/{_aipConfig.TenantId}";
                // var certificate = Utilities.ReadCertificateFromStore(_aipConfig.CertThumbPrint);
                // var myCertificate = X509Certificate2.CreateFromCertFile(_aipConfig.CertThumbPrint, "audwls");
                var myCertificate = new X509Certificate2(_aipConfig.CertThumbPrint, "hanteinfo1234!");
                X509Certificate2 certificate = new X509Certificate2(myCertificate);
                _confidentialApp = ConfidentialClientApplicationBuilder.Create(_aipConfig.ClientId)
                    .WithCertificate(certificate)
                    .WithAuthority(new Uri(authority))
                    .Build();
                cacheHelper.RegisterCache(_confidentialApp.UserTokenCache);
            }
            else
            {
                _publicApp = PublicClientApplicationBuilder.Create(_aipConfig.ClientId)
                    .WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
                    .WithAuthority(AzureCloudInstance.AzurePublic, _aipConfig.TenantId)
                    .Build();
                cacheHelper.RegisterCache(_publicApp.UserTokenCache);
            }
        }

        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 AcquireTokenByCertificate(identity, authority, resource, claim);
            }
            
            if (_aipConfig.LoginType == AipAuthLoginType.authLoginCert)
            {
                return AcquireTokenByCertificate(identity, authority, resource, claim);
            }

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

        private string AcquireTokenByCertificate(Identity identity, string authority, string resource, string claims)
        {
            // AuthenticationResult result;
            // var authorityUri = new Uri(authority);
            // authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";
            var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
            
            try
            {
                //Console.WriteLine("B:  AppTokenCache: {0}", _confidentialApp.AppTokenCache);
                //Console.WriteLine("B: UserTokenCache: {0}", _confidentialApp.UserTokenCache);
                AuthenticationResult result = _confidentialApp.AcquireTokenForClient(scopes)
                    .ExecuteAsync(CancellationToken.None)
                    .ConfigureAwait(false)
                    .GetAwaiter()
                    .GetResult();
                // result = _confidentialApp.AcquireTokenForClient(scopes)
                //     .WithTenantId(_aipConfig.TenantId)
                //     .ExecuteAsync()
                //     .GetAwaiter()
                //     .GetResult();
                // result = _confidentialApp.AcquireTokenForClient(scopes).ExecuteAsync().GetAwaiter().GetResult();
                //Console.WriteLine("A:  AppTokenCache: {0}", _confidentialApp.AppTokenCache);
                //Console.WriteLine("A: UserTokenCache: {0}", _confidentialApp.UserTokenCache);
                //Console.WriteLine("A:    AccessToken: {0}", result.AccessToken);
                return result.AccessToken;
            }
            catch (MsalUiRequiredException 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, "AcquireTokenByCertificate::AcquireTokenByCertificate, Scope provided is not supported.", ex.Message);
            }
            catch (Exception ex)
            {
                SetError(1, "AcquireTokenByCertificate::AcquireTokenByCertificate Failed.", ex.Message);
            }

            return null;
        }

        // private string AcquireTokenByCertificate_Temp(Identity identity, string authority, string resource, string claims)
        // {
        //     AuthenticationResult result;
        //     var authorityUri = new Uri(authority);
        //     authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";
        //
        //     var scopes = new[] { resource[resource.Length - 1].Equals('/') ? $"{resource}.default" : $"{resource}/.default" };
        //
        //     try
        //     {
        //         var accounts = (_confidentialApp.GetAccountsAsync()).GetAwaiter().GetResult();
        //         result = _confidentialApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
        //             .ExecuteAsync().GetAwaiter().GetResult();
        //
        //         // result = _confidentialApp.AcquireTokenForClient(scopes)
        //         //     .ExecuteAsync().GetAwaiter().GetResult();
        //
        //         Console.WriteLine("AuthDelegateImplementation::AcquireTokenByCertificate ==> AcquireTokenSilent Succeed.");
        //         return result.AccessToken;
        //     }
        //     catch (MsalUiRequiredException)
        //     {
        //         Console.WriteLine("AuthDelegateImplementation::AcquireTokenByCertificate ==> AcquireTokenSilent Failed. Login AcquireTokenForClient.");
        //         try
        //         {
        //             result = _confidentialApp.AcquireTokenForClient(scopes)
        //                 .ExecuteAsync().GetAwaiter().GetResult();
        //             // result = _confidentialApp.AcquireTokenForClient(scopes)
        //             //     .WithTenantId(_aipConfig.TenantId)
        //             //     .ExecuteAsync()
        //             //     .GetAwaiter()
        //             //     .GetResult();
        //             // result = _confidentialApp.AcquireTokenForClient(scopes).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 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, _aipConfig.TenantId)
            //     .Build();

            var accounts = (_publicApp.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 = _publicApp.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync().Result;
                //Console.WriteLine("AuthDelegateImplementation::AcquireTokenById ==> AcquireTokenSilent Succeed.");
                return result.AccessToken;
            }
            catch (MsalUiRequiredException)
            {
                //Console.WriteLine("AuthDelegateImplementation::AcquireTokenById ==> AcquireTokenSilent Failed. Login AcquireTokenForClient.");
                result = _publicApp.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;
        }
        
        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;
        }
        // public Identity GetUserIdentity()
        // {
        //     try
        //     {
        //         string resource = "https://graph.microsoft.com/";
        //
        //         AuthenticationContext authContext = new AuthenticationContext("https://login.windows.net/common", tokenCache);
        //         var result = authContext.AcquireTokenAsync(resource, appInfo.ApplicationId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Always)).Result;
        //         return new Identity(result.UserInfo.DisplayableId);
        //
        //     }
        //     catch (Exception ex)
        //     {
        //         throw ex;
        //     }
        // }

        // 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);
        // }      
    }
}