using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using System.Configuration;

/// <summary>
/// Handles FedEx Token (Get / Refresh / Reuse)
/// </summary>
public class FedexTokenService
{
    private static readonly string TOKEN_URL =
    ConfigurationManager.AppSettings["FedexTokenUrl"];

    #region MAIN METHOD (USE THIS)
    public async Task<FedexTokenResult> GetValidToken(FedexHelper fedexHelper)
    {
        var fedexDetails = fedexHelper.any.FedexDetail__r;

        if (fedexDetails == null)
            throw new Exception("FedEx details missing");

        // ✅ 1. Use existing token if valid
        if (!string.IsNullOrEmpty(fedexDetails.Access_Token__c) &&
            fedexDetails.AccessTokenExpire__c.HasValue &&
            fedexDetails.AccessTokenExpire__c > DateTime.UtcNow.AddMinutes(2))
        {
            return new FedexTokenResult
            {
                AccessToken = fedexDetails.Access_Token__c,
                IsNewToken = false
            };
        }

        // 🔁 2. Try refresh token
        if (!string.IsNullOrEmpty(fedexDetails.RefreshToken__c) &&
            fedexDetails.RefreshTokenExpire__c.HasValue &&
            fedexDetails.RefreshTokenExpire__c > DateTime.UtcNow)
        {
            try
            {
                var refreshResponse = await RefreshToken(
                    fedexDetails.UserCredentialKey__c,
                    fedexDetails.UserCredentialPassword__c,
                    fedexDetails.RefreshToken__c
                ).ConfigureAwait(false);

                var token = UpdateTokenValues(fedexDetails, refreshResponse);

                return new FedexTokenResult
                {
                    AccessToken = token,
                    IsNewToken = true
                };
            }
            catch
            {
                // fallback to new token
            }
        }

        // 🆕 3. Generate new token
        var newTokenResponse = await GetFedexAccessToken(
            fedexDetails.UserCredentialKey__c,
            fedexDetails.UserCredentialPassword__c
        ).ConfigureAwait(false);

        var newToken = UpdateTokenValues(fedexDetails, newTokenResponse);

        return new FedexTokenResult
        {
            AccessToken = newToken,
            IsNewToken = true
        };
    }
    #endregion

    #region GENERATE TOKEN
    public async Task<string> GetFedexAccessToken(string clientId, string clientSecret)
    {
        try
        {
            // VERY IMPORTANT (TLS FIX)
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            var handler = new HttpClientHandler()
            {
                UseProxy = false
            };

            using (var client = new HttpClient(handler))
            {
                client.Timeout = TimeSpan.FromSeconds(30);

                var formData = new Dictionary<string, string>
                {
                    { "grant_type", "client_credentials" },
                    { "client_id", clientId },
                    { "client_secret", clientSecret }
                };

                var response = await client.PostAsync(
                    TOKEN_URL,
                    new FormUrlEncodedContent(formData)
                ).ConfigureAwait(false);

                var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                if (!response.IsSuccessStatusCode)
                {
                    throw new Exception("FedEx OAuth failed: " + result);
                }

                return result;
            }
        }
        catch (Exception ex)
        {
            Logger.WriteToLog(Logger.LogType.Error, "FEDEX_TOKEN", "ERROR", "GetFedexAccessToken", ex.Message);
            throw;
        }
    }
    #endregion

    #region REFRESH TOKEN
    public async Task<string> RefreshToken(string clientId, string clientSecret, string refreshToken)
    {
        try
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            using (var client = new HttpClient(new HttpClientHandler { UseProxy = false }))
            {
                client.Timeout = TimeSpan.FromSeconds(30);

                var formData = new Dictionary<string, string>
                {
                    { "grant_type", "refresh_token" },
                    { "refresh_token", refreshToken },
                    { "client_id", clientId },
                    { "client_secret", clientSecret }
                };

                var response = await client.PostAsync(
                    TOKEN_URL,
                    new FormUrlEncodedContent(formData)
                ).ConfigureAwait(false);

                var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                if (!response.IsSuccessStatusCode)
                    throw new Exception("FedEx Refresh Error: " + result);

                return result;
            }
        }
        catch (Exception ex)
        {
            Logger.WriteToLog(Logger.LogType.Error, "FEDEX_REFRESH", "ERROR", "RefreshToken", ex.Message);
            throw;
        }
    }
    #endregion

    #region UPDATE TOKEN VALUES
    private string UpdateTokenValues(FedexHelper.FedexDetailR fedexDetails, string jsonResponse)
    {
        var tokenObj = JObject.Parse(jsonResponse);

        fedexDetails.Access_Token__c = tokenObj["access_token"] != null
            ? tokenObj["access_token"].ToString()
            : "";

        fedexDetails.RefreshToken__c = tokenObj["refresh_token"] != null
            ? tokenObj["refresh_token"].ToString()
            : "";

        int expiresIn = tokenObj["expires_in"] != null ? (int)tokenObj["expires_in"] : 0;
        int refreshExpiresIn = tokenObj["refresh_token_expires_in"] != null
            ? (int)tokenObj["refresh_token_expires_in"]
            : 0;

        fedexDetails.AccessTokenExpire__c = DateTime.UtcNow.AddSeconds(expiresIn);

        if (refreshExpiresIn > 0)
        {
            fedexDetails.RefreshTokenExpire__c = DateTime.UtcNow.AddSeconds(refreshExpiresIn);
        }

        return fedexDetails.Access_Token__c;
    }
    #endregion

    #region RESULT CLASS
    public class FedexTokenResult
    {
        public string AccessToken { get; set; }
        public bool IsNewToken { get; set; }
    }
    #endregion
}