using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Nethereum.Signer;
using Nethereum.Signer.EIP712;
using Nethereum.ABI.FunctionEncoding.Attributes;
using Nethereum.ABI.EIP712;
using Nethereum.Util;
using PolyTraderSharp.Models;

namespace PolyTraderSharp.Services
{
    [Struct("EIP712Domain")]
    public class ClobDomain
    {
        [Parameter("string", "name", 1)]
        public string Name { get; set; } = string.Empty;

        [Parameter("string", "version", 2)]
        public string Version { get; set; } = "";
        
        [Parameter("uint256", "chainId", 3)]
        public System.Numerics.BigInteger ChainId { get; set; }
    }

    [Struct("EIP712Domain")]
    public class CtfDomain
    {
        [Parameter("string", "name", 1)]
        public string Name { get; set; } = string.Empty;

        [Parameter("string", "version", 2)]
        public string Version { get; set; } = string.Empty;

        [Parameter("uint256", "chainId", 3)]
        public ulong ChainId { get; set; }

        [Parameter("address", "verifyingContract", 4)]
        public string VerifyingContract { get; set; } = string.Empty;
    }

    [Struct("ClobAuth")]
    public class ClobAuth
    {
        [Parameter("address", "address", 1)]
        public string Address { get; set; } = string.Empty;

        [Parameter("string", "timestamp", 2)]
        public string Timestamp { get; set; } = "";
        
        [Parameter("uint256", "nonce", 3)]
        public System.Numerics.BigInteger Nonce { get; set; }
        
        [Parameter("string", "message", 4)]
        public string Message { get; set; } = string.Empty;
    }

    [Struct("Order")]
    public class CtfOrder
    {
        [Parameter("uint256", "salt", 1)]
        public System.Numerics.BigInteger Salt { get; set; }

        [Parameter("address", "maker", 2)]
        public string Maker { get; set; } = string.Empty;

        [Parameter("address", "signer", 3)]
        public string Signer { get; set; } = string.Empty;

        [Parameter("address", "taker", 4)]
        public string Taker { get; set; } = string.Empty;

        [Parameter("uint256", "tokenId", 5)]
        public System.Numerics.BigInteger TokenId { get; set; }

        [Parameter("uint256", "makerAmount", 6)]
        public System.Numerics.BigInteger MakerAmount { get; set; }

        [Parameter("uint256", "takerAmount", 7)]
        public System.Numerics.BigInteger TakerAmount { get; set; }

        [Parameter("uint256", "expiration", 8)]
        public System.Numerics.BigInteger Expiration { get; set; }

        [Parameter("uint256", "nonce", 9)]
        public System.Numerics.BigInteger Nonce { get; set; }

        [Parameter("uint256", "feeRateBps", 10)]
        public System.Numerics.BigInteger FeeRateBps { get; set; }

        [Parameter("uint8", "side", 11)]
        public byte Side { get; set; }

        [Parameter("uint8", "signatureType", 12)]
        public byte SignatureType { get; set; }
    }

    public class PolymarketClobClient : IClobClient
    {
        private readonly HttpClient _httpClient;
        private readonly TerminalLogger _logger;
        private const string ClobHost = "https://clob.polymarket.com";
        private const int ChainId = 137;
        private static readonly object _fileLock = new object();

        public PolymarketClobClient(TerminalLogger logger, HttpClient httpClient)
        {
            _logger = logger;
            _httpClient = httpClient;
        }

        /// <summary>
        /// Creates an HMAC signature for authenticated requests to the Polymarket CLOB.
        /// </summary>
        private static string GenerateHmacSignature(string secret, string timestamp, string method, string requestPath, string body = "")
        {
            string payload = timestamp + method + requestPath + body;
            
            // Convert URL-Safe Base64 back to Standard Base64
            string b64 = secret.Replace('-', '+').Replace('_', '/');
            switch (b64.Length % 4)
            {
                case 2: b64 += "=="; break;
                case 3: b64 += "="; break;
            }
            
            byte[] secretBytes = Convert.FromBase64String(b64);
            byte[] payloadBytes = Encoding.UTF8.GetBytes(payload);

            using var hmac = new HMACSHA256(secretBytes);
            byte[] hash = hmac.ComputeHash(payloadBytes);
            
            string signature = Convert.ToBase64String(hash);
            return signature.Replace('+', '-').Replace('/', '_');
        }

        private static long _serverTimeDeltaSeconds = 0;
        private static DateTime _lastTimeSync = DateTime.MinValue;

