using System.Net;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Managing.Infrastructure.Evm;
using Managing.Infrastructure.Evm.Abstractions;
using Managing.Infrastructure.Evm.Models.Privy;
using Moq;
using Moq.Protected;
using Org.Webpki.JsonCanonicalizer;
using Xunit;

namespace Managing.Infrastructure.Tests;

public class PrivyServiceTests
{
    private readonly PrivyService _privyService;

    public PrivyServiceTests()
    {
        _privyService = new PrivyService(new PrivySettings()
        {
            AppId = "cm7u09v0u002zrkuf2yjjr58p",
            AppSecret = "25wwYu5AgxArU7djgvQEuioc9YSdGY3WN3r1dmXftPfH33KfGVfzopW3vqoPFjy1b8wS2gkDDZ9iQ8yxSo9Vi4iN",
            AuthorizationKey =
                "wallet-auth:MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQggpJ65PCo4E6NYpY867AyE6p1KxOrs8LJqHZw+t+076yhRANCAAS2EM23CtIfQRmHWTxcqb1j5yfrVePjZyBOZZ2RoPZHb9bDGLos206fTuVA3zgLVomlOoHTeYifkBASCn9Mfg3b"
        });
    }

    [Fact]
    public async Task Should_sign_message()
    {
        // Arrange
        var walletId = "cm7vxs99f0007blcl8cmzv74t";
        var message = "Hello, Ethereum";
        var address = "0x932167388dD9aad41149b3cA23eBD489E2E2DD78";

        // Act
        var signature = await _privyService.SignMessageAsync(address, message);

        // Assert
        Assert.NotNull(signature);
    }
    // GsyIYCt202K339eTNmB8ZG/bziRiGUcwFoXwfq85Wf+kpZgaSvJSJ/zO6TSEbDdqrb6JEWVyv4zaBV6j28w2SQ==
    // MEUCIQCxx3BVsVu+GyFI/vIYm2x4hloHojKhpFrnj4KjfypgkgIgFnrlQ8CmJ479qgXpY+wdt1D5ki5+SFzXXBzMd+ckIAM=


    [Fact]
    public async Task Should_Get_User_Wallets()
    {
        var did = "did:privy:cm7vxs99f0007blcl8cmzv74t";
        var result = await _privyService.GetUserWalletsAsync(did);

        Assert.NotNull(result);
    }


