// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_bluetooth_sapphire/internal/host/hci/sequential_command_runner.h"

#include "pw_bluetooth_sapphire/internal/host/common/log.h"
#include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
#include "pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
#include "pw_bluetooth_sapphire/internal/host/testing/mock_controller.h"
#include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
#include "pw_bluetooth_sapphire/internal/host/testing/test_packets.h"

namespace bt::hci {
namespace {

constexpr hci_spec::OpCode kTestOpCode = 0xFFFF;
constexpr hci_spec::OpCode kTestOpCode2 = 0xF00F;

using bt::testing::CommandTransaction;

using TestingBase =
    bt::testing::FakeDispatcherControllerTest<bt::testing::MockController>;

class SequentialCommandRunnerTest : public TestingBase {
 public:
  SequentialCommandRunnerTest() = default;
  ~SequentialCommandRunnerTest() override = default;
};

using HCI_SequentialCommandRunnerTest = SequentialCommandRunnerTest;

TEST_F(SequentialCommandRunnerTest, SequentialCommandRunner) {
  // HCI command with custom opcode FFFF.
  StaticByteBuffer command_bytes(0xFF, 0xFF, 0x00);

  StaticByteBuffer command_status_error_bytes(
      hci_spec::kCommandStatusEventCode,
      0x04,  // parameter_total_size (4 byte payload)
      pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE,
      1,
      0xFF,
      0xFF);

  StaticByteBuffer command_cmpl_error_bytes(
      hci_spec::kCommandCompleteEventCode,
      0x04,  // parameter_total_size (4 byte payload)
      1,
      0xFF,
      0xFF,
      pw::bluetooth::emboss::StatusCode::RESERVED_0);

  auto command_cmpl_success_bytes =
      StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
                       0x04,  // parameter_total_size (4 byte payload)
                       1,
                       0xFF,
                       0xFF,
                       pw::bluetooth::emboss::StatusCode::SUCCESS);

  // Here we perform multiple test sequences where we queue up several  commands
  // in each sequence. We expect each sequence to terminate differently after
  // the following HCI transactions:
  //
  // Sequence 1 (HCI packets)
  //    -> Command; <- error status
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_status_error_bytes);

  // Sequence 2 (HCI packets)
  //    -> Command; <- error complete
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_error_bytes);

  // Sequence 3 (HCI packets)
  //    -> Command; <- success complete
  //    -> Command; <- error complete
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_error_bytes);

  // Sequence 4 (HCI packets)
  //    -> Command; <- success complete
  //    -> Command; <- success complete
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);

  // Sequence 5 (HCI packets)
  //    -> Command; <- success complete
  //    -> Command; <- success complete
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);

  Result<> status = fit::ok();
  int status_cb_called = 0;
  auto status_cb = [&](Result<> cb_status) {
    status = cb_status;
    status_cb_called++;
  };

  int cb_called = 0;
  auto cb = [&](const EventPacket& event) { cb_called++; };

  // Sequence 1 (test)
  SequentialCommandRunner cmd_runner(cmd_channel()->AsWeakPtr());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());

  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
                          cb);  // <-- Should not run

  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());
  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  EXPECT_EQ(1, cb_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE),
            status);
  cb_called = 0;

  // Sequence 2 (test)
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
                          cb);  // <-- Should not run

  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());
  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  EXPECT_EQ(1, cb_called);
  EXPECT_EQ(2, status_cb_called);
  EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::RESERVED_0), status);
  cb_called = 0;

  // Sequence 3 (test)
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
                          cb);  // <-- Should not run

  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());
  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  EXPECT_EQ(2, cb_called);
  EXPECT_EQ(3, status_cb_called);
  EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::RESERVED_0), status);
  cb_called = 0;

  // Sequence 4 (test)
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);

  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());
  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  EXPECT_EQ(2, cb_called);
  EXPECT_EQ(4, status_cb_called);
  EXPECT_EQ(fit::ok(), status);
  cb_called = 0;
  status_cb_called = 0;

  // Sequence 5 (test) (no callback passed to QueueCommand)
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode));
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode));

  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());
  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  EXPECT_EQ(0, cb_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(fit::ok(), status);
}

