defmodule ETH.Utils do
  def get_private_key, do: :crypto.strong_rand_bytes(32)

  def get_public_key(<<private_key::binary-size(32)>>) do
    {:ok, public_key} = ExSecp256k1.create_public_key(private_key)
    public_key
  end

  def get_public_key(<<encoded_private_key::binary-size(64)>>) do
    private_key = Base.decode16!(encoded_private_key, case: :mixed)
    {:ok, public_key} = ExSecp256k1.create_public_key(private_key)
    public_key
  end

  def get_address(<<private_key::binary-size(32)>>) do
    <<4::size(8), key::binary-size(64)>> = private_key |> get_public_key()
    <<_::binary-size(12), eth_address::binary-size(20)>> = ExKeccak.hash_256(key)
    "0x#{Base.encode16(eth_address)}"
  end

  def get_address(<<encoded_private_key::binary-size(64)>>) do
    public_key = Base.decode16!(encoded_private_key, case: :mixed) |> get_public_key()
    <<4::size(8), key::binary-size(64)>> = public_key
    <<_::binary-size(12), eth_address::binary-size(20)>> = ExKeccak.hash_256(key)
    "0x#{Base.encode16(eth_address)}"
  end

  def get_address(<<4::size(8), key::binary-size(64)>>) do
    <<_::binary-size(12), eth_address::binary-size(20)>> = ExKeccak.hash_256(key)
    "0x#{Base.encode16(eth_address)}"
  end

  def get_address(<<encoded_public_key::binary-size(130)>>) do
    <<4::size(8), key::binary-size(64)>> = Base.decode16!(encoded_public_key, case: :mixed)
    <<_::binary-size(12), eth_address::binary-size(20)>> = ExKeccak.hash_256(key)
    "0x#{Base.encode16(eth_address)}"
  end

  # NOTE: not tested area:
  def convert(number, denomination \\ :ether) do
    denom =
      [
        wei: 1,
        kwei: 1000,
        mwei: 1_000_000,
        gwei: 1_000_000_000,
        shannon: 1_000_000_000,
        nano: 1_000_000_000,
        szabo: 1_000_000_000_000,
        micro: 1_000_000_000_000,
        finney: 1_000_000_000_000_000,
        milli: 1_000_000_000_000_000,
        ether: 1_000_000_000_000_000_000
      ]
      |> List.keyfind(denomination, 0)
      |> elem(1)

    number / denom
  end

  def secp256k1_signature(hash, private_key) do
    {:ok, {signature, recovery}} = ExSecp256k1.sign_compact(hash, private_key)

    [signature: signature, recovery: recovery]
  end

  def encode16(value), do: Base.encode16(value, case: :lower)
  def decode16(value), do: Base.decode16!(value, case: :mixed)

  def to_buffer(nil), do: ""
  def to_buffer(0), do: ""

  def to_buffer(data) when is_number(data) do
    data
    |> Integer.to_string(16)
    |> pad_to_even
    |> Base.decode16!(case: :mixed)
  end

  def to_buffer("0x00"), do: ""

  def to_buffer("0x" <> data) do
    padded_data = pad_to_even(data)

    case Base.decode16(padded_data, case: :mixed) do
      {:ok, decoded_binary} -> decoded_binary
      _ -> data
    end
  end

  def to_buffer(data), do: data
  # NOTE: to_buffer else if (v === null || v === undefined) { v = Buffer.allocUnsafe(0) }

  # defp buffer_to_int(""), do: 0
  def buffer_to_int(data) do
    <<number>> = to_buffer(data)
    number
  end

  def pad_to_even(data) do
    if rem(String.length(data), 2) == 1, do: "0#{data}", else: data
  end

  def get_chain_id(v, chain_id \\ nil) do
    computed_chain_id = compute_chain_id(v)
    if computed_chain_id == 0, do: chain_id || 0, else: computed_chain_id
  end

  defp compute_chain_id("0x" <> v) do
    sig_v = buffer_to_int(v)
    chain_id = Float.floor((sig_v - 35) / 2)
    if chain_id < 0, do: 0, else: Kernel.trunc(chain_id)
  end

  defp compute_chain_id(v) do
    sig_v = buffer_to_int(v)
    chain_id = Float.floor((sig_v - 35) / 2)
    if chain_id < 0, do: 0, else: Kernel.trunc(chain_id)
  end

  # defp buffer_to_json_value(buffer) do
  #   "0x" <> Base.encode16(buffer, case: :mixed)
  # end
end

# NOTE: old version that is error-prone:
# {public_key, ^private_key} = :crypto.generate_key(:ecdh, :secp256k1, private_key)