    [Fact]
    public async Task Should_Get_User_Wallets_With_Delegation_Status()
    {
        // Arrange
        var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
        var testDid = "did:privy:test123456789";

        // Sample JSON response matching the actual API response structure
        var mockResponse = new
        {
            did = testDid,
            linked_accounts = new object[]
            {
                new
                {
                    type = "wallet",
                    address = "0x123abc456def789ghi",
                    chain_type = "ethereum",
                    chain_id = "eip155:1",
                    delegated = true,
                    verified = true,
                    wallet_index = 0,
                    wallet_client = "privy",
                    wallet_client_type = "privy",
                    connector_type = "embedded",
                    imported = false,
                    recovery_method = "privy",
                    verified_at = 1741180715,
                    first_verified_at = 1741180715,
                    latest_verified_at = 1741180715,
                    id = "abc123"
                },
                new
                {
                    type = "wallet",
                    address = "0x987zyx654wvu321tsr",
                    chain_type = "ethereum",
                    chain_id = "eip155:1",
                    delegated = false,
                    verified = true,
                    wallet_index = 1,
                    wallet_client = "privy",
                    wallet_client_type = "privy",
                    connector_type = "embedded",
                    imported = false,
                    recovery_method = "privy",
                    verified_at = 1741180715,
                    first_verified_at = 1741180715,
                    latest_verified_at = 1741180715,
                    id = "def456"
                },
                new
                {
                    type = "email",
                    address = "test@example.com",
                    verified = true
                }
            }
        };

        var mockResponseJson = JsonSerializer.Serialize(mockResponse);

        // Setup mock HttpClient to return our mock response
        mockHttpMessageHandler
            .Protected()
            .Setup<Task<HttpResponseMessage>>(
                "SendAsync",
                ItExpr.IsAny<HttpRequestMessage>(),
                ItExpr.IsAny<CancellationToken>()
            )
            .ReturnsAsync(new HttpResponseMessage
            {
                StatusCode = HttpStatusCode.OK,
                Content = new StringContent(mockResponseJson, Encoding.UTF8, "application/json")
            });

        var httpClient = new HttpClient(mockHttpMessageHandler.Object)
        {
            BaseAddress = new Uri("https://auth.privy.io/")
        };

        // Create mock settings
        var mockSettings = new Mock<IPrivySettings>();
        mockSettings.Setup(s => s.AppId).Returns("test-app-id");
        mockSettings.Setup(s => s.AppSecret).Returns("test-app-secret");
        mockSettings.Setup(s => s.AuthorizationKey).Returns("wallet-auth:test-auth-key");

        // Create PrivyService with mocked dependencies
        var privyService = new PrivyService(mockSettings.Object);

        // Use reflection to set the private _privyClient field
        var privyClientField = typeof(PrivyService).GetField("_privyClient",
            BindingFlags.Instance | BindingFlags.NonPublic);
        privyClientField.SetValue(privyService, httpClient);

        // Act
        var result = await privyService.GetUserWalletsAsync(testDid);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(testDid, result.Did);
        Assert.Equal(3, result.LinkedAccounts.Count);

        // Verify wallet accounts correctly processed
        var delegatedWallets = result.LinkedAccounts.Where(a => a.Type == "wallet" && a.Delegated).ToList();
        Assert.Single(delegatedWallets);
        Assert.Equal("0x123abc456def789ghi", delegatedWallets[0].Address);
        Assert.Equal("privy", delegatedWallets[0].WalletClient);
        Assert.Equal(0, delegatedWallets[0].WalletIndex);
        Assert.Equal(1741180715, delegatedWallets[0].VerifiedAtTimestamp);
        Assert.NotNull(delegatedWallets[0].VerifiedAt); // Verify timestamp conversion works

        // Verify non-delegated wallet
        var nonDelegatedWallets = result.LinkedAccounts.Where(a => a.Type == "wallet" && !a.Delegated).ToList();
        Assert.Single(nonDelegatedWallets);
        Assert.Equal("0x987zyx654wvu321tsr", nonDelegatedWallets[0].Address);

        // Verify non-wallet account
        var nonWalletAccounts = result.LinkedAccounts.Where(a => a.Type != "wallet").ToList();
        Assert.Single(nonWalletAccounts);
        Assert.Equal("email", nonWalletAccounts[0].Type);

        // Verify HTTP request made correctly
        mockHttpMessageHandler.Protected().Verify(
            "SendAsync",
            Times.Once(),
            ItExpr.Is<HttpRequestMessage>(req =>
                req.Method == HttpMethod.Get &&
                req.RequestUri.ToString().Contains($"/api/v1/users/{testDid}")),
            ItExpr.IsAny<CancellationToken>()
        );
    }

    [Fact]
    public void Should_Generate_Correct_Authorization_Signature()
    {
        // Arrange - Use the exact values from Privy documentation
        var url = "https://api.privy.io/v1/wallets";
        var body = new { chain_type = "ethereum" };
        var httpMethod = "POST";

        // Expected signature from Privy documentation
        var expectedSignature =
            "MEUCIQDvwv0Ci+A+7bqi1x0UNBS7of5tV3dmon8hO3sbD3stSgIgEBGn1EdMukw7IrFS4WgYXbgZhXkp3NXL7O0T7dnO8Ck=";

        // Create a PrivyService instance with the sample auth key from the docs
        var settings = new PrivySettings
        {
            AppId = "cm4db8x9t000ccn87pctvcg9j", // Sample app ID from Privy docs
            AppSecret = "test-app-secret", // Not used for signature generation
            AuthorizationKey =
                "wallet-auth:MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqOBE+hZld+PCaj051uOl0XpEwe3tKBC5tsYsKdnPymGhRANCAAQ2HyYUbLRcfj9obpViwjYU/S7FdNUehkcfjYdd+R2gH/1q0ZJx7mOF1zpiEbbBNRLuXzP0NPN6nonkI8umzLXZ"
        };

        var privyService = new PrivyService(settings);

        // Act
        var actualSignature = privyService.GenerateAuthorizationSignature(url, body, httpMethod);

        // Assert
        Assert.Equal(expectedSignature, actualSignature);
        Console.WriteLine($"Signature verification passed! Generated: {actualSignature}");
    }

    [Fact]
    public void Should_Generate_Same_Signature_For_Identical_Requests()
    {
        // Arrange
        var url = "https://auth.privy.io/v1/wallets/rpc";
        var body = new
        {
            address = "0x932167388dD9aad41149b3cA23eBD489E2E2DD78",
            chain_type = "ethereum",
            method = "personal_sign",
            @params = new
            {
                message = "Hello, Ethereum",
                encoding = "utf-8"
            }
        };

        // Act
        var signature1 = _privyService.GenerateAuthorizationSignature(url, body);
        var signature2 = _privyService.GenerateAuthorizationSignature(url, body);

        // Assert
        Assert.NotNull(signature1);
        Assert.NotNull(signature2);
        Assert.Equal(signature1, signature2); // Signatures should be deterministic for the same input
    }

