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

/// <summary>
/// Synor ZK SDK for C#/.NET
///
/// Zero-Knowledge proof systems for ZK-Rollups and privacy.
/// </summary>
public class SynorZk : IDisposable
{
    private readonly ZkConfig _config;
    private readonly HttpClient _httpClient;
    private readonly JsonSerializerOptions _jsonOptions;
    private bool _closed;

    public ProofsClient Proofs { get; }
    public CircuitsClient Circuits { get; }
    public RollupClient Rollup { get; }
    public StateClient State { get; }
    public CeremonyClient Ceremony { get; }

    public SynorZk(ZkConfig 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) }
        };

        Proofs = new ProofsClient(this);
        Circuits = new CircuitsClient(this);
        Rollup = new RollupClient(this);
        State = new StateClient(this);
        Ceremony = new CeremonyClient(this);
    }

    public ProofSystem DefaultProofSystem => _config.DefaultProofSystem;

    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 ZkException(
                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 ZkException("Client has been closed", "CLIENT_CLOSED");
    }
}

/// <summary>Proofs sub-client</summary>
public class ProofsClient
{
    private readonly SynorZk _zk;

    internal ProofsClient(SynorZk zk) => _zk = zk;

    public async Task<Proof> GenerateAsync(ProofRequest request, CancellationToken ct = default)
    {
        var body = new Dictionary<string, object>
        {
            ["circuit_type"] = request.CircuitType.ToApiString(),
            ["public_inputs"] = request.PublicInputs,
            ["private_inputs"] = request.PrivateInputs,
            ["system"] = (request.System ?? _zk.DefaultProofSystem).ToApiString()
        };
        return await _zk.PostAsync<Proof>("/proofs/generate", body, ct);
    }

    public async Task<VerificationResult> VerifyAsync(Proof proof, CancellationToken ct = default)
    {
        var body = new Dictionary<string, object>
        {
            ["proof_id"] = proof.Id,
            ["data"] = proof.Data,
            ["public_inputs"] = proof.PublicInputs,
            ["system"] = proof.System.ToApiString(),
            ["circuit_type"] = proof.CircuitType.ToApiString()
        };
        return await _zk.PostAsync<VerificationResult>("/proofs/verify", body, ct);
    }

    public Task<Proof> GetAsync(string proofId, CancellationToken ct = default) =>
        _zk.GetAsync<Proof>($"/proofs/{proofId}", ct);

    public async Task<List<Proof>> ListAsync(int? limit = null, int? offset = null, CancellationToken ct = default)
    {
        var path = "/proofs";
        var qs = new List<string>();
        if (limit.HasValue) qs.Add($"limit={limit}");
        if (offset.HasValue) qs.Add($"offset={offset}");
        if (qs.Count > 0) path += "?" + string.Join("&", qs);

        var result = await _zk.GetAsync<ProofsListResponse>(path, ct);
        return result.Proofs ?? new List<Proof>();
    }

    public async Task<byte[]> SerializeAsync(Proof proof, CancellationToken ct = default)
    {
        var result = await _zk.PostAsync<SerializeResponse>("/proofs/serialize", new { proof_id = proof.Id }, ct);
        return Convert.FromBase64String(result.Data);
    }

    public Task<Proof> DeserializeAsync(byte[] data, ProofSystem system, CancellationToken ct = default) =>
        _zk.PostAsync<Proof>("/proofs/deserialize", new { data = Convert.ToBase64String(data), system = system.ToApiString() }, ct);
}

/// <summary>Circuits sub-client</summary>
public class CircuitsClient
{
    private readonly SynorZk _zk;

    internal CircuitsClient(SynorZk zk) => _zk = zk;

    public Task<VerificationKey> GetVerificationKeyAsync(CircuitType circuitType, ProofSystem? system = null, CancellationToken ct = default)
    {
        var sys = system ?? _zk.DefaultProofSystem;
        return _zk.GetAsync<VerificationKey>($"/circuits/{circuitType.ToApiString()}/vk?system={sys.ToApiString()}", ct);
    }

    public Task<ProvingKey> GetProvingKeyAsync(CircuitType circuitType, ProofSystem? system = null, CancellationToken ct = default)
    {
        var sys = system ?? _zk.DefaultProofSystem;
        return _zk.GetAsync<ProvingKey>($"/circuits/{circuitType.ToApiString()}/pk?system={sys.ToApiString()}", ct);
    }

    public async Task<List<CircuitConfig>> ListAsync(CancellationToken ct = default)
    {
        var result = await _zk.GetAsync<CircuitsListResponse>("/circuits", ct);
        return result.Circuits ?? new List<CircuitConfig>();
    }

    public Task<CircuitConfig> GetConfigAsync(CircuitType circuitType, CancellationToken ct = default) =>
        _zk.GetAsync<CircuitConfig>($"/circuits/{circuitType.ToApiString()}/config", ct);

    public async Task<string> CompileAsync(CircuitConfig config, CancellationToken ct = default)
    {
        var result = await _zk.PostAsync<CircuitIdResponse>("/circuits/compile", config, ct);
        return result.CircuitId;
    }
}

/// <summary>Rollup sub-client</summary>
public class RollupClient
{
    private readonly SynorZk _zk;

