require_relative '../spec_helper.rb'

include Bitcoin
include Builder

describe 'Node Command API' do

  def test_command command, params = [], response = nil, &block
    $responses = {}
    EM.run do
      @client = Bitcoin::Network::CommandClient.connect(*@config[:command]) do
        on_connected do
          request(command, *params)
        end
        on_response do |cmd, data|
          $responses[cmd] = data
          EM.stop
        end
      end
    end

    result = $responses[command]

    return result  unless response || block

    if block
      block.call(result)
    else
      result.should == response
    end
  end


  before do
    Bitcoin::Validation::Block::RULES.merge({
      syntax: [:hash, :tx_list, :bits, :max_timestamp, :coinbase, :coinbase_scriptsig, :transactions_syntax],
      context: [:prev_hash, :coinbase_value, :min_timestamp, :transactions_context]
    })

    Bitcoin.network = :spec
    @config = {
      listen: ["127.0.0.1", 38333],
      command: ["127.0.0.1", 38332],
      storage: "sequel::sqlite:/",
      dns: false,
      intervals: { queue: 0.01 },
      log: { network: :warn, storage: :warn },
    }

    @node = Bitcoin::Network::Node.new(@config)
    @pid = fork do
      $stdout = StringIO.new
      SimpleCov.running = false if defined?(SimpleCov)
      @node.run
    end

    @genesis = P::Block.new("0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000".htb)

    Bitcoin.network[:proof_of_work_limit] = Bitcoin.encode_compact_bits("ff"*32)
    @key = Bitcoin::Key.generate
    @block = create_block @genesis.hash, false, [], @key

    test_command "store_block", [@genesis.to_payload.hth]
    sleep 0.1
  end

  after do
    Process.kill("TERM", @pid)
  end

  it "should return error for unknown command" do
    test_command("foo").should == {"error" => "unknown command: foo. send 'help' for help."}
  end

  it "should return error for wrong parameters" do
    test_command("info", "foo").should == {"error" => "wrong number of arguments (1 for 0)"}
  end

  it "should query tslb" do
    test_command("tslb") do |res|
      res.keys.include?("tslb").should == true
      res["tslb"].should >= 0
      res["tslb"].should <= 1
    end
  end

  it "should query info" do
    info = test_command "info"
    info.is_a?(Hash).should == true
    info["blocks"].should == "0 (?)"
    info["addrs"].should == "0 (0)"
    info["connections"].should == "0 established (0 out, 0 in), 0 connecting"
    info["queue"].should == 0
    info["inv_queue"].should == 0
    info["inv_cache"].should == 0
    info["network"].should == "bitcoin"
    info["storage"].should == "sequel::sqlite:/"
    info["version"].should == 70001
    info["external_ip"].should == "127.0.0.1"
    info["uptime"].should =~ /00:00:00:0[0|1]/
  end

  it "should query config" do
    test_command("config").should == JSON.load(@node.config.to_json)
  end

  # TODO
  it "should query connections" do
    test_command("connections").should == []
  end

  # TODO
  it "should connect" do
    test_command("connect", ["127.0.0.1:1234"])["state"].should == "Connecting..."
  end

  # TODO
  it "should disconnect" do
    test_command("disconnect", ["127.0.0.1:1234"])["state"].should == "Disconnected"
  end

  it "should store block" do
    test_command("info")["blocks"].should == "0 (?)"
    res = test_command "store_block", [ @block.to_payload.hth ]
    res.should == { "queued" => [ "block", @block.hash ] }
    sleep 0.1
    test_command("info")["blocks"].should == "1 (?) sync"
  end

  describe :create_tx do

    before do
      @key2 = Key.generate
      test_command("store_block", [@block.to_payload.hth])
      sleep 0.1
    end

    it "should create transaction from given private keys" do
      res = test_command("create_tx", [[@key.to_base58], [[@key2.addr, 10e8], [@key.addr, 40e8]]])
      tx = P::Tx.new(res[0].htb)
      tx.is_a?(P::Tx).should == true
      tx.verify_input_signature(0, @block.tx[0]).should == true
    end

    it "should create transaction from given addresses" do
      res = test_command("create_tx", [[@key.addr], [[@key2.addr, 10e8], [@key.addr, 40e8]]])
      tx = P::Tx.new(res[0].htb)
      tx.is_a?(P::Tx).should == true
      tx.in[0].script_sig.should == ""
      -> { tx.verify_input_signature(0, @block.tx[0]) }.should.raise(TypeError)

      res[1].each.with_index do |sig_data, idx|
        sig_hash, sig_addr = *sig_data
        sig_addr.should == @key.addr
        sig = @key.sign(sig_hash.htb)
        script_sig = Script.to_signature_pubkey_script(sig, @key.pub.htb)
        tx.in[idx].script_sig_length = script_sig.bytesize
        tx.in[idx].script_sig = script_sig
      end

      tx.verify_input_signature(0, @block.tx[0]).should == true
    end

    it "should create transaction from given pubkeys" do
      res = test_command("create_tx", [[@key.pub], [[@key2.addr, 10e8], [@key.addr, 40e8]]])
      tx = P::Tx.new(res[0].htb)
      tx.is_a?(P::Tx).should == true
      -> { tx.verify_input_signature(0, @block.tx[0]) }.should.raise(TypeError)

      res[1].each.with_index do |sig_data, idx|
        sig_hash, sig_addr = *sig_data
        sig_addr.should == @key.addr
        sig = @key.sign(sig_hash.htb)
        script_sig = Script.to_signature_pubkey_script(sig, @key.pub.htb)
        tx.in[idx].script_sig_length = script_sig.bytesize
        tx.in[idx].script_sig = script_sig
      end

      tx.verify_input_signature(0, @block.tx[0]).should == true
    end

  end

  describe :assemble_tx do

    it "should assemble tx from unsigned tx structure, signatures and pubkeys" do
      tx = build_tx do |t|
        t.input do |i|
          i.prev_out @block.tx[0]
          i.prev_out_index 0
        end
        t.output {|o| o.value 50e8; o.script {|s| s.recipient @key.addr } }
      end
      sig = @key.sign(tx.in[0].sig_hash)
      test_command("store_block", [@block.to_payload.hth])
      sleep 0.1
      res = test_command("assemble_tx", [tx.to_payload.hth, [[sig.hth, @key.pub]]])
      tx = Bitcoin::P::Tx.new(res.htb)
      tx.verify_input_signature(0, @block.tx[0]).should == true
    end

  end

  describe :relay_tx do

    it "should handle decoding error" do
      res = test_command("relay_tx", ["foobar"])
      res["error"].should == "Error decoding transaction."
    end

    it "should handle syntax error" do
      # create transaction with invalid output size
      block = create_block(@block.hash, false, [->(t) {
        create_tx(t, @block.tx[0], 0, [[22e14, @key]]) }], @key)
      tx = block.tx[1]

      error = test_command("relay_tx", [tx.to_payload.hth])
      error["error"].should == "Transaction syntax invalid."
      error["details"].should == ["output_values", [22e14, 21e14]]
    end

    it "should handle context error" do
      # create transaction with invalid input
      block = create_block(@block.hash, false, [->(t) {
        create_tx(t, @block.tx[0], 0, [[25e8, @key]]) }], @key)
      tx = block.tx[1]

      error = test_command("relay_tx", [tx.to_payload.hth])
      error["error"].should == "Transaction context invalid."
      error["details"].should == ["prev_out", [[@block.tx[0].hash, 0]]]
    end

    it "should relay transaction" do
      block = create_block(@block.hash, false, [->(t) {
        create_tx(t, @block.tx[0], 0, [[25e8, @key]]) }], @key)
      tx = block.tx[1]

      test_command("store_block", [@block.to_payload.hth])
      sleep 0.1
      res = test_command("relay_tx", [tx.to_payload.hth, 1, 0])
      res["success"].should == true
      res["hash"].should == tx.hash
      res["propagation"].should == { "sent" => 1, "received" => 0, "percent" => 0.0 }
    end

  end

  describe :monitor do

    before do
      @client = TCPSocket.new(*@config[:command])

      def send data
        @client.write(data.to_json + "\x00")
      end

      def should_receive expected
        buf = ""
        while b = @client.read(1)
          break  if b == "\x00"
          buf << b
        end
        resp = JSON.load(buf)
        resp.should == expected
      end

      def store_block block
        send ["store_block", [ block.to_payload.hth ]]
        should_receive ["store_block", {"queued" => [ "block", block.hash ]}]
      end

    end

    describe :block do

      before do
        send ["monitor", ["block"]]
        should_receive ["monitor", ["block", [ @genesis.to_hash, 0 ]]]
        store_block @block
        should_receive ["monitor", ["block", [ @block.to_hash, 1 ]]]
      end

      it "should monitor block" do
        @block = create_block @block.hash, false
        store_block @block
        should_receive ["monitor", ["block", [ @block.to_hash, 2 ]]]
      end


      it "should not monitor side or orphan blocks" do
        @side = create_block @genesis.hash, false
        store_block @side

        @orphan = create_block "00" * 32, false
        store_block @orphan

        # should not send side or orphan block only the next main block
        @block = create_block @block.hash, false
        store_block @block
        should_receive ["monitor", ["block", [ @block.to_hash, 2 ]]]
      end

    end

    describe :tx do

      it "should monitor unconfirmed tx" do
        send ["monitor", ["tx"]]
        tx = @block.tx[0]
        send ["store_tx", [ tx.to_payload.hth ] ]
        should_receive ["store_tx", { "queued" => [ "tx", tx.hash ]}]
        should_receive ["monitor", ["tx", [ tx.to_hash, 0 ]]]
      end

      it "should monitor confirmed tx" do
        send ["monitor", ["tx_1"]]
        store_block @block
        should_receive ["monitor", ["tx_1", [ @block.tx[0].to_hash, 1 ]]]
      end

      it "should monitor tx for given confirmation level" do
        send ["monitor", ["tx_3"]]
        @tx = @block.tx[0]
        store_block @block
        @block = create_block @block.hash, false
        store_block @block
        should_receive ["monitor", ["tx_3", [ @genesis.tx[0].to_hash, 3 ]]]
        @block = create_block @block.hash, false
        store_block @block
        should_receive ["monitor", ["tx_3", [ @tx.to_hash, 3 ]]]
      end

    end

    describe :output do

      before do
        @tx = @block.tx[0]; @out = @tx.out[0]
        @addr = Bitcoin::Script.new(@out.pk_script).get_address
      end

      it "should monitor unconfirmed outputs" do
        send ["monitor", ["output"]]
        tx = @block.tx[0]
        send ["store_tx", [ tx.to_payload.hth ]]
        should_receive ["store_tx", { "queued" => [ "tx", tx.hash ]}]
        addr = Bitcoin::Script.new(tx.out[0].pk_script).get_address
        should_receive ["monitor", ["output", [ tx.hash, addr, tx.out[0].value, 0]]]
      end

      it "should monitor confirmed output" do
        send ["monitor", ["output_1"]]
        store_block @block
        should_receive ["monitor", ["output_1", [ @tx.hash, @addr, @out.value, 1 ]]]
      end

      it "should monitor output for given confirmation level" do
        send ["monitor", ["output_3"]]
        store_block @block
        @block = create_block @block.hash, false
        store_block @block
        tx = @genesis.tx[0]; out = tx.out[0]
        addr = Bitcoin::Script.new(out.pk_script).get_address
        should_receive ["monitor", ["output_3", [ tx.hash, addr, out.value, 3 ]]]

        @block = create_block @block.hash, false
        store_block @block
        should_receive ["monitor", ["output_3", [ @tx.hash, @addr, @out.value, 3 ]]]
        
      end

    end

  end

end