    [Fact]
    public void Should_Generate_Different_Signatures_For_Different_Inputs()
    {
        // Arrange
        var url = "https://auth.privy.io/v1/wallets/rpc";
        var body1 = new
        {
            address = "0x932167388dD9aad41149b3cA23eBD489E2E2DD78",
            chain_type = "ethereum",
            method = "personal_sign",
            @params = new
            {
                message = "Hello, Ethereum",
                encoding = "utf-8"
            }
        };

        var body2 = new
        {
            address = "0x932167388dD9aad41149b3cA23eBD489E2E2DD78",
            chain_type = "ethereum",
            method = "personal_sign",
            @params = new
            {
                message = "Different message", // Only the message is different
                encoding = "utf-8"
            }
        };

        var differentUrl = "https://auth.privy.io/v1/wallets/different";

        // Act
        var signature1 = _privyService.GenerateAuthorizationSignature(url, body1);
        var signature2 = _privyService.GenerateAuthorizationSignature(url, body2);
        var signature3 = _privyService.GenerateAuthorizationSignature(differentUrl, body1);

        // Assert
        Assert.NotEqual(signature1, signature2); // Different message should produce different signature
        Assert.NotEqual(signature1, signature3); // Different URL should produce different signature
        Assert.NotEqual(signature2, signature3); // Different URL and message should produce different signature
    }

    [Fact]
    public void Should_Generate_Correct_Signature_Payload_Structure()
    {
        // This test validates that we're constructing the payload structure correctly

        // Arrange - Use the same sample data as the signature test
        var url = "https://api.privy.io/v1/wallets";
        var body = new { chain_type = "ethereum" };
        var appId = "cm4db8x9t000ccn87pctvcg9j";
        var httpMethod = "POST";

        // Expected payload structure from the Privy documentation
        var expectedPayloadStructure = new
        {
            version = 1,
            method = "POST",
            url = "https://api.privy.io/v1/wallets",
            body = new { chain_type = "ethereum" },
            headers = new Dictionary<string, string> { { "privy-app-id", appId } }
        };

        // Capture the actual payload by overriding the signature generation
        string capturedPayload = null;

        // We need to create a testable service that lets us inspect the payload
        // Here's a simple mock that captures the JSON payload by inheriting from PrivyService
        var testService = new TestPrivyService(new PrivySettings
        {
            AppId = appId,
            AppSecret = "test-secret",
            AuthorizationKey = "wallet-auth:test-key"
        });

        // Act - Generate the signature (this will capture the payload internally)
        var payload = testService.GetSignaturePayload(url, body, httpMethod);

        // Assert - verify payload matches expected structure

        // Convert both to dictionaries for easier comparison
        var expectedDict = ConvertAnonymousObjectToDictionary(expectedPayloadStructure);
        var actualDict = ConvertAnonymousObjectToDictionary(payload);

        // Check top-level properties
        Assert.Equal(expectedDict["version"], actualDict["version"]);
        Assert.Equal(expectedDict["method"], actualDict["method"]);
        Assert.Equal(expectedDict["url"], actualDict["url"]);

        // Compare body properties
        var expectedBody = ConvertAnonymousObjectToDictionary(expectedDict["body"]);
        var actualBody = ConvertAnonymousObjectToDictionary(actualDict["body"]);
        Assert.Equal(expectedBody["chain_type"], actualBody["chain_type"]);

        // Compare headers
        var expectedHeaders = (Dictionary<string, string>)expectedDict["headers"];
        var actualHeaders = (Dictionary<string, string>)actualDict["headers"];
        Assert.Equal(expectedHeaders["privy-app-id"], actualHeaders["privy-app-id"]);

        Console.WriteLine("Payload structure verification passed!");
    }

