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.Crypto;

/// <summary>
/// Synor Crypto SDK for C#/.NET
///
/// Quantum-resistant cryptographic primitives for the Synor blockchain.
/// </summary>
public class SynorCrypto : IDisposable
{
    private readonly CryptoConfig _config;
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions;
    private bool _closed;

    public MnemonicClient Mnemonic { get; }
    public KeypairClient Keypairs { get; }
    public SigningClient Signing { get; }
    public FalconClient Falcon { get; }
    public SphincsClient Sphincs { get; }
    public KdfClient Kdf { get; }
    public HashClient Hash { get; }

    public SynorCrypto(CryptoConfig config)
    {
        _config = config;
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri(config.Endpoint),
            Timeout = TimeSpan.FromMilliseconds(config.Timeout)
        };
        _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
        _httpClient.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0");

        _jsonOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
            Converters = { new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) }
        };

        Mnemonic = new MnemonicClient(this);
        Keypairs = new KeypairClient(this);
        Signing = new SigningClient(this);
        Falcon = new FalconClient(this);
        Sphincs = new SphincsClient(this);
        Kdf = new KdfClient(this);
        Hash = new HashClient(this);
    }

    public Network DefaultNetwork => _config.DefaultNetwork;

    public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
    {
        try
        {
            var result = await GetAsync<Dictionary<string, object>>("/health", ct);
            return result.TryGetValue("status", out var status) && status?.ToString() == "healthy";
        }
        catch
        {
            return false;
        }
    }

    public Task<Dictionary<string, object>> GetInfoAsync(CancellationToken ct = default) =>
        GetAsync<Dictionary<string, object>>("/info", ct);

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

    public bool IsClosed => _closed;

    public void Dispose()
    {
        Close();
        GC.SuppressFinalize(this);
    }

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

    internal async Task<T> PostAsync<T>(string path, object? body = null, CancellationToken ct = default)
    {
        CheckClosed();
        var content = body != null
            ? JsonContent.Create(body, options: _jsonOptions)
            : JsonContent.Create(new { });
        var response = await _httpClient.PostAsync(path, content, ct);
        return await HandleResponseAsync<T>(response);
    }

    private async Task<T> HandleResponseAsync<T>(HttpResponseMessage response)
    {
        var json = await response.Content.ReadAsStringAsync();
        if (!response.IsSuccessStatusCode)
        {
            var error = JsonSerializer.Deserialize<Dictionary<string, object>>(json, _jsonOptions) ?? new();
            throw new CryptoException(
                error.TryGetValue("message", out var msg) ? msg?.ToString() ?? $"HTTP {(int)response.StatusCode}" : $"HTTP {(int)response.StatusCode}",
                error.TryGetValue("code", out var code) ? code?.ToString() : null,
                (int)response.StatusCode
            );
        }
        return JsonSerializer.Deserialize<T>(json, _jsonOptions)!;
    }

    private void CheckClosed()
    {
        if (_closed) throw new CryptoException("Client has been closed", "CLIENT_CLOSED");
    }
}

/// <summary>Mnemonic sub-client</summary>
public class MnemonicClient
{
    private readonly SynorCrypto _crypto;

    internal MnemonicClient(SynorCrypto crypto) => _crypto = crypto;

    public Task<Mnemonic> GenerateAsync(int wordCount = 24, CancellationToken ct = default) =>
        _crypto.PostAsync<Mnemonic>("/mnemonic/generate", new { word_count = wordCount }, ct);

    public Task<Mnemonic> FromPhraseAsync(string phrase, CancellationToken ct = default) =>
        _crypto.PostAsync<Mnemonic>("/mnemonic/from-phrase", new { phrase }, ct);

    public Task<MnemonicValidation> ValidateAsync(string phrase, CancellationToken ct = default) =>
        _crypto.PostAsync<MnemonicValidation>("/mnemonic/validate", new { phrase }, ct);

    public async Task<byte[]> ToSeedAsync(string phrase, string passphrase = "", CancellationToken ct = default)
    {
        var result = await _crypto.PostAsync<SeedResponse>("/mnemonic/to-seed", new { phrase, passphrase }, ct);
        return Convert.FromBase64String(result.Seed);
    }

