using System.Net.Http.Json;
using System.Text.Json;

namespace Synor.Sdk.Wallet;

/// <summary>
/// Synor Wallet SDK client for C#.
///
/// Provides key management, transaction signing, and balance queries
/// for the Synor blockchain.
/// </summary>
/// <example>
/// <code>
/// var wallet = new SynorWallet(new WalletConfig { ApiKey = "your-api-key" });
///
/// // Create a new wallet
/// var result = await wallet.CreateWalletAsync();
/// Console.WriteLine($"Wallet ID: {result.Wallet.Id}");
/// Console.WriteLine($"Mnemonic: {result.Mnemonic}");
///
/// // Get balance
/// var balance = await wallet.GetBalanceAsync(result.Wallet.Addresses[0].AddressString);
/// Console.WriteLine($"Balance: {balance.Total}");
///
/// wallet.Dispose();
/// </code>
/// </example>
public class SynorWallet : IDisposable
{
    private readonly WalletConfig _config;
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions;
    private bool _disposed;

    public SynorWallet(WalletConfig config)
    {
        _config = config;
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri(config.Endpoint),
            Timeout = config.Timeout
        };
        _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");

        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
            PropertyNameCaseInsensitive = true
        };
    }

    /// <summary>
    /// Create a new wallet.
    /// </summary>
    public async Task<CreateWalletResult> CreateWalletAsync(
        WalletType type = WalletType.Standard,
        CancellationToken cancellationToken = default)
    {
        var request = new { type = type.ToString().ToLower(), network = _config.Network.ToString().ToLower() };
        return await PostAsync<CreateWalletResult>("/wallets", request, cancellationToken);
    }

    /// <summary>
    /// Import a wallet from mnemonic.
    /// </summary>
    public async Task<Wallet> ImportWalletAsync(
        string mnemonic,
        string? passphrase = null,
        CancellationToken cancellationToken = default)
    {
        var request = new
        {
            mnemonic,
            passphrase,
            network = _config.Network.ToString().ToLower()
        };
        return await PostAsync<Wallet>("/wallets/import", request, cancellationToken);
    }

    /// <summary>
    /// Get a wallet by ID.
    /// </summary>
    public async Task<Wallet> GetWalletAsync(
        string walletId,
        CancellationToken cancellationToken = default)
    {
        return await GetAsync<Wallet>($"/wallets/{walletId}", cancellationToken);
    }

    /// <summary>
    /// Generate a new address for a wallet.
    /// </summary>
    public async Task<Address> GenerateAddressAsync(
        string walletId,
        bool isChange = false,
        CancellationToken cancellationToken = default)
    {
        var request = new { is_change = isChange };
        return await PostAsync<Address>($"/wallets/{walletId}/addresses", request, cancellationToken);
    }

    /// <summary>
    /// Get a stealth address for privacy transactions.
    /// </summary>
    public async Task<StealthAddress> GetStealthAddressAsync(
        string walletId,
        CancellationToken cancellationToken = default)
    {
        return await GetAsync<StealthAddress>($"/wallets/{walletId}/stealth-address", cancellationToken);
    }

    /// <summary>
    /// Sign a transaction.
    /// </summary>
    public async Task<SignedTransaction> SignTransactionAsync(
        string walletId,
        Transaction transaction,
        CancellationToken cancellationToken = default)
    {
        var request = new { wallet_id = walletId, transaction };
        return await PostAsync<SignedTransaction>("/transactions/sign", request, cancellationToken);
    }

    /// <summary>
    /// Sign a message.
    /// </summary>
    public async Task<Signature> SignMessageAsync(
        string walletId,
        string message,
        int addressIndex = 0,
        CancellationToken cancellationToken = default)
    {
        var request = new { wallet_id = walletId, message, address_index = addressIndex };
        return await PostAsync<Signature>("/messages/sign", request, cancellationToken);
    }

    /// <summary>
    /// Verify a message signature.
    /// </summary>
    public async Task<bool> VerifyMessageAsync(
        string message,
        string signature,
        string address,
        CancellationToken cancellationToken = default)
    {
        var request = new { message, signature, address };
        var response = await PostAsync<JsonElement>("/messages/verify", request, cancellationToken);
        return response.GetProperty("valid").GetBoolean();
    }

    /// <summary>
    /// Get balance for an address.
    /// </summary>
    public async Task<Balance> GetBalanceAsync(
        string address,
        CancellationToken cancellationToken = default)
    {
        return await GetAsync<Balance>($"/addresses/{address}/balance", cancellationToken);
    }

    /// <summary>
    /// Get UTXOs for an address.
    /// </summary>
    public async Task<List<UTXO>> GetUTXOsAsync(
        string address,
        int minConfirmations = 1,
        CancellationToken cancellationToken = default)
    {
        return await GetAsync<List<UTXO>>(
            $"/addresses/{address}/utxos?min_confirmations={minConfirmations}",
            cancellationToken);
    }

    /// <summary>
    /// Estimate transaction fee.
    /// </summary>
    public async Task<long> EstimateFeeAsync(
        Priority priority = Priority.Medium,
        CancellationToken cancellationToken = default)
    {
        var response = await GetAsync<JsonElement>(
            $"/fees/estimate?priority={priority.ToString().ToLower()}",
            cancellationToken);
        return response.GetProperty("fee_per_byte").GetInt64();
    }

    private async Task<T> GetAsync<T>(string path, CancellationToken cancellationToken)
    {
        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.GetAsync(path, cancellationToken);
            await EnsureSuccessAsync(response);
            return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, cancellationToken)
                ?? throw new WalletException("Invalid response");
        });
    }

    private async Task<T> PostAsync<T>(string path, object body, CancellationToken cancellationToken)
    {
        return await ExecuteWithRetryAsync(async () =>
        {
            var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, cancellationToken);
            await EnsureSuccessAsync(response);
            return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, cancellationToken)
                ?? throw new WalletException("Invalid response");
        });
    }

    private async Task EnsureSuccessAsync(HttpResponseMessage response)
    {
        if (!response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            throw new WalletException(
                $"HTTP error: {content}",
                statusCode: (int)response.StatusCode);
        }
    }

    private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation)
    {
        Exception? lastException = null;

        for (int attempt = 0; attempt < _config.Retries; attempt++)
        {
            try
            {
                return await operation();
            }
            catch (Exception ex)
            {
                lastException = ex;
                if (_config.Debug)
                {
                    Console.WriteLine($"Attempt {attempt + 1} failed: {ex.Message}");
                }
                if (attempt < _config.Retries - 1)
                {
                    await Task.Delay(TimeSpan.FromSeconds(attempt + 1));
                }
            }
        }

        throw lastException ?? new WalletException("Unknown error after retries");
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _httpClient.Dispose();
            _disposed = true;
        }
        GC.SuppressFinalize(this);
    }
}