    [Fact]
    public async Task Should_Use_n8n_To_sign()
    {
        var httpClient = new HttpClient();
        var url = "https://n8n.kaigen.managing.live/webhook-test/3d931e75-40c8-403d-a26e-6200b328ca85";
        httpClient.BaseAddress = new Uri(url);

        var privyUrl = "https://api.privy.io/v1/wallets";
        var body = new { chain_type = "ethereum" };
        var httpMethod = "POST";

        // Expected signature from Privy documentation
        var expectedSignature =
            "MEUCIQDvwv0Ci+A+7bqi1x0UNBS7of5tV3dmon8hO3sbD3stSgIgEBGn1EdMukw7IrFS4WgYXbgZhXkp3NXL7O0T7dnO8Ck=";

        // Use Privy docs data for the request body
        var signaturePayload = new Dictionary<string, object>
        {
            ["method"] = "POST",
            ["url"] = privyUrl, // Use the FULL URL for signature calculation as per Privy docs
            ["body"] = body,
        };

        // Serialize the payload to JSON
        var jsonPayload = JsonSerializer.Serialize(signaturePayload);

        // Create the request
        var request = new HttpRequestMessage(HttpMethod.Post, url)
        {
            Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
        };

        // Act
        var response = await httpClient.SendAsync(request);

        // Assert
        Assert.NotNull(response);
        Assert.True(response.IsSuccessStatusCode);

        var responseContent = await response.Content.ReadAsStringAsync();
        Assert.Equal(expectedSignature, responseContent);
    }

    [Fact]
    public void Should_Sign_Correctly()
    {
        // Arrange
        var url = "https://api.privy.io/v1/wallets";
        var body = new { chain_type = "ethereum" };
        var httpMethod = "POST";

        var expectedSignature =
            "MEUCIQDvwv0Ci+A+7bqi1x0UNBS7of5tV3dmon8hO3sbD3stSgIgEBGn1EdMukw7IrFS4WgYXbgZhXkp3NXL7O0T7dnO8Ck=";

        var settings = new PrivySettings
        {
            AppId = "cm4db8x9t000ccn87pctvcg9j",
            AuthorizationKey =
                "wallet-auth:MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqOBE+hZld+PCaj051uOl0XpEwe3tKBC5tsYsKdnPymGhRANCAAQ2HyYUbLRcfj9obpViwjYU/S7FdNUehkcfjYdd+R2gH/1q0ZJx7mOF1zpiEbbBNRLuXzP0NPN6nonkI8umzLXZ"
        };

        var payload = new
        {
            version = 1,
            method = httpMethod,
            url = url.TrimEnd('/'),
            body = body,
            headers = new
            {
                privy_app_id = settings.AppId
            }
        };

        string serializedPayload = JsonSerializer.Serialize(payload);
        JsonCanonicalizer jsonCanonicalizer = new JsonCanonicalizer(serializedPayload);
        byte[] canonicalizedBytes = jsonCanonicalizer.GetEncodedUTF8();

        Console.WriteLine($"Canonicalized JSON: {Encoding.UTF8.GetString(canonicalizedBytes)}");

        byte[] privateKeyBytes = Convert.FromBase64String(settings.AuthorizationKey.Replace("wallet-auth:", ""));
        using var privateKey = ECDsa.Create(ECCurve.NamedCurves.nistP256);
        privateKey.ImportPkcs8PrivateKey(privateKeyBytes, out _);

        byte[] signatureBuffer = privateKey.SignData(canonicalizedBytes, HashAlgorithmName.SHA256);
        string signature = Convert.ToBase64String(signatureBuffer);

        Console.WriteLine($"Generated Signature: {signature}");

        Assert.Equal(expectedSignature, signature);
    }

    // Helper class for testing the payload structure
    private class TestPrivyService : PrivyService
    {
        public TestPrivyService(IPrivySettings settings) : base(settings)
        {
        }

        public object GetSignaturePayload(string url, object body, string httpMethod)
        {
            // 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(new Uri("https://auth.privy.io/"), relativePath).ToString();
            }

            // Create the signature payload structure exactly as in the GenerateAuthorizationSignature method
            var headers = new Dictionary<string, string> { { "privy-app-id", GetPrivyAppId() } };

            return new
            {
                version = 1,
                method = httpMethod,
                url = fullUrl,
                body = body,
                headers = headers
            };
        }

        // Helper to expose the private _appId
        private string GetPrivyAppId()
        {
            // Use reflection to get the private field
            var field = typeof(PrivyService).GetField("_appId",
                BindingFlags.Instance | BindingFlags.NonPublic);
            return (string)field.GetValue(this);
        }
    }

    // Helper for converting anonymous objects to dictionaries for easier comparison
    private Dictionary<string, object> ConvertAnonymousObjectToDictionary(object obj)
    {
        if (obj is Dictionary<string, object> dict)
            return dict;

        var result = new Dictionary<string, object>();
        foreach (var prop in obj.GetType().GetProperties())
        {
            result[prop.Name] = prop.GetValue(obj);
        }

        return result;
    }
}