TEST_F(SequentialCommandRunnerTest, SequentialCommandRunnerCancel) {
  StaticByteBuffer command_bytes(0xFF, 0xFF, 0x00);

  auto command_cmpl_error_bytes =
      StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
                       0x04,  // parameter_total_size (4 byte payload)
                       1,
                       0xFF,
                       0xFF,
                       pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE);

  auto command_cmpl_success_bytes =
      StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
                       0x04,  // parameter_total_size (4 byte payload)
                       1,
                       0xFF,
                       0xFF,
                       pw::bluetooth::emboss::StatusCode::SUCCESS);

  // Sequence 1
  //   -> Command; <- success complete
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);

  // Sequence 2
  //   -> Command; <- success complete
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);

  // Sequence 3
  //   -> Command; <- success complete
  //   -> Command; <- error complete
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_error_bytes);

  Result<> status = fit::ok();
  int status_cb_called = 0;
  auto status_cb = [&](Result<> cb_status) {
    status = cb_status;
    status_cb_called++;
  };

  int cb_called = 0;
  auto cb = [&](const EventPacket& event) { cb_called++; };

  // Sequence 1: Sequence will be cancelled after the first command.
  SequentialCommandRunner cmd_runner(cmd_channel()->AsWeakPtr());
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  // Call RunCommands() and cancel the sequence immediately. The
  // first command will go out but no successive packets should be sent.
  // The status callback should be invoked
  // No command callbacks should be called.
  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());
  cmd_runner.Cancel();

  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  EXPECT_EQ(0, cb_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(ToResult(HostError::kCanceled), status);
  cb_called = 0;
  status_cb_called = 0;
  status = fit::ok();

  // Sequence 2: Sequence will be cancelled after first command. This tests
  // canceling a sequence from a CommandCompleteCallback.
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
                          [&](const EventPacket& event) {
                            bt_log(TRACE, "hci-test", "callback called");
                            cmd_runner.Cancel();
                            EXPECT_TRUE(cmd_runner.IsReady());
                            EXPECT_FALSE(cmd_runner.HasQueuedCommands());
                          });
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
                          cb);  // <-- Should not run
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());

  // |status_cb| is expected to get called with kCanceled
  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());

  EXPECT_EQ(0, cb_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(ToResult(HostError::kCanceled), status);
  cb_called = 0;
  status_cb_called = 0;
  status = fit::ok();

  // Sequence 3: Sequence will be cancelled after first command and immediately
  // followed by a second command which will fail. This tests canceling a
  // sequence and initiating a new one from a CommandCompleteCallback.
  cmd_runner.QueueCommand(
      CommandPacket::New(kTestOpCode), [&](const EventPacket& event) {
        cmd_runner.Cancel();
        EXPECT_TRUE(cmd_runner.IsReady());
        EXPECT_FALSE(cmd_runner.HasQueuedCommands());

        EXPECT_EQ(1, status_cb_called);
        EXPECT_EQ(ToResult(HostError::kCanceled), status);

        // Queue multiple commands (only one will execute since MockController
        // will send back an error status).
        cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb);
        cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
                                cb);  // <-- Should not run
        cmd_runner.RunCommands(status_cb);
      });
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
                          cb);  // <-- Should not run
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());

  RunUntilIdle();

  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());

  // The cb queued from inside the first callback should have been called.
  EXPECT_EQ(1, cb_called);
  // The result callback should have been called with the failure result.
  EXPECT_EQ(2, status_cb_called);
  EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE),
            status);
}

