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

namespace Synor.Ibc
{
    /// <summary>
    /// Synor IBC SDK for C#
    ///
    /// Inter-Blockchain Communication (IBC) protocol for cross-chain interoperability.
    /// </summary>
    public class SynorIbc : IDisposable
    {
        private readonly IbcConfig _config;
        private readonly HttpClient _httpClient;
        private bool _closed;

        public LightClientClient Clients { get; }
        public ConnectionsClient Connections { get; }
        public ChannelsClient Channels { get; }
        public TransferClient Transfer { get; }
        public SwapsClient Swaps { get; }

        public SynorIbc(IbcConfig config)
        {
            _config = config;
            _httpClient = new HttpClient
            {
                Timeout = TimeSpan.FromSeconds(config.Timeout)
            };

            Clients = new LightClientClient(this);
            Connections = new ConnectionsClient(this);
            Channels = new ChannelsClient(this);
            Transfer = new TransferClient(this);
            Swaps = new SwapsClient(this);
        }

        public string ChainId => _config.ChainId;

        public Task<Dictionary<string, object>> GetChainInfoAsync() => GetAsync("/chain");

        public async Task<Height> GetHeightAsync()
        {
            var result = await GetAsync("/chain/height");
            return new Height(
                result.TryGetValue("revision_number", out var rn) ? Convert.ToUInt64(rn) : 0,
                result.TryGetValue("revision_height", out var rh) ? Convert.ToUInt64(rh) : 1
            );
        }

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

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

        public bool IsClosed => _closed;

        // Internal HTTP methods
        internal async Task<Dictionary<string, object>> GetAsync(string path)
        {
            CheckClosed();
            var request = new HttpRequestMessage(HttpMethod.Get, $"{_config.Endpoint}{path}");
            AddHeaders(request);
            var response = await _httpClient.SendAsync(request);
            return await HandleResponseAsync(response);
        }

        internal async Task<Dictionary<string, object>> PostAsync(string path, Dictionary<string, object> body)
        {
            CheckClosed();
            var request = new HttpRequestMessage(HttpMethod.Post, $"{_config.Endpoint}{path}")
            {
                Content = new StringContent(
                    JsonSerializer.Serialize(body),
                    Encoding.UTF8,
                    "application/json"
                )
            };
            AddHeaders(request);
            var response = await _httpClient.SendAsync(request);
            return await HandleResponseAsync(response);
        }

        private void AddHeaders(HttpRequestMessage request)
        {
            request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _config.ApiKey);
            request.Headers.Add("X-SDK-Version", "csharp/0.1.0");
            request.Headers.Add("X-Chain-Id", _config.ChainId);
        }