    public async Task<List<string>> SuggestWordsAsync(string partial, int limit = 5, CancellationToken ct = default)
    {
        var result = await _crypto.PostAsync<SuggestResponse>("/mnemonic/suggest", new { partial, limit }, ct);
        return result.Suggestions ?? new List<string>();
    }
}

/// <summary>Keypair sub-client</summary>
public class KeypairClient
{
    private readonly SynorCrypto _crypto;

    internal KeypairClient(SynorCrypto crypto) => _crypto = crypto;

    public Task<HybridKeypair> GenerateAsync(CancellationToken ct = default) =>
        _crypto.PostAsync<HybridKeypair>("/keypair/generate", null, ct);

    public Task<HybridKeypair> FromMnemonicAsync(string phrase, string passphrase = "", CancellationToken ct = default) =>
        _crypto.PostAsync<HybridKeypair>("/keypair/from-mnemonic", new { phrase, passphrase }, ct);

    public Task<HybridKeypair> FromSeedAsync(byte[] seed, CancellationToken ct = default) =>
        _crypto.PostAsync<HybridKeypair>("/keypair/from-seed", new { seed = Convert.ToBase64String(seed) }, ct);

    public Task<Address> GetAddressAsync(HybridPublicKey publicKey, Network network, CancellationToken ct = default) =>
        _crypto.PostAsync<Address>("/keypair/address", new
        {
            public_key = publicKey.ToDict(),
            network = network.ToApiString()
        }, ct);
}

/// <summary>Signing sub-client</summary>
public class SigningClient
{
    private readonly SynorCrypto _crypto;

    internal SigningClient(SynorCrypto crypto) => _crypto = crypto;

    public Task<HybridSignature> SignAsync(HybridKeypair keypair, byte[] message, CancellationToken ct = default) =>
        _crypto.PostAsync<HybridSignature>("/sign/hybrid", new
        {
            secret_key = keypair.SecretKey.ToDict(),
            message = Convert.ToBase64String(message)
        }, ct);

    public async Task<bool> VerifyAsync(HybridPublicKey publicKey, byte[] message, HybridSignature signature, CancellationToken ct = default)
    {
        var result = await _crypto.PostAsync<ValidResponse>("/sign/verify", new
        {
            public_key = publicKey.ToDict(),
            message = Convert.ToBase64String(message),
            signature = signature.ToDict()
        }, ct);
        return result.Valid;
    }

    public async Task<byte[]> SignEd25519Async(byte[] secretKey, byte[] message, CancellationToken ct = default)
    {
        var result = await _crypto.PostAsync<SignatureResponse>("/sign/ed25519", new
        {
            secret_key = Convert.ToBase64String(secretKey),
            message = Convert.ToBase64String(message)
        }, ct);
        return Convert.FromBase64String(result.Signature);
    }
}

/// <summary>Falcon sub-client</summary>
public class FalconClient
{
    private readonly SynorCrypto _crypto;

    internal FalconClient(SynorCrypto crypto) => _crypto = crypto;

    public Task<FalconKeypair> GenerateAsync(FalconVariant variant = FalconVariant.Falcon512, CancellationToken ct = default) =>
        _crypto.PostAsync<FalconKeypair>("/falcon/generate", new { variant = variant.ToApiString() }, ct);

    public Task<FalconSignature> SignAsync(FalconKeypair keypair, byte[] message, CancellationToken ct = default) =>
        _crypto.PostAsync<FalconSignature>("/falcon/sign", new
        {
            variant = keypair.VariantEnum.ToApiString(),
            secret_key = Convert.ToBase64String(keypair.SecretKey.KeyBytes),
            message = Convert.ToBase64String(message)
        }, ct);

    public async Task<bool> VerifyAsync(byte[] publicKey, byte[] message, FalconSignature signature, CancellationToken ct = default)
    {
        var result = await _crypto.PostAsync<ValidResponse>("/falcon/verify", new
        {
            variant = signature.VariantEnum.ToApiString(),
            public_key = Convert.ToBase64String(publicKey),
            message = Convert.ToBase64String(message),
            signature = Convert.ToBase64String(signature.SignatureBytes)
        }, ct);
        return result.Valid;
    }
}