TEST_F(SequentialCommandRunnerTest, ParallelCommands) {
  // Need to signal to the queue that we can run more than one command at once.
  auto command_status_queue_increase =
      StaticByteBuffer(hci_spec::kCommandStatusEventCode,
                       0x04,  // parameter_total_size (4 byte payload)
                       pw::bluetooth::emboss::StatusCode::SUCCESS,
                       250,
                       0x00,
                       0x00);
  // HCI command with custom opcode FFFF.
  StaticByteBuffer command_bytes(0xFF, 0xFF, 0x00);
  auto command_status_error_bytes =
      StaticByteBuffer(hci_spec::kCommandStatusEventCode,
                       0x04,  // parameter_total_size (4 byte payload)
                       pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE,
                       2,
                       0xFF,
                       0xFF);

  auto command_cmpl_error_bytes =
      StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
                       0x04,  // parameter_total_size (4 byte payload)
                       2,
                       0xFF,
                       0xFF,
                       pw::bluetooth::emboss::StatusCode::RESERVED_0);

  auto command_cmpl_success_bytes =
      StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
                       0x04,  // parameter_total_size (4 byte payload)
                       2,
                       0xFF,
                       0xFF,
                       pw::bluetooth::emboss::StatusCode::SUCCESS);

  // HCI command with custom opcode F00F.
  StaticByteBuffer command2_bytes(0x0F, 0xF0, 0x00);
  auto command2_status_error_bytes =
      StaticByteBuffer(hci_spec::kCommandStatusEventCode,
                       0x04,  // parameter_total_size (4 byte payload)
                       pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE,
                       2,
                       0x0F,
                       0xF0);

  auto command2_cmpl_error_bytes =
      StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
                       0x04,  // parameter_total_size (4 byte payload)
                       2,
                       0x0F,
                       0xF0,
                       pw::bluetooth::emboss::StatusCode::RESERVED_0);

  auto command2_cmpl_success_bytes =
      StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
                       0x04,  // parameter_total_size (4 byte payload)
                       2,
                       0x0F,
                       0xF0,
                       pw::bluetooth::emboss::StatusCode::SUCCESS);

  test_device()->SendCommandChannelPacket(command_status_queue_increase);

  // Parallel commands should all run before commands that require success.
  // command and command2 are answered in opposite order because they should be
  // sent simultaneously.
  EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, );
  EXPECT_CMD_PACKET_OUT(test_device(), command2_bytes, );
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);
  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);

  int cb_called = 0;
  auto cb = [&](const hci::EventPacket&) { cb_called++; };

  int status_cb_called = 0;
  Result<> status = ToResult(HostError::kFailed);
  auto status_cb = [&](Result<> cb_status) {
    status = cb_status;
    status_cb_called++;
  };

  SequentialCommandRunner cmd_runner(cmd_channel()->AsWeakPtr());

  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb, /*wait=*/false);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode2), cb, /*wait=*/false);
  cmd_runner.QueueCommand(
      CommandPacket::New(kTestOpCode),
      [&](const hci::EventPacket&) {
        EXPECT_EQ(2, cb_called);
        cb_called++;
      },
      /*wait=*/true);
  // We can also queue to the end of the queue without the last one being a
  // wait.
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb, /*wait=*/false);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), cb, /*wait=*/false);
  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());

  RunUntilIdle();
  // The first two commands should have been sent but no responses are back yet.
  EXPECT_EQ(0, cb_called);

  // It should not matter if they get answered in opposite order.
  test_device()->SendCommandChannelPacket(command2_cmpl_success_bytes);
  test_device()->SendCommandChannelPacket(command_cmpl_success_bytes);
  RunUntilIdle();

  EXPECT_EQ(5, cb_called);
  EXPECT_EQ(fit::ok(), status);
  EXPECT_EQ(1, status_cb_called);
  cb_called = 0;
  status_cb_called = 0;

  // If any simultaneous commands fail, the sequence fails and the command
  // sequence is terminated.
  EXPECT_CMD_PACKET_OUT(test_device(), command_bytes, );
  EXPECT_CMD_PACKET_OUT(test_device(), command2_bytes, );

  int cb_0_called = 0;
  auto cb_0 = [&](const hci::EventPacket&) { cb_0_called++; };
  int cb_1_called = 0;
  auto cb_1 = [&](const hci::EventPacket&) { cb_1_called++; };
  int cb_2_called = 0;
  auto cb_2 = [&](const hci::EventPacket&) { cb_2_called++; };
  cmd_runner.QueueCommand(
      CommandPacket::New(kTestOpCode), cb_0, /*wait=*/false);
  cmd_runner.QueueCommand(
      CommandPacket::New(kTestOpCode2), cb_1, /*wait=*/false);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
                          cb_2);  // shouldn't run

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());

  RunUntilIdle();
  // The first two commands should have been sent but no responses are back yet.
  EXPECT_EQ(0, cb_0_called);
  EXPECT_EQ(0, cb_1_called);
  EXPECT_EQ(0, cb_2_called);

  test_device()->SendCommandChannelPacket(command_status_error_bytes);
  test_device()->SendCommandChannelPacket(command2_cmpl_success_bytes);
  RunUntilIdle();

  // Only the first command's callback should be called, as further callbacks
  // will be canceled due to the error status.
  EXPECT_EQ(1, cb_0_called);
  EXPECT_EQ(0, cb_1_called);
  EXPECT_EQ(0, cb_2_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE),
            status);
}

