#region Using Statements

using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;

#endregion

namespace ClearstreamAPIConnector
{
    class Program
    {
        // callback used to validate the certificate in an SSL conversation
        private static bool ValidateRemoteCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors policyErrors)
        {
            var result = cert.Subject.Contains("YourServerName");
            return result;
        }

        /// <summary>
        /// Get our refresh token
        /// </summary>
        /// <param name="originalToken"></param>
        /// <returns></returns>
        private static async Task<Token?> GetRefreshTokenAsync(Token originalToken)
        {
            const string username = "stb2_01014165_1000181";
            const string certPw = "clearstream";

            #region Private Member
            //private static string clearstreamApiEndpointUrl = "https://api.clearstream.com/";
            var clearstreamApiEndpointUrl = "https://api-t2s-test.clearstream.com/";
            #endregion

            Token? tok = null;

            // First create a proxy object
            var proxy = new WebProxy
            {
                Address = new Uri($"http://l7fwout.usinet.it:3128"),
                BypassProxyOnLocal = false,
                UseDefaultCredentials = false,
                Credentials = CredentialCache.DefaultNetworkCredentials
            };


            // check certificate chain
            ServicePointManager.ServerCertificateValidationCallback =
                (sender, certificate, chain, sslPolicyErrors) =>
                {
                    Console.WriteLine("Ssl Policy Errors:");
                    switch (sslPolicyErrors)
                    {
                        case SslPolicyErrors.None:
                            Console.WriteLine("No error in certificate found");
                            break;
                        case SslPolicyErrors.RemoteCertificateNotAvailable:
                            Console.WriteLine("RemoteCertificateNotAvailable");
                            break;
                        case SslPolicyErrors.RemoteCertificateNameMismatch:
                            Console.WriteLine("RemoteCertificateNameMismatch");
                            break;
                        case SslPolicyErrors.RemoteCertificateChainErrors:
                            Console.WriteLine("RemoteCertificateChainErrors");
                            break;
                    }

                    // call our validation logic
                    //return ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors);
                    Console.WriteLine("Overriding cert chain errors. Proceeding...");
                    return true;
                };

            // configure endpoint and certificate
            try
            {
                var remoteAddress = $"{clearstreamApiEndpointUrl}authmanager/oauth2/access_token";
                var certFile = @"./Cert/P12/xact-api-credentials-3des.p12";
                var certificates = new X509Certificate2Collection();
                certificates.Import(certFile, certPw, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

                // values to pass on to service
                var values = new Dictionary<string, string>
                {
                    { "grant_type", "refresh_token" },{"refresh_token",originalToken.refresh_token},
                    { "username", username},
                };

                // encode it in url form...
                var data = new FormUrlEncodedContent(values);

                // use modern http client implementation to call endpoint
                using var httpHandler = new HttpClientHandler();
                httpHandler.ClientCertificates.Add(certificates[0]);
                httpHandler.Proxy = proxy;

                using var client = new HttpClient(httpHandler);
                // handle communiction with API
                var response = await client.PostAsync(remoteAddress, data);

                // get our response content
                var responseContent = await response.Content.ReadAsStringAsync();

                // get our token
                tok = JsonSerializer.Deserialize<Token>(responseContent);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception: {ex.Message}.");
            }

            return tok;
        }

        /// <summary>
        /// Get Elibility Token
        /// </summary>
        /// <returns></returns>
        private static async Task<Token> GetElibilityTokenAsync()
        {
            //string scope = "allow ocapi-playground-v1";
            var scopeDi = "allow digital-instruments";
            var username = "stb2_01014165_1000181";
            var password = "Creation1,";
            var certPw = "clearstream";

            #region Private Member
            //private static string clearstreamApiEndpointUrl = "https://api.clearstream.com/";
            var clearstreamApiEndpointUrl = "https://api-t2s-test.clearstream.com/";
            #endregion

            Token? tok = null;

            // First create a proxy object
            //var proxy = new WebProxy
            //{
            //    Address = new Uri($"http://l7fwout.usinet.it:3128"),
            //    BypassProxyOnLocal = false,
            //    UseDefaultCredentials = false,

            //    Credentials = CredentialCache.DefaultNetworkCredentials
            //};

            // check certificate chain
            ServicePointManager.ServerCertificateValidationCallback =
                (sender, certificate, chain, sslPolicyErrors) =>
                {
                    Console.WriteLine("Ssl Policy Errors:");
                    switch (sslPolicyErrors)
                    {
                        case SslPolicyErrors.None:
                            Console.WriteLine("No error in certificate found");
                            break;
                        case SslPolicyErrors.RemoteCertificateNotAvailable:
                            Console.WriteLine("RemoteCertificateNotAvailable");
                            break;
                        case SslPolicyErrors.RemoteCertificateNameMismatch:
                            Console.WriteLine("RemoteCertificateNameMismatch");
                            break;
                        case SslPolicyErrors.RemoteCertificateChainErrors:
                            Console.WriteLine("RemoteCertificateChainErrors");
                            break;
                    }

                    Console.WriteLine("Overriding cert chain errors. Proceeding...");
                    return true;
                };

            // configure endpoint and certificate
            try
            {
                var remoteAddress = $"{clearstreamApiEndpointUrl}authmanager/oauth2/access_token";
                var certFile = @".\Cert\P12\xact-api-credentials-3des.p12";
                X509Certificate2Collection certificates = new();
                certificates.Import(certFile, certPw, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

                // values to pass on to service
                var values = new Dictionary<string, string>
                        {
                            { "grant_type", "password" },
                            //{ "scope", scope},
                            {"scope", scopeDi },
                            { "username", username},
                            { "password", password},
                        };

                // encode it in url form...
                var data = new FormUrlEncodedContent(values);

                // use modern http client implementation to call endpoint
                using var httpHandler = new HttpClientHandler();

                httpHandler.ClientCertificates.Add(certificates[0]);
                //httpHandler.Proxy = proxy;

                using var client = new HttpClient(httpHandler);
                // handle communiction with API
                var response = await client.PostAsync(remoteAddress, data);

                // get our response content
                var responseContent = await response.Content.ReadAsStringAsync();

                // get our token
                tok = JsonSerializer.Deserialize<Token>(responseContent);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception: {ex.Message}.");
            }

            return tok;
        }

        /// <summary>
        /// Get all associcated templates
        /// </summary>
        /// <param name="accessToken">Bearer token derived from OAuth2.0 request</param>
        /// <returns>JSON with all templates available as a <see cref="string"/></returns>
        static async Task<DiTemplateList> GetAllTemplates(Token accessToken)
        {
            Console.WriteLine("Attempting to get all templates for our account");

            if (accessToken == null)
            {
                throw new ArgumentNullException(nameof(accessToken));
            }

            DiTemplateList templates = null;
            var certPw = "clearstream";

            #region Private Member
            //private static string clearstreamApiEndpointUrl = "https://api.clearstream.com/";
            var clearstreamApiEndpointUrl = "https://api-t2s-test.clearstream.com/";
            #endregion

            var remoteAddress = $"{clearstreamApiEndpointUrl}digital-instruments/di/templates";

            // First create a proxy object
            // var proxy = new WebProxy
            // {
            //     Address = new Uri($"http://l7fwout.usinet.it:3128"),
            //     BypassProxyOnLocal = false,
            //     UseDefaultCredentials = false,
            //     Credentials = CredentialCache.DefaultNetworkCredentials
            // };

            try
            {
                var certFile = @"./Cert/P12/xact-api-credentials-3des.p12";
                var certificates = new X509Certificate2Collection();
                certificates.Import(certFile, certPw, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

                // use modern http client implementation to call endpoint
                using var httpHandler = new HttpClientHandler();

                httpHandler.ClientCertificates.Add(certificates[0]);
                // httpHandler.Proxy = proxy;

                using var client = new HttpClient(new LoggingHandler(httpHandler));

                // handle additional client header
                client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken.access_token}");

                // handle communiction with API
                var response = await client.GetAsync(remoteAddress);

                // get our response content
                var responseContent = await response.Content.ReadAsStringAsync();

                templates = JsonSerializer.Deserialize<DiTemplateList>(responseContent);

                Console.WriteLine($"Received the following content from {remoteAddress}: '{responseContent}'");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception: {ex.Message}.");
            }

            return templates;
        }

        /// <summary>

        /// Call the playground API asynchronously
        /// </summary>
        /// <param name="accessToken"></param>
        /// <returns></returns>
        static async Task<string> CallClearstreamPlaygroundApiAsync(Token accessToken)
        {
            if (accessToken == null)
            {
                throw new ArgumentNullException(nameof(accessToken));
            }

            var jsonContent = string.Empty;
            var certPw = "clearstream";

            #region Private Member
            //private static string clearstreamApiEndpointUrl = "https://api.clearstream.com/";
            var clearstreamApiEndpointUrl = "https://api-t2s-test.clearstream.com/";
            #endregion

            var remoteAddress = $"{clearstreamApiEndpointUrl}/playground/v1/echo?value=Hello%20World";

            // // First create a proxy object
            // var proxy = new WebProxy
            // {
            //     Address = new Uri($"http://l7fwout.usinet.it:3128"),
            //     BypassProxyOnLocal = false,
            //     UseDefaultCredentials = false,
            //     Credentials = CredentialCache.DefaultNetworkCredentials
            // };

            try
            {
                var certFile = @"./Cert/P12/xact-api-credentials-3des.p12";
                var certificates = new X509Certificate2Collection();
                certificates.Import(certFile, certPw, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

                // values to pass on to service
                var values = new Dictionary<string, string>();

                // use modern http client implementation to call endpoint
                using var httpHandler = new HttpClientHandler();

                httpHandler.ClientCertificates.Add(certificates[0]);
                // httpHandler.Proxy = proxy;

                using var client = new HttpClient(httpHandler);

                // handle additional client header
                client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken.access_token}");

                // handle communiction with API
                var response = await client.GetAsync(remoteAddress);

                // get our response content
                var responseContent = await response.Content.ReadAsStringAsync();

                jsonContent = responseContent;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Exception: {ex.Message}.");
            }

            return jsonContent;
        }

        /// <summary>
        /// Validate the whole certificate chain using the cacert certificate
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="certificate"></param>
        /// <param name="chain"></param>
        /// <param name="sslPolicyErrors"></param>
        /// <returns></returns>
        static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            Console.WriteLine("Checking server certificate...");

            if (sslPolicyErrors == SslPolicyErrors.None)
            {
                Console.WriteLine("sslPolicyErrors == SslPolicyErrors.None. Thus returning true to validate certificate from clearstream.");
                return true;
            }

            Console.WriteLine("Cert issues found. Attempting to load cacert pem from Clearstream.");

            var privateChain = new X509Chain();
            privateChain.ChainPolicy.RevocationMode = X509RevocationMode.Offline;

            var cert2 = new X509Certificate2(certificate);
            var signerCert2 = new X509Certificate2(@".\Cert\ocapi-stb2_01014165_1000181-cacert.pem");

            privateChain.ChainPolicy.ExtraStore.Add(signerCert2);
            privateChain.Build(cert2);

            var isValid = true;

            Console.WriteLine("Validate Certificate chain manually.");
            foreach (var chainStatus in privateChain.ChainStatus)
            {
                if (chainStatus.Status != X509ChainStatusFlags.NoError)
                {
                    Console.WriteLine("chainStatus.Status != X509ChainStatusFlags.NoError");

                    isValid = false;
                    break;
                }
            }

            Console.WriteLine($"Returning {isValid} as result of cert chain validation.");

            if (isValid == false)
            {
                Console.WriteLine("Overriding false value for cert result with 'true'");
                isValid = true;
            }

            return isValid;
        }

        /// <summary>
        /// Main entry point for 
        /// </summary>
        /// <param name="args"></param>
        static async Task Main(string[] args)
        {
            try
            {
                // Console.WriteLine("Setting proxy to 'http://l7fwout.usinet.it:3128'");

                // string resultString = string.Empty;
                var errorOccured = false;

                // // First create a proxy object
                // var proxy = new WebProxy
                // {
                //     Address = new Uri($"http://l7fwout.usinet.it:3128"),
                //     BypassProxyOnLocal = false,
                //     UseDefaultCredentials = false,
                //     Credentials = CredentialCache.DefaultNetworkCredentials
                // };

                Console.WriteLine("Preparing the request...");

                // check certificate chain
                ServicePointManager.ServerCertificateValidationCallback =
                    (sender, certificate, chain, sslPolicyErrors) =>
                    {
                        Console.WriteLine("Ssl Policy Errors:");
                        switch (sslPolicyErrors)
                        {
                            case SslPolicyErrors.None:
                                Console.WriteLine("No error in certificate found");
                                break;
                            case SslPolicyErrors.RemoteCertificateNotAvailable:
                                Console.WriteLine("RemoteCertificateNotAvailable");
                                break;
                            case SslPolicyErrors.RemoteCertificateNameMismatch:
                                Console.WriteLine("RemoteCertificateNameMismatch");
                                break;
                            case SslPolicyErrors.RemoteCertificateChainErrors:
                                Console.WriteLine("RemoteCertificateChainErrors");
                                break;
                        }

                        // call our validation logic
                        return ValidateServerCertificate(sender, certificate, chain, sslPolicyErrors);
                    };

                // validate cert by calling a function
                ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(ValidateServerCertificate);

                // first get the access token from clearstream API
                var accessToken = await GetElibilityTokenAsync();
                if (accessToken != null && !string.IsNullOrEmpty(accessToken.access_token))
                {
                    Console.WriteLine("Received non null JWT token. Calling Playground Clearstream API");
                    Console.WriteLine($"Token: '{accessToken.access_token}'");
                    Console.WriteLine($"Refresh Token: {accessToken.refresh_token}");
                    Console.WriteLine($"Expires in: {accessToken.expires_in}");

                    // call the playground API
                    //resultString = await CallClearstreamPlaygroundApiAsync(accessToken);

                    //Console.WriteLine("For testing purposes, try to regain a new token based on the refresh token.");
                    //var refreshToken = await GetRefreshTokenAsync(accessToken);
                    //if (refreshToken != null)
                    //{
                    //    Console.WriteLine("Received non null refresh token. Dumping details now:");
                    //    Console.WriteLine($"Token: '{refreshToken.access_token}'");
                    //    Console.WriteLine($"Refresh Token: {refreshToken.refresh_token}");
                    //    Console.WriteLine($"Expires in: {refreshToken.expires_in}");
                    //}

                    // next try to call the FIRST real DI method and get the available templates
                    var templates = await GetAllTemplates(accessToken);
                    if (templates != null && templates.Count > 0)
                    {
                        Console.WriteLine("Dumping all received templates available for the account used:");

                        var firstTemplate = templates[0];

                        Console.WriteLine($"PartyId: {firstTemplate.partyId}");
                    }

                    Console.WriteLine("Successfully called Digital Instruments API.");
                }
                else
                {
                    Console.WriteLine("Received empty JWT token. Aborting.");
                    errorOccured = true;
                }


                if (errorOccured)
                {
                    Console.WriteLine("Clearstream VNext connectivity test completed with error(s).");
                }
                else
                {
                    Console.WriteLine("Clearstream VNext connectivity test completed successfully.");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Got Exception while running the connetivity check. Message {ex.Message}, Stacktrace: {ex.StackTrace}");
                Console.WriteLine("Clearstream VNext connecivity check FAILED.");
            }
        }
    }
}