using C4IT.DataHistoryProvider.Base.Modules.Agent.DTOs;
using C4IT.FASD.Base;
using C4IT.FASD.Communication.Agent;
using C4IT.HTTP;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using static C4IT.Logging.cLogManager;

namespace C4IT.DataHistoryProvider.Base.Modules.Agent
{
    internal class AgentApiHandler : cHttpApiBase
    {
        private readonly Uri _baseUrl;
        private const string _agentLoginPath = "connect/token";
        private readonly cAgentApiConfiguration _agentConfig;

        public AgentApiHandler(cAgentApiConfiguration agentConfig) : base("Agent ID tenant", $"{agentConfig.ApiUrl}/{agentConfig.LogonUrl}")
        {
            _agentConfig = agentConfig;
            _baseUrl = new Uri(_agentConfig.LogonUrl);
            AlwaysReturnContent = true;
        }

        private protected override async Task<bool> privLogonAsync(cOAuthLogonInfo logonInfo)
        {
            try
            {
                var postData = new List<KeyValuePair<string, string>>()
                {
                    new KeyValuePair<string, string>("grant_type", "client_credentials"),
                    new KeyValuePair<string, string>("client_id", logonInfo.ClientID),
                    new KeyValuePair<string, string>("client_secret", logonInfo.ClientSecret)
                };

                var formData = new FormUrlEncodedContent(postData);

                var request = new HttpRequestMessage(HttpMethod.Post, new Uri(_baseUrl, _agentLoginPath)) { Content = formData };

                using (var httpClient = new HttpClient())
                {
                    var response = await httpClient.SendAsync(request);

                    if (response.StatusCode != HttpStatusCode.OK)
                    {
                        var responseMessage = await response.Content.ReadAsStreamAsync();
                        LogEntry($"F4SD Agent Logon failed. StatusCode: {response.StatusCode}");
                        LogEntry($"F4SD Agent Logon failed. Response: {responseMessage}");
                        return false;
                    }

                    string responseContent = await response.Content.ReadAsStringAsync();
                    var logonResult = JsonConvert.DeserializeObject<cAgentApiLogonInfo>(responseContent);

                    if (logonResult.TokenType.Equals("Bearer", StringComparison.OrdinalIgnoreCase) == false)
                    {
                        LogEntry("F4SD Agent Access token is not of type 'Bearer'.");
                        return false;
                    }

                    cAgentApiLogonInfo.Instance = logonResult;
                    AccessToken = logonResult.AccessToken;
                    TokenExpiresIn = DateTime.UtcNow + TimeSpan.FromSeconds(logonResult.SecondsTillTokenExpires * RelogonFactor);
                    IsOnline = true;
                }

                return true;
            }
            catch (Exception ex)
            {
                LogException(ex);
                return false;
            }
        }

        internal async Task<T> Request<T>(string url, eHttpMethod httpMethod = eHttpMethod.get, object bodyData = null, CancellationToken token = default) where T : AgentApiResult
        {
            await LogonAsync(new cOAuthLogonInfo() { ClientID = _agentConfig.ClientId, ClientSecret = _agentConfig.ClientSecret, Tenant = "F4SD Agent" });
            Uri agentUrl = new Uri(_baseUrl, url);
            dynamic requestResult = await privRequestAsync(agentUrl.ToString(), httpMethod, bodyData, token: token);
            // todo handle not successfull status
            requestResult.ToObject<T>();
            string serialized = JsonConvert.SerializeObject(requestResult); // todo find better approach for casting the dynamic
            T deserialized = JsonConvert.DeserializeObject<T>(serialized);
            return deserialized;
        }

        internal async Task Request(string url, eHttpMethod httpMethod = eHttpMethod.get, object bodyData = null)
            => await privRequestAsync(url, httpMethod, bodyData);
    }
}
