using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Models;
using Managing.Infrastructure.Evm.Models.Privy;
using Org.Webpki.JsonCanonicalizer;

public class PrivyService : IPrivyService
{
    private readonly HttpClient _privyClient;
    private readonly string _appId;
    private readonly string _appSecret;
    private readonly string _authorizationKey;

    public PrivyService(IPrivySettings settings)
    {
        _privyClient = new HttpClient();
        _appId = settings.AppId;
        _appSecret = settings.AppSecret;
        _authorizationKey = settings.AuthorizationKey;

        ConfigureHttpClient();
    }

    private void ConfigureHttpClient()
    {
        _privyClient.BaseAddress = new Uri("https://auth.privy.io/");
        var authToken = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{_appId}:{_appSecret}"));
        _privyClient.DefaultRequestHeaders.Add("privy-app-id", _appId);
        _privyClient.DefaultRequestHeaders.Add("Authorization", $"Basic {authToken}");
        _privyClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

    /// <summary>
    /// Generates an authorization signature for a request to the Privy API
    /// </summary>
    /// <param name="url">The full URL for the request</param>
    /// <param name="body">The request body</param>
    /// <param name="httpMethod">The HTTP method to use for the request (defaults to POST)</param>
    /// <returns>The generated signature</returns>
    public string GenerateAuthorizationSignature(string url, object body, string httpMethod = "POST")
    {
        try
        {
            // Ensure we have a full, absolute URL for signature calculation
            string fullUrl;
            
            if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
            {
                // Already a full URL
                fullUrl = url;
            }
            else
            {
                // It's a relative path, so construct the full URL using the base address
                string relativePath = url.StartsWith("/") ? url.Substring(1) : url;
                fullUrl = new Uri(_privyClient.BaseAddress, relativePath).ToString();
            }
            
            Console.WriteLine($"Full URL for signature: {fullUrl}");
            
            // Create a new dictionary for headers to ensure consistent ordering
            var headers = new Dictionary<string, string>
            {
                { "privy-app-id", _appId }
            };

            // Create the properly structured payload object according to Privy's specification
            var signaturePayload = new Dictionary<string, object>
            {
                ["version"] = 1,
                ["method"] = httpMethod,
                ["url"] = fullUrl, // Use the FULL URL for signature calculation as per Privy docs
                ["body"] = body,
                ["headers"] = headers
            };

            // Serialize to JSON with consistent settings
            // Note: We're not forcing camelCase conversion, preserving original property casing
            var options = new JsonSerializerOptions
            {
                WriteIndented = false,
                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
                PropertyNamingPolicy = null // Preserve original property casing
            };

            string serializedPayload = JsonSerializer.Serialize(signaturePayload, options);
            Console.WriteLine($"Request payload for signature: {serializedPayload}");

            // Use the JSON Canonicalizer to ensure consistent JSON formatting
            JsonCanonicalizer jsonCanonicalizer = new JsonCanonicalizer(serializedPayload);
            byte[] canonicalizedBytes = jsonCanonicalizer.GetEncodedUTF8();
            string canonicalizedString = jsonCanonicalizer.GetEncodedString();
            Console.WriteLine($"Request jsonCanonicalizer payload for signature: {canonicalizedString}");

            // Remove the 'wallet-auth:' prefix from the authorization key
            string privateKeyAsString = _authorizationKey.Replace("wallet-auth:", "");

            // Convert the private key to PEM format
            string privateKeyAsPem = $"-----BEGIN PRIVATE KEY-----\n{privateKeyAsString}\n-----END PRIVATE KEY-----";

            // Create a private key object explicitly using ECDSA P-256 curve
            using var privateKey = ECDsa.Create(ECCurve.NamedCurves.nistP256);
            privateKey.ImportFromPem(privateKeyAsPem);

            // Sign the canonicalized payload buffer with the private key using SHA-256
            // CngAlgorithm.ECDsaP256 is implicitly used through the curve specification above
            byte[] signatureBuffer = privateKey.SignData(canonicalizedBytes, HashAlgorithmName.SHA256);

            // Convert the signature to a base64 string
            string signature = Convert.ToBase64String(signatureBuffer);
            Console.WriteLine($"Generated signature: {signature}");
            
            return signature;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error generating signature: {ex.Message}");
            Console.WriteLine($"Stack trace: {ex.StackTrace}");
            if (ex.InnerException != null)
            {
                Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
            }
            throw new Exception($"Failed to generate authorization signature: {ex.Message}", ex);
        }
    }

    /// <summary>
    /// Generates an authorization signature for delegated actions and sends the HTTP request with the same payload
    /// </summary>
    /// <param name="url">The full URL for the request</param>
    /// <param name="body">The request body</param>
    /// <param name="httpMethod">The HTTP method to use for the request (defaults to POST)</param>
    /// <returns>The HTTP response from the request</returns>
    private async Task<HttpResponseMessage> GenerateAuthorizationSignatureAndSendRequestAsync(string url, object body, HttpMethod httpMethod = null)
    {
        httpMethod ??= HttpMethod.Post;
        
        try
        {
            // Ensure we have a full, absolute URL for the request
            string fullUrl;
            string requestPath;
            
            if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
            {
                // Already a full URL
                fullUrl = url;
                
                // For the HTTP request, we need just the path if it matches our base address
                if (uri.Host == new Uri(_privyClient.BaseAddress.ToString()).Host)
                {
                    requestPath = uri.PathAndQuery;
                }
                else
                {
                    // Using a different host than the base address
                    throw new InvalidOperationException($"URL host {uri.Host} doesn't match base address host {_privyClient.BaseAddress.Host}");
                }
            }
            else
            {
                // It's a relative path, so construct the full URL using the base address
                string relativePath = url.StartsWith("/") ? url.Substring(1) : url;
                fullUrl = new Uri(_privyClient.BaseAddress, relativePath).ToString();
                requestPath = url.StartsWith("/") ? url : $"/{url}";
            }
            
            Console.WriteLine($"Full URL for signature: {fullUrl}");
            Console.WriteLine($"Request path for HTTP request: {requestPath}");
            
            // Generate the authorization signature
            string signature = GenerateAuthorizationSignature(fullUrl, body, httpMethod.Method);
            
            // Prepare the JSON serialization options
            var options = new JsonSerializerOptions
            {
                WriteIndented = false,
                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
                PropertyNamingPolicy = null // Preserve original property casing
            };
            
            // Create the HTTP request
            var request = new HttpRequestMessage(httpMethod, requestPath);
            
            // Use the same serialization options to ensure the request body is identical to what we signed
            var json = JsonSerializer.Serialize(body, options);
            
            // Create StringContent with explicit Content-Type header
            var content = new StringContent(json, Encoding.UTF8);
            content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            request.Content = content;
            
            // Add the headers in the same order we used for signing
            request.Headers.Add("privy-app-id", _appId);
            request.Headers.Add("privy-authorization-signature", signature);
            
            // Log all request headers and content for debugging
            Console.WriteLine($"Sending request to {fullUrl}");
            Console.WriteLine($"With signature: {signature}");
            Console.WriteLine($"Request content: {json}");
            Console.WriteLine("Request headers:");
            foreach (var header in request.Headers)
            {
                Console.WriteLine($"  {header.Key}: {string.Join(", ", header.Value)}");
            }
            if (request.Content != null && request.Content.Headers != null)
            {
                Console.WriteLine("Content headers:");
                foreach (var header in request.Content.Headers)
                {
                    Console.WriteLine($"  {header.Key}: {string.Join(", ", header.Value)}");
                }
            }
            
            // Send the request and return the response
            var response = await _privyClient.SendAsync(request);
            
            // Log response information
            Console.WriteLine($"Response status: {response.StatusCode}");
            string responseContent = await response.Content.ReadAsStringAsync();
            Console.WriteLine($"Response content: {responseContent}");
            
            return response;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error sending request: {ex.Message}");
            Console.WriteLine($"Stack trace: {ex.StackTrace}");
            if (ex.InnerException != null)
            {
                Console.WriteLine($"Inner exception: {ex.InnerException.Message}");
            }
            throw new Exception($"Failed to send request: {ex.Message}", ex);
        }
    }

    /// <summary>
    /// Adds the authorization signature header to the request
    /// </summary>
    /// <remarks>
    /// This method is kept for backward compatibility. 
    /// Prefer using GenerateAuthorizationSignatureAndSendRequestAsync which both generates
    /// the signature and sends the request with the same payload.
    /// </remarks>
    /// <param name="request">The HTTP request message</param>
    /// <param name="url">The full URL for the request</param>
    /// <param name="body">The request body</param>
    private void AddAuthorizationSignatureHeader(HttpRequestMessage request, string url, object body)
    {
        if (!string.IsNullOrEmpty(_authorizationKey))
        {
            string signature = GenerateAuthorizationSignature(url, body);
            request.Headers.Add("privy-authorization-signature", signature);
        }
    }

    public async Task<PrivyWallet> CreateWalletAsync(string chainType = "ethereum")
    {
        try
        {
            var requestBody = new { chain_type = chainType };
            var url = "https://api.privy.io/v1/wallets";

            // Use the new method that both generates the signature and sends the request
            var response = await GenerateAuthorizationSignatureAndSendRequestAsync(url, requestBody);

            var result = new PrivyWallet();

            if (response.IsSuccessStatusCode)
            {
                result = await response.Content.ReadFromJsonAsync<PrivyWallet>();
            }
            else
            {
                throw new Exception(await response.Content.ReadAsStringAsync());
            }

            return result;
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message);
        }
    }