/// <summary>SPHINCS+ sub-client</summary>
public class SphincsClient
{
    private readonly SynorCrypto _crypto;

    internal SphincsClient(SynorCrypto crypto) => _crypto = crypto;

    public Task<SphincsKeypair> GenerateAsync(SphincsVariant variant = SphincsVariant.Shake128s, CancellationToken ct = default) =>
        _crypto.PostAsync<SphincsKeypair>("/sphincs/generate", new { variant = variant.ToApiString() }, ct);

    public Task<SphincsSignature> SignAsync(SphincsKeypair keypair, byte[] message, CancellationToken ct = default) =>
        _crypto.PostAsync<SphincsSignature>("/sphincs/sign", new
        {
            variant = keypair.VariantEnum.ToApiString(),
            secret_key = Convert.ToBase64String(keypair.SecretKey.KeyBytes),
            message = Convert.ToBase64String(message)
        }, ct);

    public async Task<bool> VerifyAsync(byte[] publicKey, byte[] message, SphincsSignature signature, CancellationToken ct = default)
    {
        var result = await _crypto.PostAsync<ValidResponse>("/sphincs/verify", new
        {
            variant = signature.VariantEnum.ToApiString(),
            public_key = Convert.ToBase64String(publicKey),
            message = Convert.ToBase64String(message),
            signature = Convert.ToBase64String(signature.SignatureBytes)
        }, ct);
        return result.Valid;
    }
}

/// <summary>KDF sub-client</summary>
public class KdfClient
{
    private readonly SynorCrypto _crypto;

    internal KdfClient(SynorCrypto crypto) => _crypto = crypto;

    public async Task<byte[]> DeriveKeyAsync(byte[] seed, DerivationConfig? config = null, CancellationToken ct = default)
    {
        config ??= new DerivationConfig();
        var body = new Dictionary<string, object>
        {
            ["seed"] = Convert.ToBase64String(seed),
            ["output_length"] = config.OutputLength
        };
        if (config.Salt != null) body["salt"] = Convert.ToBase64String(config.Salt);
        if (config.Info != null) body["info"] = Convert.ToBase64String(config.Info);

        var result = await _crypto.PostAsync<KeyResponse>("/kdf/hkdf", body, ct);
        return Convert.FromBase64String(result.Key);
    }

    public async Task<byte[]> DeriveFromPasswordAsync(byte[] password, PasswordDerivationConfig config, CancellationToken ct = default)
    {
        var result = await _crypto.PostAsync<KeyResponse>("/kdf/pbkdf2", new
        {
            password = Convert.ToBase64String(password),
            salt = Convert.ToBase64String(config.Salt),
            iterations = config.Iterations,
            output_length = config.OutputLength
        }, ct);
        return Convert.FromBase64String(result.Key);
    }
}

/// <summary>Hash sub-client</summary>
public class HashClient
{
    private readonly SynorCrypto _crypto;

    internal HashClient(SynorCrypto crypto) => _crypto = crypto;

    public Task<Hash256> Sha3_256Async(byte[] data, CancellationToken ct = default) =>
        _crypto.PostAsync<Hash256>("/hash/sha3-256", new { data = Convert.ToBase64String(data) }, ct);

    public Task<Hash256> Blake3Async(byte[] data, CancellationToken ct = default) =>
        _crypto.PostAsync<Hash256>("/hash/blake3", new { data = Convert.ToBase64String(data) }, ct);

    public Task<Hash256> Keccak256Async(byte[] data, CancellationToken ct = default) =>
        _crypto.PostAsync<Hash256>("/hash/keccak256", new { data = Convert.ToBase64String(data) }, ct);
}

// Response helper types
internal record SeedResponse(string Seed);
internal record SuggestResponse(List<string>? Suggestions);
internal record SignatureResponse(string Signature);
internal record KeyResponse(string Key);
internal record ValidResponse(bool Valid);