TEST_F(SequentialCommandRunnerTest, CommandCompletesOnStatusEvent) {
  auto command = bt::testing::EmptyCommandPacket(kTestOpCode);
  auto command0_status_event = bt::testing::CommandStatusPacket(
      kTestOpCode, pw::bluetooth::emboss::StatusCode::SUCCESS);

  auto command1 = bt::testing::EmptyCommandPacket(kTestOpCode2);
  auto command1_cmpl_event = bt::testing::CommandCompletePacket(
      kTestOpCode2, pw::bluetooth::emboss::StatusCode::SUCCESS);

  Result<> status = fit::ok();
  int status_cb_called = 0;
  auto status_cb = [&](Result<> cb_status) {
    status = cb_status;
    status_cb_called++;
  };

  int cb_called = 0;
  auto cb = [&](const EventPacket& event) { cb_called++; };

  SequentialCommandRunner cmd_runner(cmd_channel()->AsWeakPtr());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());

  EXPECT_CMD_PACKET_OUT(test_device(), command, &command0_status_event);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode),
                          cb,
                          /*wait=*/false,
                          hci_spec::kCommandStatusEventCode);

  EXPECT_CMD_PACKET_OUT(test_device(), command1, &command1_cmpl_event);
  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode2), cb, /*wait=*/true);

  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());
  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  EXPECT_EQ(2, cb_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(fit::ok(), status);
}

TEST_F(SequentialCommandRunnerTest, AsyncCommands) {
  auto command = bt::testing::EmptyCommandPacket(hci_spec::kRemoteNameRequest);
  auto command0_status_event = bt::testing::CommandStatusPacket(
      hci_spec::kRemoteNameRequest, pw::bluetooth::emboss::StatusCode::SUCCESS);
  auto command0_cmpl_event =
      bt::testing::RemoteNameRequestCompletePacket(DeviceAddress());

  auto command1 =
      bt::testing::EmptyCommandPacket(hci_spec::kLEReadRemoteFeatures);
  auto command1_status_event = bt::testing::CommandStatusPacket(
      hci_spec::kLEReadRemoteFeatures,
      pw::bluetooth::emboss::StatusCode::SUCCESS);
  auto command1_cmpl_event = bt::testing::LEReadRemoteFeaturesCompletePacket(
      /*conn=*/0x0000, hci_spec::LESupportedFeatures{0});

  auto command2 =
      bt::testing::EmptyCommandPacket(hci_spec::kReadRemoteVersionInfo);
  auto command2_status_event = bt::testing::CommandStatusPacket(
      hci_spec::kReadRemoteVersionInfo,
      pw::bluetooth::emboss::StatusCode::SUCCESS);
  auto command2_cmpl_event =
      bt::testing::ReadRemoteVersionInfoCompletePacket(/*conn=*/0x0000);

  Result<> status = fit::ok();
  int status_cb_called = 0;
  auto status_cb = [&](Result<> cb_status) {
    status = cb_status;
    status_cb_called++;
  };

  int cb_called = 0;
  auto cb = [&](const EventPacket& event) { cb_called++; };

  SequentialCommandRunner cmd_runner(cmd_channel()->AsWeakPtr());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());

  EXPECT_CMD_PACKET_OUT(test_device(), command, &command0_status_event);
  cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kRemoteNameRequest),
                          cb,
                          /*wait=*/false,
                          hci_spec::kRemoteNameRequestCompleteEventCode);

  EXPECT_CMD_PACKET_OUT(test_device(), command1, &command1_status_event);
  cmd_runner.QueueLeAsyncCommand(
      EmbossCommandPacket::New<pw::bluetooth::emboss::CommandHeaderView>(
          hci_spec::kLEReadRemoteFeatures),
      hci_spec::kLEReadRemoteFeaturesCompleteSubeventCode,
      cb,
      /*wait=*/false);

  cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kReadRemoteVersionInfo),
                          cb,
                          /*wait=*/true,
                          hci_spec::kReadRemoteVersionInfoCompleteEventCode);

  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());

  RunUntilIdle();
  // Command 2 should wait on command 0 & command 1 complete events.
  EXPECT_FALSE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());
  // Completing the commands out of order shouldn't matter.
  test_device()->SendCommandChannelPacket(command1_cmpl_event);
  test_device()->SendCommandChannelPacket(command0_cmpl_event);

  EXPECT_CMD_PACKET_OUT(
      test_device(), command2, &command2_status_event, &command2_cmpl_event);
  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  EXPECT_EQ(3, cb_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(fit::ok(), status);
}