        public async Task SyncServerTimeAsync()
        {
            if ((DateTime.UtcNow - _lastTimeSync).TotalMinutes < 15) return;
            try
            {
                using var response = await _httpClient.GetAsync($"{ClobHost}/time");
                if (response.IsSuccessStatusCode)
                {
                    string jsonStr = await response.Content.ReadAsStringAsync();
                    using var doc = JsonDocument.Parse(jsonStr);
                    long epochSecs = 0;

                    if (doc.RootElement.ValueKind == JsonValueKind.Number)
                    {
                        epochSecs = doc.RootElement.GetInt64();
                        if (epochSecs > 1000000000000) epochSecs /= 1000;
                        DateTime serverTime = DateTimeOffset.FromUnixTimeSeconds(epochSecs).UtcDateTime;
                        _serverTimeDeltaSeconds = (long)(serverTime - DateTime.UtcNow).TotalSeconds;
                        _lastTimeSync = DateTime.UtcNow;
                        _logger.Info($"🕒 CLOB Server Time Sync: Offset ist {_serverTimeDeltaSeconds} Sekunden.");
                    }
                    else if (doc.RootElement.ValueKind == JsonValueKind.Object && doc.RootElement.TryGetProperty("iso", out var isoProp) && DateTime.TryParse(isoProp.GetString(), null, System.Globalization.DateTimeStyles.RoundtripKind, out DateTime serverTime))
                    {
                        serverTime = serverTime.ToUniversalTime();
                        _serverTimeDeltaSeconds = (long)(serverTime - DateTime.UtcNow).TotalSeconds;
                        _lastTimeSync = DateTime.UtcNow;
                        _logger.Info($"🕒 CLOB Server Time Sync: Offset ist {_serverTimeDeltaSeconds} Sekunden.");
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Warning($"🕒 Time Sync Error: {ex.Message}");
            }
        }

        private string GetClobTimestamp()
        {
            // Background fire-and-forget sync if expired
            if ((DateTime.UtcNow - _lastTimeSync).TotalMinutes > 15)
            {
                _ = SyncServerTimeAsync();
            }
            return (DateTimeOffset.UtcNow.ToUnixTimeSeconds() + _serverTimeDeltaSeconds).ToString(System.Globalization.CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Derives a new Polymarket Level 2 API Key using an EIP712 Message signed by the L1 private key.
        /// </summary>
        public async Task<(string ApiKey, string ApiSecret, string ApiPassphrase)> DeriveApiKeyAsync(string privateKey, string walletAddress)
        {
            try
            {
                var signer = new Eip712TypedDataSigner();
                var key = new EthECKey(privateKey);
                string computedAddress = key.GetPublicAddress();
                
                string timestamp = GetClobTimestamp();
                
                var typedData = new TypedData<ClobDomain>
                {
                    Domain = new ClobDomain
                    {
                        Name = "ClobAuthDomain",
                        Version = "1",
                        ChainId = new System.Numerics.BigInteger(ChainId)
                    },
                    Types = Nethereum.ABI.EIP712.MemberDescriptionFactory.GetTypesMemberDescription(typeof(ClobDomain), typeof(ClobAuth)),
                    PrimaryType = "ClobAuth"
                };
                
                var clobAuth = new ClobAuth
                {
                    Address = computedAddress,
                    Timestamp = timestamp,
                    Nonce = new System.Numerics.BigInteger(0),
                    Message = "This message attests that I control the given wallet"
                };

                var encoder = new Nethereum.ABI.EIP712.Eip712TypedDataEncoder();
                var rawData = encoder.EncodeTypedData(clobAuth, typedData);
                _logger.Warning($"DEBUG_CS_RAW_DATA: {Nethereum.Hex.HexConvertors.Extensions.HexByteConvertorExtensions.ToHex(rawData)}");
                
                string signature = signer.SignTypedDataV4(clobAuth, typedData, key);
                _logger.Warning($"DEBUG_CS_SIG: {signature}");

                var request = new HttpRequestMessage(HttpMethod.Get, $"{ClobHost}/auth/derive-api-key");
                request.Headers.Add("POLY_ADDRESS", computedAddress);
                request.Headers.Add("POLY_SIGNATURE", signature);
                request.Headers.Add("POLY_TIMESTAMP", timestamp);
                request.Headers.Add("POLY_NONCE", "0");

                using (var response = await _httpClient.SendAsync(request))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        var jsonStr = await response.Content.ReadAsStringAsync();
                        using var doc = JsonDocument.Parse(jsonStr);
                        string apiKey = doc.RootElement.GetProperty("apiKey").GetString() ?? "";
                        string secret = doc.RootElement.GetProperty("secret").GetString() ?? "";
                        string passphrase = doc.RootElement.GetProperty("passphrase").GetString() ?? "";
                        
                        return (apiKey, secret, passphrase);
                    }
                }
                
                _logger.Warning($"Derivation failed. Attempting to CREATE new Api Key L2 instead...");
                using (var request2 = new HttpRequestMessage(HttpMethod.Post, $"{ClobHost}/auth/api-key"))
                {
                    request2.Headers.Add("POLY_ADDRESS", computedAddress);
                    request2.Headers.Add("POLY_SIGNATURE", signature);
                    request2.Headers.Add("POLY_TIMESTAMP", timestamp);
                    request2.Headers.Add("POLY_NONCE", "0");
                    using (var response2 = await _httpClient.SendAsync(request2))
                    {
                        if (response2.IsSuccessStatusCode)
                        {
                            var jsonStr = await response2.Content.ReadAsStringAsync();
                            using var doc = JsonDocument.Parse(jsonStr);
                            string apiKey = doc.RootElement.GetProperty("apiKey").GetString() ?? "";
                            string secret = doc.RootElement.GetProperty("secret").GetString() ?? "";
                            string passphrase = doc.RootElement.GetProperty("passphrase").GetString() ?? "";
                            
                            return (apiKey, secret, passphrase);
                        }
                        else
                        {
                            string err = await response2.Content.ReadAsStringAsync();
                            _logger.Error($"Failed to execute L1 Auth: {response2.StatusCode} {err}");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"DeriveApiKeyAsync Exception: {ex.Message}");
            }

            return (string.Empty, string.Empty, string.Empty);
        }

        public async Task<decimal> GetUsdcBalanceAsync(AccountState acc, bool isRetry = false)
        {
            if (string.IsNullOrEmpty(acc.ApiKey) || string.IsNullOrEmpty(acc.ApiSecret) || string.IsNullOrEmpty(acc.ApiPassphrase) || string.IsNullOrEmpty(acc.PrivateKey))
            {
                _logger.Warning($"🔑 [{acc.Name}] Skipping balance fetch: ApiKey={!string.IsNullOrEmpty(acc.ApiKey)}, Secret={!string.IsNullOrEmpty(acc.ApiSecret)}, Pass={!string.IsNullOrEmpty(acc.ApiPassphrase)}, PK={!string.IsNullOrEmpty(acc.PrivateKey)}");
                return 0;
            }

            try 
            {
                string endpoint = "/balance-allowance";
                string requestUrl = $"{endpoint}?asset_type=COLLATERAL&signature_type=2";
                string timestamp = GetClobTimestamp();
                
                // Python SDK signs ONLY the base path, not the query params
                string signature = GenerateHmacSignature(acc.ApiSecret, timestamp, "GET", endpoint);

                var request = new HttpRequestMessage(HttpMethod.Get, $"{ClobHost}{requestUrl}");
                var keyObj = new EthECKey(acc.PrivateKey.Replace("0x", ""));
                request.Headers.Add("POLY_ADDRESS", keyObj.GetPublicAddress());
                request.Headers.Add("POLY_API_KEY", acc.ApiKey);
                request.Headers.Add("POLY_SIGNATURE", signature);
                request.Headers.Add("POLY_TIMESTAMP", timestamp);
                request.Headers.Add("POLY_PASSPHRASE", acc.ApiPassphrase);

                using var response = await _httpClient.SendAsync(request);
                if (response.IsSuccessStatusCode)
                {
                    var jsonStr = await response.Content.ReadAsStringAsync();
                    _logger.Info($"💰 [{acc.Name}] Balance API Response: {jsonStr}");
                    using var doc = JsonDocument.Parse(jsonStr);
                    if (doc.RootElement.ValueKind == JsonValueKind.Object && doc.RootElement.TryGetProperty("balance", out var balProp))
                    {
                        var balanceStr = balProp.GetString();
                        if (decimal.TryParse(balanceStr, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out decimal balRaw))
                        {
                            decimal finalBal = balRaw / 1_000_000m;
                            _logger.Info($"💰 [{acc.Name}] Parsed Balance: {finalBal} USDC (raw: {balRaw})");
                            return finalBal;
                        }
                    }
                    _logger.Warning($"💰 [{acc.Name}] Could not parse 'balance' from response: {jsonStr}");
                }
                else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized || response.StatusCode == System.Net.HttpStatusCode.Forbidden)
                {
                    string errStr = await response.Content.ReadAsStringAsync();
                    _logger.Warning($"🌐 [{acc.Name}] API Keys expired/invalid. Deriving new L2 Keys from PrivateKey...");
                    
                    if (!isRetry && !string.IsNullOrEmpty(acc.PrivateKey) && !string.IsNullOrEmpty(acc.WalletAddress))
                    {
                        var fallbackKeyObj = new EthECKey(acc.PrivateKey.Replace("0x", ""));
                        var newKeys = await DeriveApiKeyAsync(acc.PrivateKey, fallbackKeyObj.GetPublicAddress());
                        if (!string.IsNullOrEmpty(newKeys.ApiKey))
                        {
                            acc.ApiKey = newKeys.ApiKey;
                            acc.ApiSecret = newKeys.ApiSecret;
                            acc.ApiPassphrase = newKeys.ApiPassphrase;
                            _logger.Info($"🌐 [{acc.Name}] Successfully derived new L2 Keys! Resuming in 2.5s...");
                            
                            // Await propagation of new keys inside Polymarket's Gamma backend
                            await Task.Delay(2500);

                            // Retry recursively strictly once 
                            return await GetUsdcBalanceAsync(acc, true);
                        }
                    }
                    _logger.Error($"CLOB Balance Fetch failed: {response.StatusCode} {errStr}");
                }
                else 
                {
                    string errStr = await response.Content.ReadAsStringAsync();
                    _logger.Error($"CLOB Balance Fetch failed: {response.StatusCode} {errStr}");
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"CLOB Balance Fetch Error: {ex.Message}");
            }
            return 0;
        }

        public async Task<System.Collections.Generic.List<(string Id, string Side, decimal Price)>> GetOpenOrdersAsync(AccountState acc, string assetId = "")
        {
            var result = new System.Collections.Generic.List<(string Id, string Side, decimal Price)>();
            if (string.IsNullOrEmpty(acc.ApiKey) || string.IsNullOrEmpty(acc.ApiSecret) || string.IsNullOrEmpty(acc.ApiPassphrase) || string.IsNullOrEmpty(acc.PrivateKey))
                return result;

            try
            {
                string endpoint = "/data/orders";
                // Ohne assetId: ALLE offenen Orders des Accounts (Startup-Reconciliation K2).
                // Die HMAC-Signatur geht über den Pfad "/data/orders" ohne Query-String, daher ist
                // das Weglassen des asset_id-Filters signaturneutral.
                string requestUrl = string.IsNullOrEmpty(assetId) ? endpoint : $"{endpoint}?asset_id={assetId}";
                string timestamp = GetClobTimestamp();

                string signature = GenerateHmacSignature(acc.ApiSecret, timestamp, "GET", endpoint);

                var request = new HttpRequestMessage(HttpMethod.Get, $"{ClobHost}{requestUrl}");
                var keyObj = new EthECKey(acc.PrivateKey.Replace("0x", ""));
                request.Headers.Add("POLY_ADDRESS", keyObj.GetPublicAddress());
                request.Headers.Add("POLY_API_KEY", acc.ApiKey);
                request.Headers.Add("POLY_SIGNATURE", signature);
                request.Headers.Add("POLY_TIMESTAMP", timestamp);
                request.Headers.Add("POLY_PASSPHRASE", acc.ApiPassphrase);

                using var response = await _httpClient.SendAsync(request);
                if (response.IsSuccessStatusCode)
                {
                    var jsonStr = await response.Content.ReadAsStringAsync();
                    using var doc = JsonDocument.Parse(jsonStr);
                    if (doc.RootElement.TryGetProperty("data", out var dataArr) && dataArr.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var orderLine in dataArr.EnumerateArray())
                        {
                            if (orderLine.TryGetProperty("orderID", out var oid) || orderLine.TryGetProperty("id", out oid))
                            {
                                string idStr = oid.GetString() ?? "";
                                string sideStr = orderLine.TryGetProperty("side", out var s) ? (s.GetString() ?? "") : "";
                                string priceStr = orderLine.TryGetProperty("price", out var p) ? (p.GetString() ?? "0") : "0";
                                decimal.TryParse(priceStr, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out decimal priceDec);

                                if (!string.IsNullOrEmpty(idStr))
                                    result.Add((idStr, sideStr, priceDec));
                            }
                        }
                    }
                    else if (doc.RootElement.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var orderLine in doc.RootElement.EnumerateArray())
                        {
                            if (orderLine.TryGetProperty("orderID", out var oid) || orderLine.TryGetProperty("id", out oid))
                            {
                                string idStr = oid.GetString() ?? "";
                                string sideStr = orderLine.TryGetProperty("side", out var s) ? (s.GetString() ?? "") : "";
                                string priceStr = orderLine.TryGetProperty("price", out var p) ? (p.GetString() ?? "0") : "0";
                                decimal.TryParse(priceStr, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out decimal priceDec);

                                if (!string.IsNullOrEmpty(idStr))
                                    result.Add((idStr, sideStr, priceDec));
                            }
                        }
                    }
                }
                else
                {
                    string errStr = await response.Content.ReadAsStringAsync();
                    _logger.Warning($"Failed to GET open orders for {assetId}: {response.StatusCode} {errStr}");
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"GetOpenOrdersAsync Error: {ex.Message}");
            }

            return result;
        }

        public async Task<bool> CancelOrderAsync(AccountState acc, string orderId)
        {
            if (string.IsNullOrEmpty(acc.ApiKey) || string.IsNullOrEmpty(acc.ApiSecret) || string.IsNullOrEmpty(acc.ApiPassphrase) || string.IsNullOrEmpty(acc.PrivateKey))
                return false;

            try
            {
                string endpoint = "/order";
                var reqBody = new { orderID = orderId };
                string jsonBody = JsonSerializer.Serialize(reqBody);
                string timestamp = GetClobTimestamp();

                string signature = GenerateHmacSignature(acc.ApiSecret, timestamp, "DELETE", endpoint, jsonBody);

                using var request = new HttpRequestMessage(HttpMethod.Delete, $"{ClobHost}{endpoint}");
                var keyObj = new EthECKey(acc.PrivateKey.Replace("0x", ""));
                request.Headers.Add("POLY_ADDRESS", keyObj.GetPublicAddress());
                request.Headers.Add("POLY_API_KEY", acc.ApiKey);
                request.Headers.Add("POLY_SIGNATURE", signature);
                request.Headers.Add("POLY_TIMESTAMP", timestamp);
                request.Headers.Add("POLY_PASSPHRASE", acc.ApiPassphrase);
                
                request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");

                using var response = await _httpClient.SendAsync(request);
                if (response.IsSuccessStatusCode)
                {
                    _logger.Info($"🚮 [{acc.Name}] Stornierung erfolgreich. OrderID: {orderId}");
                    return true;
                }
                else
                {
                    string errStr = await response.Content.ReadAsStringAsync();
                    _logger.Warning($"Failed to cancel order {orderId}: {response.StatusCode} {errStr}");
                    return false;
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"CancelOrderAsync Error: {ex.Message}");
                return false;
            }
        }

        public async Task CancelConflictingOrdersAsync(AccountState acc, string assetId, decimal newPrice, string sideStr)
        {
            var openOrders = await GetOpenOrdersAsync(acc, assetId);
            
            if (openOrders.Count > 0)
            {
                var tasks = new System.Collections.Generic.List<Task>();
                
                foreach (var order in openOrders)
                {
                    bool shouldCancel = false;

                    if (sideStr.Equals("SELL", StringComparison.OrdinalIgnoreCase))
                    {
                        shouldCancel = true;
                        _logger.Info($"⚠️ [{acc.Name}] Storniere Order {order.Id} wegen Verkaufs-Signal des Master-Traders.");
                    }
                    else if (sideStr.Equals("BUY", StringComparison.OrdinalIgnoreCase) && order.Side.Equals("BUY", StringComparison.OrdinalIgnoreCase))
                    {
                        if (Math.Abs(order.Price - newPrice) > 0.001m)
                        {
                            shouldCancel = true;
                            _logger.Info($"⚠️ [{acc.Name}] Storniere veraltete Order {order.Id} (Alter Preis: {order.Price:F3}, Neuer Preis: {newPrice:F3})");
                        }
                        else
                        {
                            _logger.Info($"✅ [{acc.Name}] Behalte bestehende Order {order.Id} (Preis identisch: {order.Price:F3})");
                        }
                    }

                    if (shouldCancel)
                    {
                        tasks.Add(CancelOrderAsync(acc, order.Id));
                    }
                }

                if (tasks.Count > 0)
                {
                    await Task.WhenAll(tasks);
                    // Minimal delay to ensure rapid executions don't conflict with in-flight deletions
                    await Task.Delay(150); 
                }
            }
        }

        private static System.Numerics.BigInteger GenerateSalt()
        {
            // Generate a salt similar to Py Clob Client (fits safely in a standard 64-bit int / JS Number)
            long t = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
            int r = System.Security.Cryptography.RandomNumberGenerator.GetInt32(0, 10000);
            return new System.Numerics.BigInteger(t * 10000 + r);
        }

        public static (decimal shares, decimal usdc, decimal makerRaw, decimal takerRaw) CalculateExactOrderAmounts(decimal investAmountUsd, decimal rawPrice, decimal limitPrice, string sideStr, string orderType = "FOK", decimal? overrideTickSize = null, int? overrideMakerDecimals = null, int? overrideTakerDecimals = null)
        {
            decimal tickSize = overrideTickSize ?? 0.001m;
            int priceDec, sizeDec, amtDec;
            if (tickSize >= 0.1m) { priceDec = 1; sizeDec = 2; amtDec = 3; }
            else if (tickSize >= 0.01m) { priceDec = 2; sizeDec = 2; amtDec = 4; }
            else if (tickSize >= 0.001m) { priceDec = 3; sizeDec = 2; amtDec = 5; }
            else { priceDec = 4; sizeDec = 2; amtDec = 6; }

            decimal priceRounded = Math.Round(limitPrice > 0 ? limitPrice : rawPrice, priceDec, MidpointRounding.AwayFromZero);
            if (priceRounded < tickSize) priceRounded = tickSize;
            
            decimal executedShares = 0m;
            decimal executedUsdc = 0m;
            decimal finalMakerAmountRaw = 0m;
            decimal finalTakerAmountRaw = 0m;

            if (sideStr.ToUpper() == "BUY")
            {
                decimal rawTakerShares = investAmountUsd / priceRounded;

                decimal multiplier = (decimal)Math.Pow(10, sizeDec);
                decimal takerShares = Math.Floor(rawTakerShares * multiplier) / multiplier;
                
                if (takerShares <= 0) return (-1, -1, 0, 0);

                decimal makerUsd = 0m;
                // Polymarket strictly enforces $1.00 minimum for MARKET BUYS and verifies it against the supported shares.
                // We increment takerShares until the floored USDC amount supports the exact shares without dropping below $1.00.
                decimal step = 1.0m / multiplier;
                while (takerShares > 0)
                {
                    makerUsd = takerShares * priceRounded;
                    int actDec = BitConverter.GetBytes(decimal.GetBits(makerUsd)[3])[2];
                    if (actDec > amtDec)
                    {
                        decimal mul2 = (decimal)Math.Pow(10, amtDec + 4);
                        makerUsd = Math.Ceiling(makerUsd * mul2) / mul2;
                        if (BitConverter.GetBytes(decimal.GetBits(makerUsd)[3])[2] > amtDec)
                        {
                            decimal mul3 = (decimal)Math.Pow(10, amtDec);
                            makerUsd = Math.Floor(makerUsd * mul3) / mul3;
                        }
                    }

                    decimal supportedShares = Math.Floor((makerUsd / priceRounded) * multiplier) / multiplier;
                    if (makerUsd >= 1.0m && supportedShares >= takerShares) 
                        break;
                    
                    takerShares += step;
                }

                finalTakerAmountRaw = Math.Round(takerShares * 1_000_000m);
                finalMakerAmountRaw = Math.Round(makerUsd * 1_000_000m);
                executedShares = takerShares;
                executedUsdc = makerUsd;
            }
            else
            {
                decimal sharesRaw = investAmountUsd / priceRounded;
                
                decimal multiplier = (decimal)Math.Pow(10, sizeDec);
                decimal makerShares = Math.Floor(sharesRaw * multiplier) / multiplier;
                
                decimal takerUsd = makerShares * priceRounded;
                int actDec = BitConverter.GetBytes(decimal.GetBits(takerUsd)[3])[2];
                if (actDec > amtDec)
                {
                    decimal mul2 = (decimal)Math.Pow(10, amtDec + 4);
                    takerUsd = Math.Ceiling(takerUsd * mul2) / mul2;
                    if (BitConverter.GetBytes(decimal.GetBits(takerUsd)[3])[2] > amtDec)
                    {
                        decimal mul3 = (decimal)Math.Pow(10, amtDec);
                        takerUsd = Math.Floor(takerUsd * mul3) / mul3;
                    }
                }

                finalMakerAmountRaw = Math.Round(makerShares * 1_000_000m);
                finalTakerAmountRaw = Math.Round(takerUsd * 1_000_000m);
                executedShares = makerShares;
                executedUsdc = takerUsd;
            }

            return (executedShares, executedUsdc, finalMakerAmountRaw, finalTakerAmountRaw);
        }

        /// <summary>
        /// Executes a native EIP-712 signed order (default Fill-Or-Kill)
        /// </summary>
        public async Task<string> PlaceOrderAsync(AccountState account, string tokenId, string sideStr, decimal investAmountUsd, decimal limitPrice, string orderType = "FOK", bool debugPayloadLog = false, bool isNegRisk = false, int actualFeeBps = 0, decimal? overrideTickSize = null, int? overrideMakerDecimals = null, int? overrideTakerDecimals = null)
        {
            if (string.IsNullOrEmpty(account.PrivateKey) || string.IsNullOrEmpty(account.ApiKey)) 
                return "Error: Missing API or Private Keys";

            try
            {
                var signer = new Eip712TypedDataSigner();
                var key = new EthECKey(account.PrivateKey);

                var typedData = new TypedData<CtfDomain>
                {
                    Domain = new CtfDomain
                    {
                        Name = "Polymarket CTF Exchange",
                        Version = "1",
                        ChainId = ChainId,
                        VerifyingContract = isNegRisk ? "0xC5d563A36AE78145C45a50134d48A1215220f80a" : "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E" 
                    },
                    Types = Nethereum.ABI.EIP712.MemberDescriptionFactory.GetTypesMemberDescription(typeof(CtfDomain), typeof(CtfOrder)),
                    PrimaryType = "Order"
                };

                var amounts = CalculateExactOrderAmounts(investAmountUsd, limitPrice, limitPrice, sideStr, orderType, overrideTickSize, overrideMakerDecimals, overrideTakerDecimals);
                
                if (amounts.shares <= 0) 
                    return $"Mathematical tick size error: Balance too small to meet fractional quantum limit for exact price matching";
                    
                decimal makerAmountRaw = amounts.makerRaw;
                decimal takerAmountRaw = amounts.takerRaw;

                System.Numerics.BigInteger parsedTokenId;
                if (tokenId.StartsWith("0x") || tokenId.Any(c => "abcdefABCDEF".Contains(c)))
                {
                    parsedTokenId = new Nethereum.Hex.HexTypes.HexBigInteger(tokenId.StartsWith("0x") ? tokenId : "0x" + tokenId).Value;
                }
                else
                {
                    parsedTokenId = System.Numerics.BigInteger.Parse(tokenId);
                }

                var ctfOrder = new CtfOrder
                {
                    Salt = GenerateSalt(),
                    Maker = account.WalletAddress,
                    Signer = key.GetPublicAddress(),
                    Taker = "0x0000000000000000000000000000000000000000",
                    TokenId = parsedTokenId,
                    MakerAmount = new System.Numerics.BigInteger(makerAmountRaw),
                    TakerAmount = new System.Numerics.BigInteger(takerAmountRaw),
                    Expiration = orderType == "GTD" ? long.Parse(GetClobTimestamp()) + 300 : 0,
                    Nonce = 0,
                    FeeRateBps = new System.Numerics.BigInteger(actualFeeBps),
                    Side = sideStr.ToUpper() == "BUY" ? (byte)0 : (byte)1,
                    SignatureType = 2
                };

                string signature = signer.SignTypedDataV4(ctfOrder, typedData, key);

                var reqBody = new
                {
                    order = new
                    {
                        salt = (long)ctfOrder.Salt,
                        maker = ctfOrder.Maker.ToLower(),
                        signer = ctfOrder.Signer.ToLower(),
                        taker = ctfOrder.Taker.ToLower(),
                        tokenId = ctfOrder.TokenId.ToString(),
                        makerAmount = ctfOrder.MakerAmount.ToString(),
                        takerAmount = ctfOrder.TakerAmount.ToString(),
                        expiration = ctfOrder.Expiration.ToString(),
                        nonce = ctfOrder.Nonce.ToString(),
                        feeRateBps = ctfOrder.FeeRateBps.ToString(),
                        side = ctfOrder.Side == 0 ? "BUY" : "SELL",
                        signatureType = ctfOrder.SignatureType,
                        signature = signature
                    },
                    owner = account.ApiKey, 
                    orderType = orderType
                };

                string jsonBody = JsonSerializer.Serialize(reqBody);
                string timestamp = GetClobTimestamp();
                string requestPath = "/order";
                
                string hmacSig = GenerateHmacSignature(account.ApiSecret, timestamp, "POST", requestPath, jsonBody);

                using var request = new HttpRequestMessage(HttpMethod.Post, $"{ClobHost}{requestPath}");
                var keyObj = new EthECKey(account.PrivateKey.Replace("0x", ""));
                request.Headers.Add("POLY_ADDRESS", keyObj.GetPublicAddress());
                request.Headers.Add("POLY_API_KEY", account.ApiKey);
                request.Headers.Add("POLY_TIMESTAMP", timestamp);
                request.Headers.Add("POLY_SIGNATURE", hmacSig);
                request.Headers.Add("POLY_PASSPHRASE", account.ApiPassphrase);
                request.Content = new StringContent(jsonBody, Encoding.UTF8, "application/json");

                if (debugPayloadLog)
                {
                    _logger.Debug($"[CLOB-PAYLOAD] -> {jsonBody}");
                }

                using var response = await _httpClient.SendAsync(request);
                var responseContent = await response.Content.ReadAsStringAsync();

                if (!response.IsSuccessStatusCode)
                {
                    bool isFokFail = responseContent.Contains("FOK orders are fully filled or killed");

                    if (isFokFail && sideStr == "BUY")
                    {
                        // Dampen FOK failed BUY logs. Usually means target price/liquidity not met for full copy size.
                        // We skip it silently.
                        return "SKIPPED_LIQUIDITY";
                    }

                    lock (_fileLock)
                    {
                        System.IO.File.WriteAllText("last_invalid_payload.json", jsonBody);
                    }

                    if (isFokFail && sideStr == "SELL")
                    {
                        _logger.Warning($"Liquidität für FOK SELL reicht nicht aus. (Orderbook Size limit). Rest-Shares bleiben erhalten.");
                        return "Nicht genügend Liquidität für vollumfänglichen Verkauf auf diesem Preisniveau (FOK).";
                    }
                    else
                    {
                        var tickMatch = System.Text.RegularExpressions.Regex.Match(responseContent, @"breaks minimum tick size rule: ([\d\.]+)");
                        if (tickMatch.Success && overrideTickSize == null)
                        {
                            if (decimal.TryParse(tickMatch.Groups[1].Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out decimal newTickSize))
                            {
                                _logger.Info($"🔄 Automatische Anpassung an Markt Tick-Size ({newTickSize}). Order wird erneut berechnet und platziert...");
                                return await PlaceOrderAsync(account, tokenId, sideStr, investAmountUsd, limitPrice, orderType, debugPayloadLog, isNegRisk, actualFeeBps, newTickSize, overrideMakerDecimals, overrideTakerDecimals);
                            }
                        }

                        var decMatch = System.Text.RegularExpressions.Regex.Match(responseContent, @"maker amount supports a max accuracy of (\d+) decimals, taker amount a max of (\d+) decimals");
                        if (decMatch.Success && overrideMakerDecimals == null)
                        {
                            if (int.TryParse(decMatch.Groups[1].Value, System.Globalization.NumberStyles.Integer,
                                    System.Globalization.CultureInfo.InvariantCulture, out int newMaker)
                                && int.TryParse(decMatch.Groups[2].Value, System.Globalization.NumberStyles.Integer,
                                    System.Globalization.CultureInfo.InvariantCulture, out int newTaker))
                            {
                                _logger.Info($"🔄 Automatische Anpassung an Dezimalregeln (Maker: {newMaker}, Taker: {newTaker}). Order wird neu berechnet...");
                                return await PlaceOrderAsync(account, tokenId, sideStr, investAmountUsd, limitPrice, orderType, debugPayloadLog, isNegRisk, actualFeeBps, overrideTickSize, newMaker, newTaker);
                            }
                        }

                        // Check if error is "invalid fee rate" -> Extract required fee -> Retry!
                        var match = System.Text.RegularExpressions.Regex.Match(responseContent, @"invalid fee rate \(\d+\), current market's (?:taker|maker) fee: (\d+)");
                        if (match.Success && actualFeeBps == 0) // Only retry once
                        {
                            if (int.TryParse(match.Groups[1].Value, System.Globalization.NumberStyles.Integer,
                                    System.Globalization.CultureInfo.InvariantCulture, out int newFeeBps))
                            {
                                _logger.Info($"🔄 Automatische Anpassung an Fee Rate ({newFeeBps} bps). Order wird erneut platziert...");
                                return await PlaceOrderAsync(account, tokenId, sideStr, investAmountUsd, limitPrice, orderType, debugPayloadLog, isNegRisk, newFeeBps, overrideTickSize);
                            }
                        }

                        // Check if error is "Size lower than minimum 5" -> Fallback to MARKET
                        var sizeMatch = System.Text.RegularExpressions.Regex.Match(responseContent, @"Size \([\d\.]+\) lower than the minimum: (\d+)");
                        if (sizeMatch.Success)
                        {
                            if (decimal.TryParse(sizeMatch.Groups[1].Value, System.Globalization.NumberStyles.Any,
                                    System.Globalization.CultureInfo.InvariantCulture, out decimal minReq))
                            {
                                if (orderType != "MARKET")
                                {
                                    _logger.Info($"🔄 Automatische Anpassung an Minimum Size Limit (Limitorder < {minReq}). Order wird als MARKET platziert...");
                                    return await PlaceOrderAsync(account, tokenId, sideStr, investAmountUsd, limitPrice, "MARKET", debugPayloadLog, isNegRisk, actualFeeBps, overrideTickSize, overrideMakerDecimals, overrideTakerDecimals);
                                }
                                else if (sideStr == "SELL")
                                {
                                    _logger.Warning($"Verkauf von unter {minReq} Shares auf Polymarket nicht möglich (Orderbook Limit). Position muss aufgestockt werden oder auslaufen.");
                                    return $"Börsenlimit: Mindestens {minReq} Shares erforderlich.";
                                }
                            }
                        }

                        var balMatch1 = System.Text.RegularExpressions.Regex.Match(responseContent, @"balance: (\d+), sum of active orders: (\d+)");
                        var balMatch2 = System.Text.RegularExpressions.Regex.Match(responseContent, @"balance: (\d+), order amount: (\d+)");

                        if ((balMatch1.Success || balMatch2.Success) && sideStr == "SELL")
                        {
                            decimal totalBal = 0m, activeOrders = 0m;
                            if (balMatch1.Success)
                            {
                                _ = decimal.TryParse(balMatch1.Groups[1].Value, System.Globalization.NumberStyles.Any,
                                    System.Globalization.CultureInfo.InvariantCulture, out totalBal);
                                _ = decimal.TryParse(balMatch1.Groups[2].Value, System.Globalization.NumberStyles.Any,
                                    System.Globalization.CultureInfo.InvariantCulture, out activeOrders);
                            }
                            else if (balMatch2.Success)
                            {
                                _ = decimal.TryParse(balMatch2.Groups[1].Value, System.Globalization.NumberStyles.Any,
                                    System.Globalization.CultureInfo.InvariantCulture, out totalBal);
                                activeOrders = 0m; 
                            }

                            decimal availableSharesRaw = totalBal - activeOrders;
                            decimal availableShares = availableSharesRaw / 1_000_000m;
                            decimal requiredShares = investAmountUsd / limitPrice;
                            
                            if (availableShares > 0 && Math.Abs(availableShares - requiredShares) > 0.001m && availableShares < requiredShares)
                            {
                                decimal newInvestAmount = availableShares * limitPrice;
                                _logger.Info($"🔄 Automatische Anpassung an verfügbare Shares (Reale Balance: {totalBal / 1000000m} / Aktive Orders: {activeOrders / 1000000m} Shares). Verkaufe exakte {availableShares} Shares...");
                                return await PlaceOrderAsync(account, tokenId, sideStr, newInvestAmount, limitPrice, orderType, debugPayloadLog, isNegRisk, actualFeeBps, overrideTickSize, overrideMakerDecimals, overrideTakerDecimals);
                            }
                        }

                        _logger.Error($"CLOB Order Error ({response.StatusCode}): {responseContent}");
                    }
                    return "ERROR";
                }
                
                if (response.IsSuccessStatusCode)
                {
                    _logger.Info($"\u2705 Order Platzierung Erfolgreich! {sideStr} @ {limitPrice:F3}");
                    
                    if (orderType == "GTC" || orderType == "GTD")
                    {
                        account.HasOpenLimitOrders = true;
                    }
                    
                    return "OK";
                }
                else
                {
                    _logger.Error($"❌ Order Fehler: {response.StatusCode} - {responseContent}");
                    return responseContent;
                }
            }
            catch (Exception ex)
            {
                _logger.Error($"PlaceFokOrderAsync Runtime Fehler: {ex.Message}");
                return ex.Message;
            }
        }
    }
}
