using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text.Json.Serialization;

namespace Synor.Ibc
{
    /// <summary>
    /// Synor IBC SDK Types for C#
    ///
    /// Inter-Blockchain Communication (IBC) protocol types.
    /// </summary>

    /// <summary>
    /// IBC Height representation
    /// </summary>
    public record Height(
        [property: JsonPropertyName("revision_number")] ulong RevisionNumber = 0,
        [property: JsonPropertyName("revision_height")] ulong RevisionHeight = 1
    )
    {
        public bool IsZero => RevisionNumber == 0 && RevisionHeight == 0;

        public Height Increment() => this with { RevisionHeight = RevisionHeight + 1 };
    }

    /// <summary>
    /// Chain identifier
    /// </summary>
    public record ChainId(string Id);

    /// <summary>
    /// IBC Version with features
    /// </summary>
    public record Version(string Identifier, List<string> Features)
    {
        public static Version DefaultConnection() =>
            new("1", new List<string> { "ORDER_ORDERED", "ORDER_UNORDERED" });
    }

    /// <summary>
    /// Light client types
    /// </summary>
    public enum ClientType
    {
        Tendermint,
        SoloMachine,
        Localhost,
        Wasm
    }

    public static class ClientTypeExtensions
    {
        public static string ToApiString(this ClientType type) => type switch
        {
            ClientType.Tendermint => "tendermint",
            ClientType.SoloMachine => "solo_machine",
            ClientType.Localhost => "localhost",
            ClientType.Wasm => "wasm",
            _ => throw new ArgumentOutOfRangeException(nameof(type))
        };
    }

    /// <summary>
    /// Light client identifier
    /// </summary>
    public record ClientId(string Id);

    /// <summary>
    /// Trust level configuration
    /// </summary>
    public record TrustLevel(int Numerator = 1, int Denominator = 3);

    /// <summary>
    /// Light client state
    /// </summary>
    public record ClientState(
        [property: JsonPropertyName("chain_id")] string ChainId,
        [property: JsonPropertyName("trust_level")] TrustLevel TrustLevel,
        [property: JsonPropertyName("trusting_period")] ulong TrustingPeriod,
        [property: JsonPropertyName("unbonding_period")] ulong UnbondingPeriod,
        [property: JsonPropertyName("max_clock_drift")] ulong MaxClockDrift,
        [property: JsonPropertyName("latest_height")] Height LatestHeight,
        [property: JsonPropertyName("frozen_height")] Height? FrozenHeight = null
    );

    /// <summary>
    /// Connection state
    /// </summary>
    public enum ConnectionState
    {
        Uninitialized,
        Init,
        TryOpen,
        Open
    }

    /// <summary>
    /// Connection identifier
    /// </summary>
    public record ConnectionId(string Id)
    {
        public static ConnectionId NewId(int sequence) => new($"connection-{sequence}");
    }

    /// <summary>
    /// Port identifier
    /// </summary>
    public record PortId(string Id)
    {
        public static PortId Transfer() => new("transfer");
    }

    /// <summary>
    /// Channel identifier
    /// </summary>
    public record ChannelId(string Id)
    {
        public static ChannelId NewId(int sequence) => new($"channel-{sequence}");
    }

    /// <summary>
    /// Channel ordering
    /// </summary>
    public enum ChannelOrder
    {
        Unordered,
        Ordered
    }

    /// <summary>
    /// Channel state
    /// </summary>
    public enum ChannelState
    {
        Uninitialized,
        Init,
        TryOpen,
        Open,
        Closed
    }

    /// <summary>
    /// Timeout information
    /// </summary>
    public record Timeout(Height Height, BigInteger Timestamp)
    {
        public Timeout(Height height) : this(height, BigInteger.Zero) { }

        public static Timeout FromHeight(ulong height) =>
            new(new Height(0, height), BigInteger.Zero);

        public static Timeout FromTimestamp(BigInteger timestamp) =>
            new(new Height(), timestamp);

        public Dictionary<string, object> ToJson() => new()
        {
            ["height"] = new Dictionary<string, object>
            {
                ["revision_number"] = Height.RevisionNumber,
                ["revision_height"] = Height.RevisionHeight
            },
            ["timestamp"] = Timestamp.ToString()
        };
    }

    /// <summary>
    /// Transfer packet data (ICS-20)
    /// </summary>
    public record FungibleTokenPacketData(
        string Denom,
        string Amount,
        string Sender,
        string Receiver,
        string Memo = ""
    )
    {
        public bool IsNative => !Denom.Contains('/');
    }

    /// <summary>
    /// Swap state
    /// </summary>
    public enum SwapState
    {
        Pending,
        Locked,
        Completed,
        Refunded,
        Expired,
        Cancelled
    }

    /// <summary>
    /// Swap identifier
    /// </summary>
    public record SwapId(string Id);

    /// <summary>
    /// Native asset for swaps
    /// </summary>
    public record NativeAsset(BigInteger Amount)
    {
        public Dictionary<string, object> ToJson() => new()
        {
            ["native"] = new Dictionary<string, object> { ["amount"] = Amount.ToString() }
        };
    }

    /// <summary>
    /// ICS-20 asset for swaps
    /// </summary>
    public record Ics20Asset(string Denom, BigInteger Amount)
    {
        public Dictionary<string, object> ToJson() => new()
        {
            ["ics20"] = new Dictionary<string, object>
            {
                ["denom"] = Denom,
                ["amount"] = Amount.ToString()
            }
        };
    }

    /// <summary>
    /// Hashlock - hash of the secret
    /// </summary>
    public record Hashlock(byte[] Hash);

    /// <summary>
    /// Timelock - expiration time
    /// </summary>
    public record Timelock(BigInteger Expiry)
    {
        public bool IsExpired(BigInteger current) => current >= Expiry;
    }

    /// <summary>
    /// Atomic swap
    /// </summary>
    public record AtomicSwap(
        SwapId SwapId,
        SwapState State,
        Dictionary<string, object> InitiatorHtlc,
        Dictionary<string, object>? ResponderHtlc
    );

    /// <summary>
    /// IBC SDK configuration
    /// </summary>
    public record IbcConfig(
        string ApiKey,
        string Endpoint = "https://ibc.synor.io/v1",
        string WsEndpoint = "wss://ibc.synor.io/v1/ws",
        int Timeout = 30,
        int Retries = 3,
        string ChainId = "synor-1",
        bool Debug = false
    );

    /// <summary>
    /// IBC exception
    /// </summary>
    public class IbcException : Exception
    {
        public string? Code { get; }
        public int? Status { get; }

        public IbcException(string message, string? code = null, int? status = null)
            : base(message)
        {
            Code = code;
            Status = status;
        }

        public override string ToString() =>
            $"IbcException: {Message}{(Code != null ? $" ({Code})" : "")}";
    }
}