TEST_F(SequentialCommandRunnerTest, ExclusiveAsyncCommands) {
  auto command = bt::testing::EmptyCommandPacket(hci_spec::kRemoteNameRequest);
  auto command0_status_event = bt::testing::CommandStatusPacket(
      hci_spec::kRemoteNameRequest, pw::bluetooth::emboss::StatusCode::SUCCESS);
  auto command0_cmpl_event =
      bt::testing::RemoteNameRequestCompletePacket(DeviceAddress());

  auto command1 =
      bt::testing::EmptyCommandPacket(hci_spec::kReadRemoteVersionInfo);
  auto command1_status_event = bt::testing::CommandStatusPacket(
      hci_spec::kReadRemoteVersionInfo,
      pw::bluetooth::emboss::StatusCode::SUCCESS);
  auto command1_cmpl_event =
      bt::testing::ReadRemoteVersionInfoCompletePacket(/*conn=*/0x0000);

  Result<> status = fit::ok();
  int status_cb_called = 0;
  auto status_cb = [&](Result<> cb_status) {
    status = cb_status;
    status_cb_called++;
  };

  int cb_called = 0;
  auto cb = [&](const EventPacket& event) { cb_called++; };

  SequentialCommandRunner cmd_runner(cmd_channel()->AsWeakPtr());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());

  EXPECT_CMD_PACKET_OUT(test_device(), command, &command0_status_event);
  cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kRemoteNameRequest),
                          cb,
                          /*wait=*/false,
                          hci_spec::kRemoteNameRequestCompleteEventCode);

  // Even though command 1 is not waiting on command 0, it should remain queued
  // due to the exclusion list.
  cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kReadRemoteVersionInfo),
                          cb,
                          /*wait=*/false,
                          hci_spec::kReadRemoteVersionInfoCompleteEventCode,
                          /*exclusions=*/{hci_spec::kRemoteNameRequest});

  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);

  RunUntilIdle();
  EXPECT_FALSE(cmd_runner.IsReady());
  // Command 1 is "sent" but queued in CommandChannel.
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  // Completing command 0 should send command 1.
  test_device()->SendCommandChannelPacket(command0_cmpl_event);

  EXPECT_CMD_PACKET_OUT(
      test_device(), command1, &command1_status_event, &command1_cmpl_event);
  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_EQ(2, cb_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(fit::ok(), status);
}

