using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace Synor.Contract
{
    /// <summary>
    /// Synor Contract SDK client for C#/.NET.
    /// Smart contract deployment, interaction, and event handling.
    /// </summary>
    public sealed class ContractClient : IDisposable
    {
        public const string Version = "0.1.0";

        private readonly ContractConfig _config;
        private readonly HttpClient _httpClient;
        private readonly JsonSerializerOptions _jsonOptions;
        private bool _closed;

        public ContractClient(ContractConfig config)
        {
            _config = config ?? throw new ArgumentNullException(nameof(config));
            _httpClient = new HttpClient
            {
                BaseAddress = new Uri(config.Endpoint),
                Timeout = TimeSpan.FromMilliseconds(config.TimeoutMs)
            };
            _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
            _httpClient.DefaultRequestHeaders.Add("X-SDK-Version", $"csharp/{Version}");

            _jsonOptions = new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
                DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
            };
        }

        #region Contract Deployment

        public async Task<DeploymentResult> DeployAsync(DeployContractOptions options, CancellationToken ct = default)
        {
            return await PostAsync<DeploymentResult>("/contract/deploy", options, ct);
        }

        public async Task<DeploymentResult> DeployCreate2Async(DeployContractOptions options, string salt, CancellationToken ct = default)
        {
            var body = new Dictionary<string, object?>
            {
                ["bytecode"] = options.Bytecode,
                ["salt"] = salt,
                ["abi"] = options.Abi,
                ["constructor_args"] = options.ConstructorArgs,
                ["value"] = options.Value,
                ["gas_limit"] = options.GasLimit,
                ["gas_price"] = options.GasPrice
            };
            return await PostAsync<DeploymentResult>("/contract/deploy/create2", body, ct);
        }

        public async Task<string> PredictAddressAsync(string bytecode, string salt, string? deployer = null, CancellationToken ct = default)
        {
            var body = new { bytecode, salt, deployer };
            var response = await PostAsync<Dictionary<string, object>>("/contract/predict-address", body, ct);
            return response["address"]?.ToString() ?? throw new ContractException("Missing address");
        }

        #endregion

        #region Contract Interaction

        public async Task<JsonElement> CallAsync(CallContractOptions options, CancellationToken ct = default)
        {
            return await PostAsync<JsonElement>("/contract/call", options, ct);
        }

        public async Task<TransactionResult> SendAsync(SendContractOptions options, CancellationToken ct = default)
        {
            return await PostAsync<TransactionResult>("/contract/send", options, ct);
        }

        #endregion

        #region Events

        public async Task<DecodedEvent[]> GetEventsAsync(EventFilter filter, CancellationToken ct = default)
        {
            return await PostAsync<DecodedEvent[]>("/contract/events", filter, ct);
        }

        public async Task<EventLog[]> GetLogsAsync(string contract, long? fromBlock = null, long? toBlock = null, CancellationToken ct = default)
        {
            var path = $"/contract/logs?contract={HttpUtility.UrlEncode(contract)}";
            if (fromBlock.HasValue) path += $"&from_block={fromBlock}";
            if (toBlock.HasValue) path += $"&to_block={toBlock}";
            return await GetAsync<EventLog[]>(path, ct);
        }

        public async Task<DecodedEvent[]> DecodeLogsAsync(IEnumerable<EventLog> logs, IEnumerable<AbiEntry> abi, CancellationToken ct = default)
        {
            var body = new { logs, abi };
            return await PostAsync<DecodedEvent[]>("/contract/decode-logs", body, ct);
        }

        #endregion

        #region ABI Utilities

        public async Task<string> EncodeCallAsync(EncodeCallOptions options, CancellationToken ct = default)
        {
            var response = await PostAsync<Dictionary<string, object>>("/contract/encode", options, ct);
            return response["data"]?.ToString() ?? throw new ContractException("Missing data");
        }

        public async Task<JsonElement> DecodeResultAsync(DecodeResultOptions options, CancellationToken ct = default)
        {
            var response = await PostAsync<Dictionary<string, JsonElement>>("/contract/decode", options, ct);
            return response["result"];
        }

        public async Task<string> GetSelectorAsync(string signature, CancellationToken ct = default)
        {
            var response = await GetAsync<Dictionary<string, object>>($"/contract/selector?signature={HttpUtility.UrlEncode(signature)}", ct);
            return response["selector"]?.ToString() ?? throw new ContractException("Missing selector");
        }

        #endregion

        #region Gas Estimation

        public async Task<GasEstimation> EstimateGasAsync(EstimateGasOptions options, CancellationToken ct = default)
        {
            return await PostAsync<GasEstimation>("/contract/estimate-gas", options, ct);
        }

        #endregion

        #region Contract Information

        public async Task<BytecodeInfo> GetBytecodeAsync(string address, CancellationToken ct = default)
        {
            return await GetAsync<BytecodeInfo>($"/contract/{HttpUtility.UrlEncode(address)}/bytecode", ct);
        }

        public async Task<VerificationResult> VerifyAsync(VerifyContractOptions options, CancellationToken ct = default)
        {
            return await PostAsync<VerificationResult>("/contract/verify", options, ct);
        }

        public async Task<VerificationResult> GetVerificationStatusAsync(string address, CancellationToken ct = default)
        {
            return await GetAsync<VerificationResult>($"/contract/{HttpUtility.UrlEncode(address)}/verification", ct);
        }

        #endregion

        #region Multicall

        public async Task<MulticallResult[]> MulticallAsync(IEnumerable<MulticallRequest> requests, CancellationToken ct = default)
        {
            var body = new { calls = requests };
            return await PostAsync<MulticallResult[]>("/contract/multicall", body, ct);
        }

        #endregion

        #region Storage

        public async Task<string> ReadStorageAsync(ReadStorageOptions options, CancellationToken ct = default)
        {
            var path = $"/contract/storage?contract={HttpUtility.UrlEncode(options.Contract)}&slot={HttpUtility.UrlEncode(options.Slot)}";
            if (options.BlockNumber.HasValue) path += $"&block={options.BlockNumber}";
            var response = await GetAsync<Dictionary<string, object>>(path, ct);
            return response["value"]?.ToString() ?? throw new ContractException("Missing value");
        }

        #endregion

        #region Lifecycle

        public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
        {
            if (_closed) return false;
            try
            {
                var response = await GetAsync<Dictionary<string, object>>("/health", ct);
                return response.TryGetValue("status", out var status) &&
                       status is JsonElement elem && elem.GetString() == "healthy";
            }
            catch
            {
                return false;
            }
        }

        public void Dispose()
        {
            _closed = true;
            _httpClient.Dispose();
        }

        public bool IsClosed => _closed;

        #endregion

        #region HTTP Helpers

        private async Task<T> GetAsync<T>(string path, CancellationToken ct)
        {
            ThrowIfClosed();
            return await ExecuteWithRetryAsync(async () =>
            {
                var response = await _httpClient.GetAsync(path, ct);
                return await HandleResponseAsync<T>(response, ct);
            });
        }

        private async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
        {
            ThrowIfClosed();
            return await ExecuteWithRetryAsync(async () =>
            {
                var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
                return await HandleResponseAsync<T>(response, ct);
            });
        }

        private async Task<T> HandleResponseAsync<T>(HttpResponseMessage response, CancellationToken ct)
        {
            var content = await response.Content.ReadAsStringAsync(ct);

            if (response.IsSuccessStatusCode)
            {
                return JsonSerializer.Deserialize<T>(content, _jsonOptions)
                    ?? throw new ContractException("Failed to deserialize response");
            }

            try
            {
                var error = JsonSerializer.Deserialize<Dictionary<string, object>>(content, _jsonOptions);
                var message = error?.GetValueOrDefault("message")?.ToString()
                    ?? error?.GetValueOrDefault("error")?.ToString()
                    ?? "Unknown error";
                var code = error?.GetValueOrDefault("code")?.ToString();
                throw new ContractException(message, (int)response.StatusCode, code);
            }
            catch (JsonException)
            {
                throw new ContractException(content, (int)response.StatusCode);
            }
        }

        private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> action)
        {
            Exception? lastException = null;
            for (var attempt = 0; attempt < _config.Retries; attempt++)
            {
                try
                {
                    return await action();
                }
                catch (Exception ex)
                {
                    lastException = ex;
                    if (attempt < _config.Retries - 1)
                    {
                        await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
                    }
                }
            }
            throw lastException ?? new ContractException("Request failed");
        }

        private void ThrowIfClosed()
        {
            if (_closed) throw new ContractException("Client has been closed");
        }

        #endregion
    }

    public class ContractConfig
    {
        public required string ApiKey { get; init; }
        public string Endpoint { get; init; } = "https://contract.synor.io";
        public int TimeoutMs { get; init; } = 30000;
        public int Retries { get; init; } = 3;
    }

    public class ContractException : Exception
    {
        public int? StatusCode { get; }
        public string? Code { get; }

        public ContractException(string message, int? statusCode = null, string? code = null)
            : base(message)
        {
            StatusCode = statusCode;
            Code = code;
        }
    }
}
