using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Polly;
using Polly.CircuitBreaker;
using Polly.Retry;

namespace MktXService.Infrastructure.ExternalServices.Twitter;

/// <summary>
/// EN: Twitter API v2 client implementation with Polly resilience.
/// VI: Triển khai Twitter API v2 client với Polly resilience.
/// </summary>
/// <remarks>
/// EN: Uses OAuth 1.0a authentication and implements rate limiting handling.
/// VI: Sử dụng xác thực OAuth 1.0a và xử lý rate limiting.
/// </remarks>
public class TwitterApiClient : ITwitterApiClient
{
    private readonly HttpClient _httpClient;
    private readonly TwitterApiOptions _options;
    private readonly ILogger<TwitterApiClient> _logger;
    private readonly AsyncRetryPolicy<HttpResponseMessage> _retryPolicy;
    private readonly AsyncCircuitBreakerPolicy<HttpResponseMessage> _circuitBreakerPolicy;

    private const string BaseUrl = "https://api.twitter.com/2";
    private const string UploadUrl = "https://upload.twitter.com/1.1/media/upload.json";

    public TwitterApiClient(
        HttpClient httpClient,
        IOptions<TwitterApiOptions> options,
        ILogger<TwitterApiClient> logger)
    {
        _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));

        // EN: Configure resilience policies
        // VI: Cấu hình các policy resilience
        _retryPolicy = Policy<HttpResponseMessage>
            .Handle<HttpRequestException>()
            .OrResult(r => r.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
            .WaitAndRetryAsync(
                3,
                retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                (outcome, timespan, retryCount, context) =>
                {
                    _logger.LogWarning("Twitter API retry {RetryCount} after {Delay}s", retryCount, timespan.TotalSeconds);
                });

        _circuitBreakerPolicy = Policy<HttpResponseMessage>
            .Handle<HttpRequestException>()
            .OrResult(r => (int)r.StatusCode >= 500)
            .CircuitBreakerAsync(
                5,
                TimeSpan.FromMinutes(1),
                (outcome, breakDuration) =>
                {
                    _logger.LogError("Twitter API circuit breaker opened for {Duration}s", breakDuration.TotalSeconds);
                },
                () => _logger.LogInformation("Twitter API circuit breaker reset"));
    }

    #region Direct Messages

    public async Task<TwitterMessageResult> SendDirectMessageAsync(
        string recipientId, 
        string text, 
        CancellationToken cancellationToken = default)
    {
        try
        {
            var payload = new
            {
                text,
                participant_id = recipientId
            };

            var response = await ExecuteWithPoliciesAsync(
                () => CreateAuthenticatedRequest(HttpMethod.Post, $"{BaseUrl}/dm_conversations/with/{recipientId}/messages", payload),
                cancellationToken);

            if (response.IsSuccessStatusCode)
            {
                var content = await response.Content.ReadAsStringAsync(cancellationToken);
                var result = JsonSerializer.Deserialize<JsonElement>(content);
                var messageId = result.GetProperty("data").GetProperty("dm_event_id").GetString();
                
                return new TwitterMessageResult(true, messageId, null);
            }

            var error = await response.Content.ReadAsStringAsync(cancellationToken);
            _logger.LogError("Twitter API error: {Error}", error);
            return new TwitterMessageResult(false, null, error);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to send direct message to {RecipientId}", recipientId);
            return new TwitterMessageResult(false, null, ex.Message);
        }
    }

    public async Task<TwitterMessageResult> SendDirectMessageWithMediaAsync(
        string recipientId, 
        string text, 
        string mediaId,
        CancellationToken cancellationToken = default)
    {
        try
        {
            var payload = new
            {
                text,
                attachments = new[] { new { media_id = mediaId } }
            };

            var response = await ExecuteWithPoliciesAsync(
                () => CreateAuthenticatedRequest(HttpMethod.Post, $"{BaseUrl}/dm_conversations/with/{recipientId}/messages", payload),
                cancellationToken);

            if (response.IsSuccessStatusCode)
            {
                var content = await response.Content.ReadAsStringAsync(cancellationToken);
                var result = JsonSerializer.Deserialize<JsonElement>(content);
                var messageId = result.GetProperty("data").GetProperty("dm_event_id").GetString();
                
                return new TwitterMessageResult(true, messageId, null);
            }

            var error = await response.Content.ReadAsStringAsync(cancellationToken);
            return new TwitterMessageResult(false, null, error);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to send direct message with media to {RecipientId}", recipientId);
            return new TwitterMessageResult(false, null, ex.Message);
        }
    }

    public async Task<IEnumerable<TwitterDirectMessage>> GetConversationAsync(
        string participantId,
        int maxResults = 50,
        string? paginationToken = null,
        CancellationToken cancellationToken = default)
    {
        var url = $"{BaseUrl}/dm_conversations/with/{participantId}/dm_events?max_results={maxResults}";
        if (!string.IsNullOrEmpty(paginationToken))
            url += $"&pagination_token={paginationToken}";

        var response = await ExecuteWithPoliciesAsync(
            () => CreateAuthenticatedRequest(HttpMethod.Get, url),
            cancellationToken);

        if (!response.IsSuccessStatusCode)
            return Enumerable.Empty<TwitterDirectMessage>();

        var content = await response.Content.ReadAsStringAsync(cancellationToken);
        var result = JsonSerializer.Deserialize<JsonElement>(content);

        var messages = new List<TwitterDirectMessage>();
        if (result.TryGetProperty("data", out var data))
        {
            foreach (var item in data.EnumerateArray())
            {
                messages.Add(new TwitterDirectMessage(
                    item.GetProperty("id").GetString()!,
                    item.GetProperty("sender_id").GetString()!,
                    participantId,
                    item.TryGetProperty("text", out var text) ? text.GetString()! : "",
                    DateTime.Parse(item.GetProperty("created_at").GetString()!),
                    null
                ));
            }
        }

        return messages;
    }

    #endregion

    #region User Operations

    public async Task<TwitterUser?> GetUserByIdAsync(string userId, CancellationToken cancellationToken = default)
    {
        var url = $"{BaseUrl}/users/{userId}?user.fields=profile_image_url,description,verified,public_metrics";
        return await GetUserAsync(url, cancellationToken);
    }

    public async Task<TwitterUser?> GetUserByUsernameAsync(string username, CancellationToken cancellationToken = default)
    {
        var url = $"{BaseUrl}/users/by/username/{username}?user.fields=profile_image_url,description,verified,public_metrics";
        return await GetUserAsync(url, cancellationToken);
    }

    public async Task<TwitterUser?> VerifyCredentialsAsync(CancellationToken cancellationToken = default)
    {
        var url = $"{BaseUrl}/users/me?user.fields=profile_image_url,description,verified,public_metrics";
        return await GetUserAsync(url, cancellationToken);
    }

    private async Task<TwitterUser?> GetUserAsync(string url, CancellationToken cancellationToken)
    {
        var response = await ExecuteWithPoliciesAsync(
            () => CreateAuthenticatedRequest(HttpMethod.Get, url),
            cancellationToken);

        if (!response.IsSuccessStatusCode)
            return null;

        var content = await response.Content.ReadAsStringAsync(cancellationToken);
        var result = JsonSerializer.Deserialize<JsonElement>(content);

        if (!result.TryGetProperty("data", out var data))
            return null;

        var metrics = data.TryGetProperty("public_metrics", out var pm) ? pm : default;

        return new TwitterUser(
            data.GetProperty("id").GetString()!,
            data.GetProperty("username").GetString()!,
            data.GetProperty("name").GetString()!,
            data.TryGetProperty("profile_image_url", out var img) ? img.GetString() : null,
            data.TryGetProperty("description", out var desc) ? desc.GetString() : null,
            data.TryGetProperty("verified", out var ver) && ver.GetBoolean(),
            metrics.ValueKind != JsonValueKind.Undefined && metrics.TryGetProperty("followers_count", out var fc) ? fc.GetInt32() : 0,
            metrics.ValueKind != JsonValueKind.Undefined && metrics.TryGetProperty("following_count", out var foc) ? foc.GetInt32() : 0
        );
    }

    #endregion

    #region Webhooks

    public async Task<string> RegisterWebhookAsync(string webhookUrl, CancellationToken cancellationToken = default)
    {
        var url = $"https://api.twitter.com/1.1/account_activity/all/{_options.EnvironmentName}/webhooks.json?url={Uri.EscapeDataString(webhookUrl)}";
        
        var response = await ExecuteWithPoliciesAsync(
            () => CreateAuthenticatedRequest(HttpMethod.Post, url),
            cancellationToken);

        if (!response.IsSuccessStatusCode)
            throw new TwitterApiException($"Failed to register webhook: {await response.Content.ReadAsStringAsync(cancellationToken)}");

        var content = await response.Content.ReadAsStringAsync(cancellationToken);
        var result = JsonSerializer.Deserialize<JsonElement>(content);
        return result.GetProperty("id").GetString()!;
    }

    public async Task DeleteWebhookAsync(string webhookId, CancellationToken cancellationToken = default)
    {
        var url = $"https://api.twitter.com/1.1/account_activity/all/{_options.EnvironmentName}/webhooks/{webhookId}.json";
        
        var response = await ExecuteWithPoliciesAsync(
            () => CreateAuthenticatedRequest(HttpMethod.Delete, url),
            cancellationToken);

        if (!response.IsSuccessStatusCode)
            throw new TwitterApiException($"Failed to delete webhook: {await response.Content.ReadAsStringAsync(cancellationToken)}");
    }

    public async Task SubscribeToWebhookAsync(CancellationToken cancellationToken = default)
    {
        var url = $"https://api.twitter.com/1.1/account_activity/all/{_options.EnvironmentName}/subscriptions.json";
        
        var response = await ExecuteWithPoliciesAsync(
            () => CreateAuthenticatedRequest(HttpMethod.Post, url),
            cancellationToken);

        if (!response.IsSuccessStatusCode)
            throw new TwitterApiException($"Failed to subscribe to webhook: {await response.Content.ReadAsStringAsync(cancellationToken)}");
    }

    public string GenerateCrcResponse(string crcToken)
    {
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_options.ConsumerSecret));
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(crcToken));
        return $"sha256={Convert.ToBase64String(hash)}";
    }

    #endregion

    #region Media

    public async Task<string> UploadMediaAsync(byte[] mediaData, string mediaType, CancellationToken cancellationToken = default)
    {
        using var content = new MultipartFormDataContent();
        content.Add(new ByteArrayContent(mediaData), "media", "media");
        content.Add(new StringContent(mediaType), "media_category");

        var request = new HttpRequestMessage(HttpMethod.Post, UploadUrl) { Content = content };
        AddOAuthHeader(request);

        var response = await _httpClient.SendAsync(request, cancellationToken);
        
        if (!response.IsSuccessStatusCode)
            throw new TwitterApiException($"Failed to upload media: {await response.Content.ReadAsStringAsync(cancellationToken)}");

        var result = await response.Content.ReadAsStringAsync(cancellationToken);
        var json = JsonSerializer.Deserialize<JsonElement>(result);
        return json.GetProperty("media_id_string").GetString()!;
    }

    #endregion

    #region Rate Limiting

    public Task<RateLimitInfo> GetRateLimitInfoAsync(string endpoint)
    {
        // EN: Rate limit info is typically extracted from response headers
        // VI: Thông tin rate limit thường được trích xuất từ response headers
        // For now, return default values
        return Task.FromResult(new RateLimitInfo(15, 15, DateTime.UtcNow.AddMinutes(15)));
    }

    #endregion

    #region Private Helpers

    private async Task<HttpResponseMessage> ExecuteWithPoliciesAsync(
        Func<HttpRequestMessage> requestFactory,
        CancellationToken cancellationToken)
    {
        return await _retryPolicy.ExecuteAsync(async () =>
        {
            return await _circuitBreakerPolicy.ExecuteAsync(async () =>
            {
                var request = requestFactory();
                AddOAuthHeader(request);
                return await _httpClient.SendAsync(request, cancellationToken);
            });
        });
    }

    private HttpRequestMessage CreateAuthenticatedRequest(HttpMethod method, string url, object? payload = null)
    {
        var request = new HttpRequestMessage(method, url);
        
        if (payload != null)
        {
            request.Content = new StringContent(
                JsonSerializer.Serialize(payload),
                Encoding.UTF8,
                "application/json");
        }

        return request;
    }

    private void AddOAuthHeader(HttpRequestMessage request)
    {
        // EN: OAuth 1.0a signature generation
        // VI: Tạo chữ ký OAuth 1.0a
        var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
        var nonce = Guid.NewGuid().ToString("N");

        var parameters = new SortedDictionary<string, string>
        {
            ["oauth_consumer_key"] = _options.ConsumerKey,
            ["oauth_nonce"] = nonce,
            ["oauth_signature_method"] = "HMAC-SHA1",
            ["oauth_timestamp"] = timestamp,
            ["oauth_token"] = _options.AccessToken,
            ["oauth_version"] = "1.0"
        };

        // EN: Create signature base string
        var baseString = CreateSignatureBaseString(request.Method.Method, request.RequestUri!.ToString(), parameters);
        var signingKey = $"{Uri.EscapeDataString(_options.ConsumerSecret)}&{Uri.EscapeDataString(_options.AccessTokenSecret)}";

        using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(signingKey));
        var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(baseString)));

        parameters["oauth_signature"] = signature;

        var authHeader = "OAuth " + string.Join(", ", parameters.Select(p => $"{p.Key}=\"{Uri.EscapeDataString(p.Value)}\""));
        request.Headers.Authorization = new AuthenticationHeaderValue("OAuth", authHeader[6..]);
    }

    private static string CreateSignatureBaseString(string method, string url, SortedDictionary<string, string> parameters)
    {
        var uri = new Uri(url);
        var baseUrl = $"{uri.Scheme}://{uri.Host}{uri.AbsolutePath}";
        var paramString = string.Join("&", parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));
        return $"{method.ToUpper()}&{Uri.EscapeDataString(baseUrl)}&{Uri.EscapeDataString(paramString)}";
    }

    #endregion
}

/// <summary>
/// EN: Twitter API configuration options.
/// VI: Các tùy chọn cấu hình Twitter API.
/// </summary>
public class TwitterApiOptions
{
    public string ConsumerKey { get; set; } = string.Empty;
    public string ConsumerSecret { get; set; } = string.Empty;
    public string AccessToken { get; set; } = string.Empty;
    public string AccessTokenSecret { get; set; } = string.Empty;
    public string EnvironmentName { get; set; } = "production";
}

/// <summary>
/// EN: Exception for Twitter API errors.
/// VI: Exception cho lỗi Twitter API.
/// </summary>
public class TwitterApiException : Exception
{
    public TwitterApiException(string message) : base(message) { }
    public TwitterApiException(string message, Exception inner) : base(message, inner) { }
}