TEST_F(SequentialCommandRunnerTest,
       CommandRunnerDestroyedBeforeSecondEventCallbackCalled) {
  auto command = bt::testing::EmptyCommandPacket(hci_spec::kRemoteNameRequest);
  auto command0_status_event = bt::testing::CommandStatusPacket(
      hci_spec::kRemoteNameRequest, pw::bluetooth::emboss::StatusCode::SUCCESS);
  auto command0_cmpl_event =
      bt::testing::RemoteNameRequestCompletePacket(DeviceAddress());

  auto command1 =
      bt::testing::EmptyCommandPacket(hci_spec::kLEReadRemoteFeatures);
  auto command1_status_event = bt::testing::CommandStatusPacket(
      hci_spec::kLEReadRemoteFeatures,
      pw::bluetooth::emboss::StatusCode::SUCCESS);
  auto command1_cmpl_event = bt::testing::LEReadRemoteFeaturesCompletePacket(
      /*conn=*/0x0000, hci_spec::LESupportedFeatures{0});

  std::optional<SequentialCommandRunner> cmd_runner;
  cmd_runner.emplace(cmd_channel()->AsWeakPtr());

  Result<> status = fit::ok();
  int status_cb_called = 0;
  auto status_cb = [&](Result<> cb_status) {
    status = cb_status;
    status_cb_called++;
  };

  int cb_called = 0;
  auto cb = [&](const EventPacket& event) {
    if (cb_called == 0) {
      cmd_runner.reset();
    }
    cb_called++;
  };

  EXPECT_FALSE(cmd_runner->HasQueuedCommands());

  EXPECT_CMD_PACKET_OUT(
      test_device(), command, &command0_status_event, &command0_cmpl_event);
  cmd_runner->QueueCommand(CommandPacket::New(hci_spec::kRemoteNameRequest),
                           cb,
                           /*wait=*/false,
                           hci_spec::kRemoteNameRequestCompleteEventCode);

  EXPECT_CMD_PACKET_OUT(test_device(), command1, &command1_status_event);
  cmd_runner->QueueLeAsyncCommand(
      CommandPacket::New(hci_spec::kLEReadRemoteFeatures),
      hci_spec::kLEReadRemoteFeaturesCompleteSubeventCode,
      cb,
      /*wait=*/false);

  EXPECT_TRUE(cmd_runner->IsReady());
  EXPECT_TRUE(cmd_runner->HasQueuedCommands());

  cmd_runner->RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner->IsReady());

  RunUntilIdle();
  EXPECT_FALSE(cmd_runner.has_value());
  EXPECT_EQ(1, cb_called);
  EXPECT_EQ(0, status_cb_called);
}

TEST_F(SequentialCommandRunnerTest,
       SequentialCommandRunnerDestroyedInCancelStatusCallbackDoesNotCrash) {
  std::optional<SequentialCommandRunner> cmd_runner;
  cmd_runner.emplace(cmd_channel()->AsWeakPtr());

  Result<> status = fit::ok();
  int status_cb_called = 0;
  auto status_cb = [&](Result<> cb_status) {
    status = cb_status;
    status_cb_called++;
    cmd_runner.reset();
  };

  int cb_called = 0;
  auto cb = [&](const EventPacket& event) { cb_called++; };

  auto command = bt::testing::EmptyCommandPacket(kTestOpCode);
  cmd_runner->QueueCommand(CommandPacket::New(kTestOpCode), cb);
  EXPECT_CMD_PACKET_OUT(test_device(), command);
  cmd_runner->RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner->IsReady());
  cmd_runner->Cancel();

  RunUntilIdle();
  EXPECT_FALSE(cmd_runner);
  EXPECT_EQ(0, cb_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(ToResult(HostError::kCanceled), status);
}