        private static async Task<Dictionary<string, object>> HandleResponseAsync(HttpResponseMessage response)
        {
            var content = await response.Content.ReadAsStringAsync();
            if ((int)response.StatusCode >= 400)
            {
                Dictionary<string, object>? error = null;
                try
                {
                    error = JsonSerializer.Deserialize<Dictionary<string, object>>(content);
                }
                catch { }
                throw new IbcException(
                    error?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)response.StatusCode}",
                    error?.GetValueOrDefault("code")?.ToString(),
                    (int)response.StatusCode
                );
            }
            return JsonSerializer.Deserialize<Dictionary<string, object>>(content) ?? new();
        }

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

        /// <summary>
        /// Light client sub-client
        /// </summary>
        public class LightClientClient
        {
            private readonly SynorIbc _ibc;

            internal LightClientClient(SynorIbc ibc) => _ibc = ibc;

            public async Task<ClientId> CreateAsync(
                ClientType clientType,
                ClientState clientState,
                Dictionary<string, object> consensusState)
            {
                var result = await _ibc.PostAsync("/clients", new Dictionary<string, object>
                {
                    ["client_type"] = clientType.ToApiString(),
                    ["client_state"] = new Dictionary<string, object>
                    {
                        ["chain_id"] = clientState.ChainId,
                        ["trust_level"] = new Dictionary<string, object>
                        {
                            ["numerator"] = clientState.TrustLevel.Numerator,
                            ["denominator"] = clientState.TrustLevel.Denominator
                        },
                        ["trusting_period"] = clientState.TrustingPeriod,
                        ["unbonding_period"] = clientState.UnbondingPeriod,
                        ["max_clock_drift"] = clientState.MaxClockDrift,
                        ["latest_height"] = new Dictionary<string, object>
                        {
                            ["revision_number"] = clientState.LatestHeight.RevisionNumber,
                            ["revision_height"] = clientState.LatestHeight.RevisionHeight
                        }
                    },
                    ["consensus_state"] = consensusState
                });
                return new ClientId(result["client_id"].ToString()!);
            }

            public async Task<ClientState> GetStateAsync(ClientId clientId)
            {
                var result = await _ibc.GetAsync($"/clients/{clientId.Id}/state");
                var trustLevel = (JsonElement)result["trust_level"];
                var latestHeight = (JsonElement)result["latest_height"];
                return new ClientState(
                    result["chain_id"].ToString()!,
                    new TrustLevel(
                        trustLevel.GetProperty("numerator").GetInt32(),
                        trustLevel.GetProperty("denominator").GetInt32()
                    ),
                    Convert.ToUInt64(result["trusting_period"]),
                    Convert.ToUInt64(result["unbonding_period"]),
                    Convert.ToUInt64(result["max_clock_drift"]),
                    new Height(
                        latestHeight.GetProperty("revision_number").GetUInt64(),
                        latestHeight.GetProperty("revision_height").GetUInt64()
                    )
                );
            }

            public async Task<List<Dictionary<string, object>>> ListAsync()
            {
                var result = await _ibc.GetAsync("/clients");
                if (result.TryGetValue("clients", out var clients) && clients is JsonElement element)
                {
                    return JsonSerializer.Deserialize<List<Dictionary<string, object>>>(element.GetRawText()) ?? new();
                }
                return new List<Dictionary<string, object>>();
            }
        }

        /// <summary>
        /// Connections sub-client
        /// </summary>
        public class ConnectionsClient
        {
            private readonly SynorIbc _ibc;

            internal ConnectionsClient(SynorIbc ibc) => _ibc = ibc;

            public async Task<ConnectionId> OpenInitAsync(ClientId clientId, ClientId counterpartyClientId)
            {
                var result = await _ibc.PostAsync("/connections/init", new Dictionary<string, object>
                {
                    ["client_id"] = clientId.Id,
                    ["counterparty_client_id"] = counterpartyClientId.Id
                });
                return new ConnectionId(result["connection_id"].ToString()!);
            }

            public Task<Dictionary<string, object>> GetAsync(ConnectionId connectionId) =>
                _ibc.GetAsync($"/connections/{connectionId.Id}");

            public async Task<List<Dictionary<string, object>>> ListAsync()
            {
                var result = await _ibc.GetAsync("/connections");
                if (result.TryGetValue("connections", out var connections) && connections is JsonElement element)
                {
                    return JsonSerializer.Deserialize<List<Dictionary<string, object>>>(element.GetRawText()) ?? new();
                }
                return new List<Dictionary<string, object>>();
            }
        }

        /// <summary>
        /// Channels sub-client
        /// </summary>
        public class ChannelsClient
        {
            private readonly SynorIbc _ibc;

            internal ChannelsClient(SynorIbc ibc) => _ibc = ibc;

            public Task BindPortAsync(PortId portId, string module) =>
                _ibc.PostAsync("/ports/bind", new Dictionary<string, object>
                {
                    ["port_id"] = portId.Id,
                    ["module"] = module
                });

            public async Task<ChannelId> OpenInitAsync(
                PortId portId,
                ChannelOrder ordering,
                ConnectionId connectionId,
                PortId counterpartyPort,
                string version)
            {
                var result = await _ibc.PostAsync("/channels/init", new Dictionary<string, object>
                {
                    ["port_id"] = portId.Id,
                    ["ordering"] = ordering.ToString().ToLowerInvariant(),
                    ["connection_id"] = connectionId.Id,
                    ["counterparty_port"] = counterpartyPort.Id,
                    ["version"] = version
                });
                return new ChannelId(result["channel_id"].ToString()!);
            }

            public Task<Dictionary<string, object>> GetAsync(PortId portId, ChannelId channelId) =>
                _ibc.GetAsync($"/channels/{portId.Id}/{channelId.Id}");

            public async Task<List<Dictionary<string, object>>> ListAsync()
            {
                var result = await _ibc.GetAsync("/channels");
                if (result.TryGetValue("channels", out var channels) && channels is JsonElement element)
                {
                    return JsonSerializer.Deserialize<List<Dictionary<string, object>>>(element.GetRawText()) ?? new();
                }
                return new List<Dictionary<string, object>>();
            }
        }

        /// <summary>
        /// Transfer sub-client (ICS-20)
        /// </summary>
        public class TransferClient
        {
            private readonly SynorIbc _ibc;

            internal TransferClient(SynorIbc ibc) => _ibc = ibc;

            public async Task<Dictionary<string, object>> TransferAsync(
                string sourcePort,
                string sourceChannel,
                string denom,
                string amount,
                string sender,
                string receiver,
                Timeout? timeout = null,
                string? memo = null)
            {
                var body = new Dictionary<string, object>
                {
                    ["source_port"] = sourcePort,
                    ["source_channel"] = sourceChannel,
                    ["token"] = new Dictionary<string, object> { ["denom"] = denom, ["amount"] = amount },
                    ["sender"] = sender,
                    ["receiver"] = receiver
                };
                if (timeout != null)
                {
                    body["timeout_height"] = new Dictionary<string, object>
                    {
                        ["revision_number"] = timeout.Height.RevisionNumber,
                        ["revision_height"] = timeout.Height.RevisionHeight
                    };
                    body["timeout_timestamp"] = timeout.Timestamp.ToString();
                }
                if (memo != null) body["memo"] = memo;
                return await _ibc.PostAsync("/transfer", body);
            }

            public Task<Dictionary<string, object>> GetDenomTraceAsync(string ibcDenom) =>
                _ibc.GetAsync($"/transfer/denom_trace/{ibcDenom}");
        }

        /// <summary>
        /// Swaps sub-client (HTLC)
        /// </summary>
        public class SwapsClient
        {
            private readonly SynorIbc _ibc;

            internal SwapsClient(SynorIbc ibc) => _ibc = ibc;

            public Task<Dictionary<string, object>> InitiateAsync(
                string responder,
                Dictionary<string, object> initiatorAsset,
                Dictionary<string, object> responderAsset) =>
                _ibc.PostAsync("/swaps/initiate", new Dictionary<string, object>
                {
                    ["responder"] = responder,
                    ["initiator_asset"] = initiatorAsset,
                    ["responder_asset"] = responderAsset
                });

            public Task LockAsync(SwapId swapId) =>
                _ibc.PostAsync($"/swaps/{swapId.Id}/lock", new Dictionary<string, object>());

            public Task<Dictionary<string, object>> RespondAsync(SwapId swapId, Dictionary<string, object> asset) =>
                _ibc.PostAsync($"/swaps/{swapId.Id}/respond", new Dictionary<string, object> { ["asset"] = asset });

            public Task<Dictionary<string, object>> ClaimAsync(SwapId swapId, byte[] secret) =>
                _ibc.PostAsync($"/swaps/{swapId.Id}/claim", new Dictionary<string, object>
                {
                    ["secret"] = Convert.ToBase64String(secret)
                });

            public Task<Dictionary<string, object>> RefundAsync(SwapId swapId) =>
                _ibc.PostAsync($"/swaps/{swapId.Id}/refund", new Dictionary<string, object>());

            public async Task<AtomicSwap> GetAsync(SwapId swapId)
            {
                var result = await _ibc.GetAsync($"/swaps/{swapId.Id}");
                return ParseAtomicSwap(result);
            }

            public async Task<List<AtomicSwap>> ListActiveAsync()
            {
                var result = await _ibc.GetAsync("/swaps/active");
                var swaps = new List<AtomicSwap>();
                if (result.TryGetValue("swaps", out var swapsObj) && swapsObj is JsonElement element)
                {
                    foreach (var swap in element.EnumerateArray())
                    {
                        var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(swap.GetRawText());
                        if (dict != null)
                            swaps.Add(ParseAtomicSwap(dict));
                    }
                }
                return swaps;
            }

            private static AtomicSwap ParseAtomicSwap(Dictionary<string, object> json)
            {
                var swapIdObj = (JsonElement)json["swap_id"];
                var swapId = swapIdObj.GetProperty("id").GetString()!;
                var stateStr = json["state"].ToString()!.ToUpperInvariant();
                var state = Enum.Parse<SwapState>(stateStr, true);
                var initiatorHtlc = JsonSerializer.Deserialize<Dictionary<string, object>>(
                    ((JsonElement)json["initiator_htlc"]).GetRawText()) ?? new();
                Dictionary<string, object>? responderHtlc = null;
                if (json.TryGetValue("responder_htlc", out var responder) && responder is JsonElement re && re.ValueKind != JsonValueKind.Null)
                {
                    responderHtlc = JsonSerializer.Deserialize<Dictionary<string, object>>(re.GetRawText());
                }
                return new AtomicSwap(new SwapId(swapId), state, initiatorHtlc, responderHtlc);
            }
        }
    }
}