    internal RollupClient(SynorZk zk) => _zk = zk;

    public Task<RollupStats> GetStatsAsync(CancellationToken ct = default) =>
        _zk.GetAsync<RollupStats>("/rollup/stats", ct);

    public Task<Batch> GetCurrentBatchAsync(CancellationToken ct = default) =>
        _zk.GetAsync<Batch>("/rollup/batch/current", ct);

    public Task<Batch> GetBatchAsync(long batchNumber, CancellationToken ct = default) =>
        _zk.GetAsync<Batch>($"/rollup/batch/{batchNumber}", ct);

    public async Task<string> SubmitTransferAsync(Transfer transfer, CancellationToken ct = default)
    {
        var result = await _zk.PostAsync<TxIdResponse>("/rollup/transfer", transfer, ct);
        return result.TxId;
    }

    public async Task<string> SubmitDepositAsync(Deposit deposit, CancellationToken ct = default)
    {
        var result = await _zk.PostAsync<TxIdResponse>("/rollup/deposit", deposit, ct);
        return result.TxId;
    }

    public async Task<string> SubmitWithdrawalAsync(Withdrawal withdrawal, CancellationToken ct = default)
    {
        var result = await _zk.PostAsync<TxIdResponse>("/rollup/withdraw", withdrawal, ct);
        return result.TxId;
    }

    public Task<Batch> FinalizeBatchAsync(CancellationToken ct = default) =>
        _zk.PostAsync<Batch>("/rollup/batch/finalize", null, ct);

    public async Task<List<Transfer>> GetPendingTransactionsAsync(CancellationToken ct = default)
    {
        var result = await _zk.GetAsync<TransactionsListResponse>("/rollup/pending", ct);
        return result.Transactions ?? new List<Transfer>();
    }
}

/// <summary>State sub-client</summary>
public class StateClient
{
    private readonly SynorZk _zk;

    internal StateClient(SynorZk zk) => _zk = zk;

    public async Task<string> GetRootAsync(CancellationToken ct = default)
    {
        var result = await _zk.GetAsync<RootResponse>("/state/root", ct);
        return result.Root;
    }

    public Task<AccountState> GetAccountAsync(string address, CancellationToken ct = default) =>
        _zk.GetAsync<AccountState>($"/state/account/{address}", ct);

    public Task<MerkleProof> GetMerkleProofAsync(string address, CancellationToken ct = default) =>
        _zk.GetAsync<MerkleProof>($"/state/proof/{address}", ct);

    public async Task<bool> VerifyMerkleProofAsync(MerkleProof proof, CancellationToken ct = default)
    {
        var result = await _zk.PostAsync<ValidResponse>("/state/proof/verify", proof, ct);
        return result.Valid;
    }

    public Task<Dictionary<string, object>> GetStateAtBatchAsync(long batchNumber, CancellationToken ct = default) =>
        _zk.GetAsync<Dictionary<string, object>>($"/state/batch/{batchNumber}", ct);
}

/// <summary>Ceremony sub-client</summary>
public class CeremonyClient
{
    private readonly SynorZk _zk;

    internal CeremonyClient(SynorZk zk) => _zk = zk;

    public Task<TrustedSetup> GetStatusAsync(CircuitType circuitType, ProofSystem? system = null, CancellationToken ct = default)
    {
        var sys = system ?? _zk.DefaultProofSystem;
        return _zk.GetAsync<TrustedSetup>($"/ceremony/{circuitType.ToApiString()}?system={sys.ToApiString()}", ct);
    }

    public Task<CeremonyContribution> ContributeAsync(CircuitType circuitType, byte[] entropy, ProofSystem? system = null, CancellationToken ct = default)
    {
        var sys = system ?? _zk.DefaultProofSystem;
        return _zk.PostAsync<CeremonyContribution>("/ceremony/contribute", new
        {
            circuit_type = circuitType.ToApiString(),
            entropy = Convert.ToBase64String(entropy),
            system = sys.ToApiString()
        }, ct);
    }

    public async Task<bool> VerifyContributionAsync(CircuitType circuitType, string contributionHash, CancellationToken ct = default)
    {
        var result = await _zk.PostAsync<ValidResponse>("/ceremony/verify", new
        {
            circuit_type = circuitType.ToApiString(),
            contribution_hash = contributionHash
        }, ct);
        return result.Valid;
    }

    public async Task<List<CeremonyContribution>> ListContributionsAsync(CircuitType circuitType, CancellationToken ct = default)
    {
        var result = await _zk.GetAsync<ContributionsListResponse>($"/ceremony/{circuitType.ToApiString()}/contributions", ct);
        return result.Contributions ?? new List<CeremonyContribution>();
    }
}

// Response helper types
internal record ProofsListResponse(List<Proof>? Proofs);
internal record CircuitsListResponse(List<CircuitConfig>? Circuits);
internal record TransactionsListResponse(List<Transfer>? Transactions);
internal record ContributionsListResponse(List<CeremonyContribution>? Contributions);
internal record SerializeResponse(string Data);
internal record CircuitIdResponse(string CircuitId);
internal record TxIdResponse(string TxId);
internal record RootResponse(string Root);
internal record ValidResponse(bool Valid);