TEST_F(SequentialCommandRunnerTest, QueueCommandsWhileAlreadyRunning) {
  auto command = bt::testing::EmptyCommandPacket(hci_spec::kRemoteNameRequest);
  auto command0_status_event = bt::testing::CommandStatusPacket(
      hci_spec::kRemoteNameRequest, pw::bluetooth::emboss::StatusCode::SUCCESS);
  auto command0_cmpl_event =
      bt::testing::RemoteNameRequestCompletePacket(DeviceAddress());

  auto command1 =
      bt::testing::EmptyCommandPacket(hci_spec::kLEReadRemoteFeatures);
  auto command1_status_event = bt::testing::CommandStatusPacket(
      hci_spec::kLEReadRemoteFeatures,
      pw::bluetooth::emboss::StatusCode::SUCCESS);
  auto command1_cmpl_event = bt::testing::LEReadRemoteFeaturesCompletePacket(
      /*conn=*/0x0000, hci_spec::LESupportedFeatures{0});

  auto command2 =
      bt::testing::EmptyCommandPacket(hci_spec::kReadRemoteVersionInfo);
  auto command2_status_event = bt::testing::CommandStatusPacket(
      hci_spec::kReadRemoteVersionInfo,
      pw::bluetooth::emboss::StatusCode::SUCCESS);
  auto command2_cmpl_event =
      bt::testing::ReadRemoteVersionInfoCompletePacket(/*conn=*/0x0000);

  SequentialCommandRunner cmd_runner(cmd_channel()->AsWeakPtr());

  Result<> status = fit::ok();
  int status_cb_called = 0;
  auto status_cb = [&](Result<> cb_status) {
    status = cb_status;
    status_cb_called++;
  };

  int cb_called = 0;
  auto cb = [&](const EventPacket& event) { cb_called++; };

  int name_cb_called = 0;
  auto name_request_callback = [&](const EventPacket& event) {
    name_cb_called++;

    EXPECT_FALSE(cmd_runner.IsReady());
    EXPECT_FALSE(cmd_runner.HasQueuedCommands());

    EXPECT_CMD_PACKET_OUT(
        test_device(), command1, &command1_status_event, &command1_cmpl_event);
    cmd_runner.QueueLeAsyncCommand(
        CommandPacket::New(hci_spec::kLEReadRemoteFeatures),
        hci_spec::kLEReadRemoteFeaturesCompleteSubeventCode,
        cb,
        /*wait=*/false);

    EXPECT_CMD_PACKET_OUT(
        test_device(), command2, &command2_status_event, &command2_cmpl_event);
    cmd_runner.QueueCommand(
        CommandPacket::New(hci_spec::kReadRemoteVersionInfo),
        cb,
        /*wait=*/false,
        hci_spec::kReadRemoteVersionInfoCompleteEventCode);
  };

  EXPECT_CMD_PACKET_OUT(
      test_device(), command, &command0_status_event, &command0_cmpl_event);
  cmd_runner.QueueCommand(CommandPacket::New(hci_spec::kRemoteNameRequest),
                          name_request_callback,
                          /*wait=*/false,
                          hci_spec::kRemoteNameRequestCompleteEventCode);
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());

  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  EXPECT_EQ(1, name_cb_called);
  EXPECT_EQ(2, cb_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(fit::ok(), status);
}

TEST_F(SequentialCommandRunnerTest, EmbossEventHandler) {
  SequentialCommandRunner cmd_runner(cmd_channel()->AsWeakPtr());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());

  // HCI command with custom opcode FFFF.
  StaticByteBuffer command_bytes(0xFF, 0xFF, 0x00);
  StaticByteBuffer command_cmpl_success_bytes(
      hci_spec::kCommandCompleteEventCode,
      0x04,  // parameter_total_size (4 byte payload)
      1,
      0xFF,
      0xFF,
      pw::bluetooth::emboss::StatusCode::SUCCESS);

  Result<> status = fit::ok();
  int status_cb_called = 0;
  auto status_cb = [&](Result<> cb_status) {
    status = cb_status;
    status_cb_called++;
  };

  int cb_called = 0;
  SequentialCommandRunner::EmbossCommandCompleteCallback cb =
      [&](const EmbossEventPacket& event) {
        cb_called++;
        EXPECT_THAT(event.data(), BufferEq(command_cmpl_success_bytes));
      };

  cmd_runner.QueueCommand(CommandPacket::New(kTestOpCode), std::move(cb));
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_TRUE(cmd_runner.HasQueuedCommands());

  EXPECT_CMD_PACKET_OUT(
      test_device(), command_bytes, &command_cmpl_success_bytes);
  cmd_runner.RunCommands(status_cb);
  EXPECT_FALSE(cmd_runner.IsReady());
  RunUntilIdle();
  EXPECT_TRUE(cmd_runner.IsReady());
  EXPECT_FALSE(cmd_runner.HasQueuedCommands());
  EXPECT_EQ(1, cb_called);
  EXPECT_EQ(1, status_cb_called);
  EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), status);
}

}  // namespace
}  // namespace bt::hci
