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;

namespace Synor.Privacy
{
    /// <summary>
    /// Synor Privacy SDK client for C#/.NET.
    /// Confidential transactions, ring signatures, stealth addresses, and cryptographic commitments.
    /// </summary>
    public sealed class PrivacyClient : IDisposable
    {
        public const string Version = "0.1.0";

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

        public PrivacyClient(PrivacyConfig 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 Confidential Transactions

        public async Task<ConfidentialTransaction> CreateConfidentialTxAsync(
            IEnumerable<ConfidentialTxInput> inputs,
            IEnumerable<ConfidentialTxOutput> outputs,
            CancellationToken ct = default)
        {
            var body = new { inputs, outputs };
            return await PostAsync<ConfidentialTransaction>("/privacy/confidential/create", body, ct);
        }

        public async Task<bool> VerifyConfidentialTxAsync(ConfidentialTransaction tx, CancellationToken ct = default)
        {
            var body = new { transaction = tx };
            var response = await PostAsync<Dictionary<string, object>>("/privacy/confidential/verify", body, ct);
            return response.TryGetValue("valid", out var valid) && valid is JsonElement elem && elem.GetBoolean();
        }

        public async Task<Commitment> CreateCommitmentAsync(
            string value,
            string blindingFactor,
            CancellationToken ct = default)
        {
            var body = new { value, blinding_factor = blindingFactor };
            return await PostAsync<Commitment>("/privacy/commitment/create", body, ct);
        }

        public async Task<bool> VerifyCommitmentAsync(
            string commitment,
            string value,
            string blindingFactor,
            CancellationToken ct = default)
        {
            var body = new { commitment, value, blinding_factor = blindingFactor };
            var response = await PostAsync<Dictionary<string, object>>("/privacy/commitment/verify", body, ct);
            return response.TryGetValue("valid", out var valid) && valid is JsonElement elem && elem.GetBoolean();
        }

        public async Task<RangeProof> CreateRangeProofAsync(
            string value,
            string blindingFactor,
            long minValue,
            long maxValue,
            CancellationToken ct = default)
        {
            var body = new { value, blinding_factor = blindingFactor, min_value = minValue, max_value = maxValue };
            return await PostAsync<RangeProof>("/privacy/range-proof/create", body, ct);
        }

        public async Task<bool> VerifyRangeProofAsync(RangeProof proof, CancellationToken ct = default)
        {
            var body = new { proof };
            var response = await PostAsync<Dictionary<string, object>>("/privacy/range-proof/verify", body, ct);
            return response.TryGetValue("valid", out var valid) && valid is JsonElement elem && elem.GetBoolean();
        }

        #endregion

        #region Ring Signatures

        public async Task<RingSignature> CreateRingSignatureAsync(
            string message,
            IEnumerable<string> ring,
            int signerIndex,
            string privateKey,
            CancellationToken ct = default)
        {
            var body = new { message, ring, signer_index = signerIndex, private_key = privateKey };
            return await PostAsync<RingSignature>("/privacy/ring/sign", body, ct);
        }

        public async Task<bool> VerifyRingSignatureAsync(RingSignature signature, string message, CancellationToken ct = default)
        {
            var body = new { signature, message };
            var response = await PostAsync<Dictionary<string, object>>("/privacy/ring/verify", body, ct);
            return response.TryGetValue("valid", out var valid) && valid is JsonElement elem && elem.GetBoolean();
        }

        public async Task<string[]> GenerateDecoysAsync(int count, string? excludeKey = null, CancellationToken ct = default)
        {
            var path = $"/privacy/ring/decoys?count={count}";
            if (!string.IsNullOrEmpty(excludeKey))
                path += $"&exclude={Uri.EscapeDataString(excludeKey)}";
            return await GetAsync<string[]>(path, ct);
        }

        public async Task<bool> CheckKeyImageAsync(string keyImage, CancellationToken ct = default)
        {
            var response = await GetAsync<Dictionary<string, object>>($"/privacy/ring/key-image/{keyImage}", ct);
            return response.TryGetValue("spent", out var spent) && spent is JsonElement elem && elem.GetBoolean();
        }

        #endregion

        #region Stealth Addresses

        public async Task<StealthKeyPair> GenerateStealthKeyPairAsync(CancellationToken ct = default)
        {
            return await PostAsync<StealthKeyPair>("/privacy/stealth/generate", new { }, ct);
        }

        public async Task<StealthAddress> DeriveStealthAddressAsync(
            string spendPublicKey,
            string viewPublicKey,
            CancellationToken ct = default)
        {
            var body = new { spend_public_key = spendPublicKey, view_public_key = viewPublicKey };
            return await PostAsync<StealthAddress>("/privacy/stealth/derive", body, ct);
        }

        public async Task<string> RecoverStealthPrivateKeyAsync(
            string stealthAddress,
            string viewPrivateKey,
            string spendPrivateKey,
            CancellationToken ct = default)
        {
            var body = new
            {
                stealth_address = stealthAddress,
                view_private_key = viewPrivateKey,
                spend_private_key = spendPrivateKey
            };
            var response = await PostAsync<Dictionary<string, object>>("/privacy/stealth/recover", body, ct);
            return response["private_key"]?.ToString() ?? throw new PrivacyException("Missing private_key");
        }

        public async Task<StealthOutput[]> ScanOutputsAsync(
            string viewPrivateKey,
            string spendPublicKey,
            long fromBlock,
            long? toBlock = null,
            CancellationToken ct = default)
        {
            var body = new Dictionary<string, object>
            {
                ["view_private_key"] = viewPrivateKey,
                ["spend_public_key"] = spendPublicKey,
                ["from_block"] = fromBlock
            };
            if (toBlock.HasValue) body["to_block"] = toBlock.Value;
            return await PostAsync<StealthOutput[]>("/privacy/stealth/scan", body, ct);
        }

        #endregion

        #region Blinding

        public async Task<string> GenerateBlindingFactorAsync(CancellationToken ct = default)
        {
            var response = await PostAsync<Dictionary<string, object>>("/privacy/blinding/generate", new { }, ct);
            return response["blinding_factor"]?.ToString() ?? throw new PrivacyException("Missing blinding_factor");
        }

        public async Task<string> BlindValueAsync(string value, string blindingFactor, CancellationToken ct = default)
        {
            var body = new { value, blinding_factor = blindingFactor };
            var response = await PostAsync<Dictionary<string, object>>("/privacy/blinding/blind", body, ct);
            return response["blinded_value"]?.ToString() ?? throw new PrivacyException("Missing blinded_value");
        }

        public async Task<string> UnblindValueAsync(string blindedValue, string blindingFactor, CancellationToken ct = default)
        {
            var body = new { blinded_value = blindedValue, blinding_factor = blindingFactor };
            var response = await PostAsync<Dictionary<string, object>>("/privacy/blinding/unblind", body, ct);
            return response["value"]?.ToString() ?? throw new PrivacyException("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 PrivacyException("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 PrivacyException(message, (int)response.StatusCode, code);
            }
            catch (JsonException)
            {
                throw new PrivacyException(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 PrivacyException("Request failed");
        }

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

        #endregion
    }

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

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

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