using System.Text.Json.Serialization;

namespace Synor.Sdk.Rpc;

/// <summary>
/// Network environment.
/// </summary>
public enum RpcNetwork
{
    [JsonPropertyName("mainnet")] Mainnet,
    [JsonPropertyName("testnet")] Testnet,
    [JsonPropertyName("devnet")] Devnet
}

/// <summary>
/// Transaction priority.
/// </summary>
public enum RpcPriority
{
    [JsonPropertyName("low")] Low,
    [JsonPropertyName("medium")] Medium,
    [JsonPropertyName("high")] High
}

/// <summary>
/// Transaction status.
/// </summary>
public enum TransactionStatus
{
    [JsonPropertyName("pending")] Pending,
    [JsonPropertyName("confirmed")] Confirmed,
    [JsonPropertyName("failed")] Failed
}

/// <summary>
/// RPC configuration.
/// </summary>
public class RpcConfig
{
    public required string ApiKey { get; init; }
    public string Endpoint { get; init; } = "https://rpc.synor.io/v1";
    public string WsEndpoint { get; init; } = "wss://rpc.synor.io/v1/ws";
    public RpcNetwork Network { get; init; } = RpcNetwork.Mainnet;
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(30);
    public int Retries { get; init; } = 3;
    public bool Debug { get; init; } = false;
}

/// <summary>
/// Block header information.
/// </summary>
public record BlockHeader(
    [property: JsonPropertyName("hash")] string Hash,
    [property: JsonPropertyName("height")] long Height,
    [property: JsonPropertyName("previous_hash")] string PreviousHash,
    [property: JsonPropertyName("timestamp")] long Timestamp,
    [property: JsonPropertyName("version")] int Version,
    [property: JsonPropertyName("merkle_root")] string MerkleRoot,
    [property: JsonPropertyName("nonce")] long Nonce,
    [property: JsonPropertyName("difficulty")] double Difficulty
);

/// <summary>
/// Script public key.
/// </summary>
public record ScriptPubKey(
    [property: JsonPropertyName("asm")] string Asm,
    [property: JsonPropertyName("hex")] string Hex,
    [property: JsonPropertyName("type")] string Type,
    [property: JsonPropertyName("address")] string? Address = null
);

/// <summary>
/// Transaction input.
/// </summary>
public record RpcTransactionInput(
    [property: JsonPropertyName("txid")] string Txid,
    [property: JsonPropertyName("vout")] int Vout,
    [property: JsonPropertyName("script_sig")] string? ScriptSig,
    [property: JsonPropertyName("sequence")] long Sequence,
    [property: JsonPropertyName("witness")] List<string>? Witness = null
);

/// <summary>
/// Transaction output.
/// </summary>
public record RpcTransactionOutput(
    [property: JsonPropertyName("value")] long Value,
    [property: JsonPropertyName("n")] int N,
    [property: JsonPropertyName("script_pubkey")] ScriptPubKey ScriptPubKey
);

/// <summary>
/// A blockchain transaction.
/// </summary>
public record RpcTransaction(
    [property: JsonPropertyName("txid")] string Txid,
    [property: JsonPropertyName("hash")] string Hash,
    [property: JsonPropertyName("version")] int Version,
    [property: JsonPropertyName("size")] int Size,
    [property: JsonPropertyName("weight")] int Weight,
    [property: JsonPropertyName("lock_time")] long LockTime,
    [property: JsonPropertyName("inputs")] List<RpcTransactionInput> Inputs,
    [property: JsonPropertyName("outputs")] List<RpcTransactionOutput> Outputs,
    [property: JsonPropertyName("fee")] long Fee,
    [property: JsonPropertyName("confirmations")] int Confirmations,
    [property: JsonPropertyName("block_hash")] string? BlockHash = null,
    [property: JsonPropertyName("block_height")] long? BlockHeight = null,
    [property: JsonPropertyName("timestamp")] long? Timestamp = null,
    [property: JsonPropertyName("status")] TransactionStatus Status = TransactionStatus.Pending
);

/// <summary>
/// A blockchain block.
/// </summary>
public record Block(
    [property: JsonPropertyName("hash")] string Hash,
    [property: JsonPropertyName("height")] long Height,
    [property: JsonPropertyName("previous_hash")] string PreviousHash,
    [property: JsonPropertyName("timestamp")] long Timestamp,
    [property: JsonPropertyName("version")] int Version,
    [property: JsonPropertyName("merkle_root")] string MerkleRoot,
    [property: JsonPropertyName("nonce")] long Nonce,
    [property: JsonPropertyName("difficulty")] double Difficulty,
    [property: JsonPropertyName("transactions")] List<RpcTransaction> Transactions,
    [property: JsonPropertyName("size")] int Size,
    [property: JsonPropertyName("weight")] int Weight
);

/// <summary>
/// Chain information.
/// </summary>
public record ChainInfo(
    [property: JsonPropertyName("chain")] string Chain,
    [property: JsonPropertyName("blocks")] long Blocks,
    [property: JsonPropertyName("headers")] long Headers,
    [property: JsonPropertyName("best_block_hash")] string BestBlockHash,
    [property: JsonPropertyName("difficulty")] double Difficulty,
    [property: JsonPropertyName("median_time")] long MedianTime,
    [property: JsonPropertyName("verification_progress")] double VerificationProgress,
    [property: JsonPropertyName("chain_work")] string ChainWork,
    [property: JsonPropertyName("pruned")] bool Pruned
);

/// <summary>
/// Mempool information.
/// </summary>
public record MempoolInfo(
    [property: JsonPropertyName("size")] int Size,
    [property: JsonPropertyName("bytes")] long Bytes,
    [property: JsonPropertyName("usage")] long Usage,
    [property: JsonPropertyName("max_mempool")] long MaxMempool,
    [property: JsonPropertyName("mempool_min_fee")] double MempoolMinFee,
    [property: JsonPropertyName("min_relay_tx_fee")] double MinRelayTxFee
);

/// <summary>
/// Fee estimate.
/// </summary>
public record FeeEstimate(
    [property: JsonPropertyName("priority")] RpcPriority Priority,
    [property: JsonPropertyName("fee_rate")] long FeeRate,
    [property: JsonPropertyName("estimated_blocks")] int EstimatedBlocks
);

/// <summary>
/// Submit result.
/// </summary>
public record SubmitResult(
    [property: JsonPropertyName("txid")] string Txid,
    [property: JsonPropertyName("accepted")] bool Accepted,
    [property: JsonPropertyName("reason")] string? Reason = null
);

/// <summary>
/// Subscription handle.
/// </summary>
public class Subscription : IDisposable
{
    public string Id { get; }
    public string Channel { get; }
    private readonly Action _onCancel;
    private bool _disposed;

    public Subscription(string id, string channel, Action onCancel)
    {
        Id = id;
        Channel = channel;
        _onCancel = onCancel;
    }

    public void Cancel()
    {
        if (!_disposed)
        {
            _onCancel();
            _disposed = true;
        }
    }

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

/// <summary>
/// RPC exception.
/// </summary>
public class RpcException : Exception
{
    public string? Code { get; }
    public int? StatusCode { get; }

    public RpcException(string message, string? code = null, int? statusCode = null)
        : base(message)
    {
        Code = code;
        StatusCode = statusCode;
    }
}