    public async Task<HttpResponseMessage> SendTransactionAsync(string walletId, string recipientAddress, long value,
        string caip2 = "eip155:84532")
    {
        var requestBody = new
        {
            method = "eth_sendTransaction",
            caip2,
            @params = new
            {
                transaction = new
                {
                    to = recipientAddress,
                    value
                }
            }
        };

        var url = $"https://api.privy.io/v1/wallets/{walletId}/rpc";
        
        // Use the new method that both generates the signature and sends the request
        return await GenerateAuthorizationSignatureAndSendRequestAsync(url, requestBody);
    }

    public class PrivyRequest
    {
        [JsonPropertyName("method")] public string Method { get; set; }
        [JsonPropertyName("chain_type")] public string ChainType { get; set; }
        [JsonPropertyName("address")] public string Address { get; set; }
        [JsonPropertyName("params")] public PrivyParamsRequest Params { get; set; }
    }

    public class PrivyParamsRequest
    {
        [JsonPropertyName("message")] public string Message { get; set; }
        [JsonPropertyName("encoding")] public string Encoding { get; set; }
    }

    /// <summary>
    /// Signs a message using the embedded wallet
    /// </summary>
    /// <param name="embeddedWallet">The address of the embedded wallet</param>
    /// <param name="message">The message to sign</param>
    /// <param name="method">The signing method to use (e.g., "personal_sign", "eth_sign")</param>
    /// <returns>The signature response</returns>
    public async Task<string> SignMessageAsync(string embeddedWallet, string message,
        string method = "personal_sign")
    {
        try
        {
            // Construct the request body using the exact format from Privy documentation
            var requestBody = new
            {
                address = embeddedWallet,
                chain_type = "ethereum",
                method = method,
                @params = new
                {
                    message = message,
                    encoding = "utf-8"
                }
            };

            // The full URL for the Privy RPC endpoint exactly as specified in docs
            var url = "https://auth.privy.io/v1/wallets/rpc";

            // Use the new method that both generates the signature and sends the request
            var response = await GenerateAuthorizationSignatureAndSendRequestAsync(url, requestBody);
            
            // Check for successful response
            string responseContent = await response.Content.ReadAsStringAsync();

            if (!response.IsSuccessStatusCode)
            {
                throw new Exception($"Failed to sign message: {response.StatusCode} - {responseContent}");
            }

            // Parse the response to get the signature
            var responseObject = JsonSerializer.Deserialize<JsonElement>(responseContent);

            // Extract the signature from the response
            if (responseObject.TryGetProperty("data", out var dataElement))
            {
                string signatureResult = dataElement.GetString() ?? string.Empty;
                Console.WriteLine($"Extracted signature: {signatureResult}");
                return signatureResult;
            }

            throw new Exception($"Invalid signature response format: {responseContent}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"SignMessageAsync error: {ex}");
            throw new Exception($"Error signing message: {ex.Message}", ex);
        }
    }

    /// <summary>
    /// Signs typed data (EIP-712) using the embedded wallet
    /// </summary>
    /// <param name="walletId">The ID of the wallet to use for signing</param>
    /// <param name="typedData">The typed data to sign (must be a valid JSON string conforming to EIP-712)</param>
    /// <param name="caip2">The CAIP-2 chain identifier</param>
    /// <returns>The signature</returns>
    public async Task<string> SignTypedDataAsync(string walletId, string typedData, string caip2 = "eip155:84532")
    {
        try
        {
            // Parse the typed data to ensure it's valid JSON
            var typedDataJson = JsonSerializer.Deserialize<JsonElement>(typedData);

            // Construct the request body according to the Privy documentation
            var requestBody = new
            {
                method = "eth_signTypedData_v4",
                caip2,
                @params = new[] { walletId, typedData }
            };

            // Construct the full URL for the request
            var url = $"https://api.privy.io/v1/wallets/{walletId}/rpc";

            // Use the new method that both generates the signature and sends the request
            var response = await GenerateAuthorizationSignatureAndSendRequestAsync(url, requestBody);

            // Handle the response
            if (!response.IsSuccessStatusCode)
            {
                var errorContent = await response.Content.ReadAsStringAsync();
                throw new Exception($"Failed to sign typed data: {errorContent}");
            }

            // Parse the response to get the signature
            var responseContent = await response.Content.ReadAsStringAsync();
            var responseObject = JsonSerializer.Deserialize<JsonElement>(responseContent);

            // Extract the signature from the response
            if (responseObject.TryGetProperty("data", out var dataElement))
            {
                return dataElement.GetString() ?? string.Empty;
            }

            throw new Exception($"Invalid signature response format: {responseContent}");
        }
        catch (JsonException ex)
        {
            throw new Exception($"Invalid typed data JSON format: {ex.Message}", ex);
        }
        catch (Exception ex)
        {
            throw new Exception($"Error signing typed data: {ex.Message}", ex);
        }
    }

    /// <summary>
    /// Gets information about a user, including their linked wallet accounts and delegation status
    /// </summary>
    /// <param name="userDid">The Privy DID of the user (format: did:privy:XXXXX)</param>
    /// <returns>User information including wallets and delegation status</returns>
    public async Task<PrivyUserInfo> GetUserWalletsAsync(string userDid)
    {
        if (string.IsNullOrEmpty(userDid))
        {
            throw new ArgumentException("User DID cannot be null or empty", nameof(userDid));
        }

        if (!userDid.StartsWith("did:privy:"))
        {
            throw new ArgumentException("User DID must start with 'did:privy:'", nameof(userDid));
        }

        try
        {
            // Construct the URL for getting user information
            var url = $"/api/v1/users/{userDid}";

            // Create the HTTP request
            var request = new HttpRequestMessage(HttpMethod.Get, url);

            // Send the request
            var response = await _privyClient.SendAsync(request);

            // Check for success
            if (!response.IsSuccessStatusCode)
            {
                var errorContent = await response.Content.ReadAsStringAsync();
                throw new Exception($"Failed to get user wallets: {response.StatusCode} - {errorContent}");
            }

            // Parse the response
            var responseContent = await response.Content.ReadAsStringAsync();
            Console.WriteLine($"User API Response: {responseContent}");

            var options = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
                PropertyNameCaseInsensitive = true,
                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
            };

            // Parse the response manually to handle potentially unexpected formats
            using var document = JsonDocument.Parse(responseContent);
            var root = document.RootElement;

            // Create the user info object
            var userInfo = new PrivyUserInfo();

            // Extract the DID
            if (root.TryGetProperty("did", out var didElement))
            {
                userInfo.Did = didElement.GetString();
            }

            // Extract timestamps if they exist
            if (root.TryGetProperty("created_at", out var createdElement) &&
                createdElement.TryGetInt64(out var createdTimestamp))
            {
                userInfo.CreatedAtTimestamp = createdTimestamp;
            }

            if (root.TryGetProperty("updated_at", out var updatedElement) &&
                updatedElement.TryGetInt64(out var updatedTimestamp))
            {
                userInfo.UpdatedAtTimestamp = updatedTimestamp;
            }

            // Extract linked accounts
            if (root.TryGetProperty("linked_accounts", out var accountsArray) &&
                accountsArray.ValueKind == JsonValueKind.Array)
            {
                foreach (var accountElement in accountsArray.EnumerateArray())
                {
                    try
                    {
                        var account =
                            JsonSerializer.Deserialize<PrivyLinkedAccount>(accountElement.GetRawText(), options);
                        if (account != null)
                        {
                            userInfo.LinkedAccounts.Add(account);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Error deserializing account: {ex.Message}");
                        // Continue with the next account if one fails
                    }
                }
            }

            return userInfo;
        }
        catch (Exception ex)
        {
            throw new Exception($"Error retrieving user wallets: {ex.Message}", ex);
        }
    }
}