// Copyright 2025 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 <cstdint>

#include "pw_bluetooth/att.emb.h"
#include "pw_bluetooth/hci_data.emb.h"
#include "pw_bluetooth/hci_h4.emb.h"
#include "pw_bluetooth/l2cap_frames.emb.h"
#include "pw_bluetooth_proxy/h4_packet.h"
#include "pw_bluetooth_proxy/l2cap_channel_common.h"
#include "pw_bluetooth_proxy/proxy_host.h"
#include "pw_bluetooth_proxy_private/test_utils.h"
#include "pw_function/function.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
#include "pw_status/try.h"
#include "pw_unit_test/framework.h"
#include "pw_unit_test/status_macros.h"

namespace pw::bluetooth::proxy {

namespace {
struct AttNotifyWithStorage {
  BFrameWithStorage bframe;
  emboss::AttHandleValueNtfWriter writer;
};

Result<AttNotifyWithStorage> SetupAttNotify(
    uint16_t connection_handle,
    uint16_t attribute_handle,
    const pw::span<const uint8_t> attribute_value) {
  const uint16_t kAttChannelId = 0x0004;
  const size_t att_size =
      emboss::AttHandleValueNtf::MinSizeInBytes() + attribute_value.size();

  AttNotifyWithStorage frame;
  PW_TRY_ASSIGN(frame.bframe,
                SetupBFrame(connection_handle, kAttChannelId, att_size));

  EXPECT_EQ(att_size, frame.bframe.writer.payload().SizeInBytes());
  PW_TRY_ASSIGN(frame.writer,
                MakeEmbossWriter<emboss::AttHandleValueNtfWriter>(
                    attribute_value.size(),
                    frame.bframe.writer.payload().BackingStorage().data(),
                    frame.bframe.writer.payload().SizeInBytes()));
  EXPECT_TRUE(frame.writer.IsComplete());

  frame.writer.attribute_opcode().Write(
      emboss::AttOpcode::ATT_HANDLE_VALUE_NTF);
  frame.writer.attribute_handle().Write(attribute_handle);

  EXPECT_TRUE(
      TryToCopyToEmbossStruct(frame.writer.attribute_value(), attribute_value));

  return frame;
}

class GattNotifyTest : public ProxyHostTest {};

TEST_F(GattNotifyTest, TestSetupAttNotify) {
  std::array<uint8_t, 1> attribute_value = {0xFA};
  PW_TEST_ASSERT_OK_AND_ASSIGN(
      AttNotifyWithStorage att,
      SetupAttNotify(0x0ACB, 0x4321, pw::span(attribute_value)));

  // Built from the preceding values in little endian order.
  std::array<uint8_t, 13> expected_gatt_notify_packet = {0x02,
                                                         0xCB,
                                                         0x0A,
                                                         0x08,
                                                         0x00,
                                                         0x04,
                                                         0x00,
                                                         0x04,
                                                         0x00,
                                                         0x1B,
                                                         0x21,
                                                         0x43,
                                                         0xFA};

  EXPECT_EQ(att.bframe.acl.h4_span().size(),
            expected_gatt_notify_packet.size());
  EXPECT_TRUE(std::equal(att.bframe.acl.h4_span().begin(),
                         att.bframe.acl.h4_span().end(),
                         expected_gatt_notify_packet.begin(),
                         expected_gatt_notify_packet.end()));
}

TEST_F(GattNotifyTest, GetAttributeHandle) {
  pw::Function<void(H4PacketWithHci && packet)>&& send_to_host_fn(
      []([[maybe_unused]] H4PacketWithHci&& packet) {});

  pw::Function<void(H4PacketWithH4 && packet)>&& send_to_controller_fn(
      []([[maybe_unused]] H4PacketWithH4&& packet) {});

  ProxyHost proxy = ProxyHost(std::move(send_to_host_fn),
                              std::move(send_to_controller_fn),
                              /*le_acl_credits_to_reserve=*/0,
                              /*br_edr_acl_credits_to_reserve=*/0);

  GattNotifyChannel channel =
      BuildGattNotifyChannel(proxy, {.attribute_handle = 0x234});
  EXPECT_EQ(channel.attribute_handle(), 0x234);
}

TEST_F(GattNotifyTest, Send1ByteAttribute) {
  struct {
    int sends_called = 0;
    // First four bits 0x0 encode PB & BC flags
    uint16_t handle = 0x0ACB;
    // Length of L2CAP PDU
    uint16_t acl_data_total_length = 0x0008;
    // Length of ATT PDU
    uint16_t pdu_length = 0x0004;
    // Attribute protocol channel ID (0x0004)
    uint16_t channel_id = 0x0004;
    // ATT_HANDLE_VALUE_NTF opcode 0x1B
    uint8_t attribute_opcode = 0x1B;
    uint16_t attribute_handle = 0x4321;
    std::array<uint8_t, 1> attribute_value = {0xFA};
    AttNotifyWithStorage att;
  } capture;

  PW_TEST_ASSERT_OK_AND_ASSIGN(
      capture.att,
      SetupAttNotify(/*connection_handle=*/capture.handle,
                     /*attribute_handle=*/capture.attribute_handle,
                     capture.attribute_value));

  pw::Function<void(H4PacketWithHci && packet)>&& send_to_host_fn(
      []([[maybe_unused]] H4PacketWithHci&& packet) {});

  pw::Function<void(H4PacketWithH4 && packet)>&& send_to_controller_fn(
      [&capture](H4PacketWithH4&& packet) {
        capture.sends_called++;
        EXPECT_EQ(packet.GetH4Type(), emboss::H4PacketType::ACL_DATA);
        EXPECT_EQ(packet.GetH4Span().size(),
                  capture.att.bframe.acl.h4_span().size());
        EXPECT_TRUE(std::equal(packet.GetH4Span().begin(),
                               packet.GetH4Span().end(),
                               capture.att.bframe.acl.h4_span().begin(),
                               capture.att.bframe.acl.h4_span().end()));
        PW_TEST_ASSERT_OK_AND_ASSIGN(
            auto acl,
            MakeEmbossView<emboss::AclDataFrameView>(packet.GetHciSpan()));
        emboss::BFrameView l2cap =
            emboss::MakeBFrameView(acl.payload().BackingStorage().data(),
                                   acl.data_total_length().Read());
        emboss::AttHandleValueNtfView gatt_notify =
            emboss::MakeAttHandleValueNtfView(
                capture.attribute_value.size(),
                l2cap.payload().BackingStorage().data(),
                l2cap.pdu_length().Read());
        EXPECT_EQ(acl.header().handle().Read(), capture.handle);
        EXPECT_EQ(acl.header().packet_boundary_flag().Read(),
                  emboss::AclDataPacketBoundaryFlag::FIRST_NON_FLUSHABLE);
        EXPECT_EQ(acl.header().broadcast_flag().Read(),
                  emboss::AclDataPacketBroadcastFlag::POINT_TO_POINT);
        EXPECT_EQ(acl.data_total_length().Read(),
                  capture.acl_data_total_length);
        EXPECT_EQ(l2cap.pdu_length().Read(), capture.pdu_length);
        EXPECT_EQ(l2cap.channel_id().Read(), capture.channel_id);
        EXPECT_EQ(gatt_notify.attribute_opcode().Read(),
                  static_cast<emboss::AttOpcode>(capture.attribute_opcode));
        EXPECT_EQ(gatt_notify.attribute_handle().Read(),
                  capture.attribute_handle);
        EXPECT_EQ(gatt_notify.attribute_value()[0].Read(),
                  capture.attribute_value[0]);
      });

  ProxyHost proxy = ProxyHost(std::move(send_to_host_fn),
                              std::move(send_to_controller_fn),
                              /*le_acl_credits_to_reserve=*/1,
                              /*br_edr_acl_credits_to_reserve=*/0);
  // Allow proxy to reserve 1 credit.
  PW_TEST_EXPECT_OK(SendLeReadBufferResponseFromController(proxy, 1));

  GattNotifyChannel channel = BuildGattNotifyChannel(
      proxy,
      {.handle = capture.handle, .attribute_handle = capture.attribute_handle});
  PW_TEST_EXPECT_OK(
      channel.Write(MultiBufFromArray(capture.attribute_value)).status);
  EXPECT_EQ(capture.sends_called, 1);
}

TEST_F(GattNotifyTest, Send2ByteAttribute) {
  struct {
    int sends_called = 0;
    // Max connection_handle value; first four bits 0x0 encode PB & BC flags
    const uint16_t handle = 0x0EFF;
    // Length of L2CAP PDU
    const uint16_t acl_data_total_length = 0x0009;
    // Length of ATT PDU
    const uint16_t pdu_length = 0x0005;
    // Attribute protocol channel ID (0x0004)
    const uint16_t channel_id = 0x0004;
    // ATT_HANDLE_VALUE_NTF opcode 0x1B
    const uint8_t attribute_opcode = 0x1B;
    const uint16_t attribute_handle = 0x1234;
    const std::array<uint8_t, 2> attribute_value = {0xAB, 0xCD};
    AttNotifyWithStorage att;
  } capture;

  PW_TEST_ASSERT_OK_AND_ASSIGN(
      capture.att,
      SetupAttNotify(/*connection_handle=*/capture.handle,
                     /*attribute_handle=*/capture.attribute_handle,
                     capture.attribute_value));

  pw::Function<void(H4PacketWithHci && packet)>&& send_to_host_fn(
      []([[maybe_unused]] H4PacketWithHci&& packet) {});

  pw::Function<void(H4PacketWithH4 && packet)>&& send_to_controller_fn(
      [&capture](H4PacketWithH4&& packet) {
        ++capture.sends_called;
        EXPECT_EQ(packet.GetH4Type(), emboss::H4PacketType::ACL_DATA);
        EXPECT_EQ(packet.GetH4Span().size(),
                  capture.att.bframe.acl.h4_span().size());
        EXPECT_TRUE(std::equal(packet.GetH4Span().begin(),
                               packet.GetH4Span().end(),
                               capture.att.bframe.acl.h4_span().begin(),
                               capture.att.bframe.acl.h4_span().end()));
        PW_TEST_ASSERT_OK_AND_ASSIGN(
            auto acl,
            MakeEmbossView<emboss::AclDataFrameView>(packet.GetHciSpan()));
        emboss::BFrameView l2cap = emboss::MakeBFrameView(
            acl.payload().BackingStorage().data(), acl.SizeInBytes());
        emboss::AttHandleValueNtfView gatt_notify =
            emboss::MakeAttHandleValueNtfView(
                capture.attribute_value.size(),
                l2cap.payload().BackingStorage().data(),
                l2cap.pdu_length().Read());
        EXPECT_EQ(acl.header().handle().Read(), capture.handle);
        EXPECT_EQ(acl.header().packet_boundary_flag().Read(),
                  emboss::AclDataPacketBoundaryFlag::FIRST_NON_FLUSHABLE);
        EXPECT_EQ(acl.header().broadcast_flag().Read(),
                  emboss::AclDataPacketBroadcastFlag::POINT_TO_POINT);
        EXPECT_EQ(acl.data_total_length().Read(),
                  capture.acl_data_total_length);
        EXPECT_EQ(l2cap.pdu_length().Read(), capture.pdu_length);
        EXPECT_EQ(l2cap.channel_id().Read(), capture.channel_id);
        EXPECT_EQ(gatt_notify.attribute_opcode().Read(),
                  static_cast<emboss::AttOpcode>(capture.attribute_opcode));
        EXPECT_EQ(gatt_notify.attribute_handle().Read(),
                  capture.attribute_handle);
        EXPECT_EQ(gatt_notify.attribute_value()[0].Read(),
                  capture.attribute_value[0]);
        EXPECT_EQ(gatt_notify.attribute_value()[1].Read(),
                  capture.attribute_value[1]);
      });

  ProxyHost proxy = ProxyHost(std::move(send_to_host_fn),
                              std::move(send_to_controller_fn),
                              /*le_acl_credits_to_reserve=*/1,
                              /*br_edr_acl_credits_to_reserve=*/0);
  // Allow proxy to reserve 1 credit.
  PW_TEST_EXPECT_OK(SendLeReadBufferResponseFromController(proxy, 1));

  GattNotifyChannel channel = BuildGattNotifyChannel(
      proxy,
      {.handle = capture.handle, .attribute_handle = capture.attribute_handle});
  PW_TEST_EXPECT_OK(
      channel.Write(MultiBufFromArray(capture.attribute_value)).status);
  EXPECT_EQ(capture.sends_called, 1);
}

TEST_F(GattNotifyTest, ReturnsErrorIfAttributeTooLarge) {
  pw::Function<void(H4PacketWithHci && packet)>&& send_to_host_fn(
      []([[maybe_unused]] H4PacketWithHci&& packet) {});
  pw::Function<void(H4PacketWithH4 && packet)> send_to_controller_fn(
      []([[maybe_unused]] H4PacketWithH4 packet) { FAIL(); });

  ProxyHost proxy = ProxyHost(std::move(send_to_host_fn),
                              std::move(send_to_controller_fn),
                              /*le_acl_credits_to_reserve=*/0,
                              /*br_edr_acl_credits_to_reserve=*/0);
  PW_TEST_EXPECT_OK(SendLeReadBufferResponseFromController(proxy, 0));

  // attribute_value 1 byte too large
  std::array<uint8_t,
             proxy.GetMaxAclSendSize() -
                 emboss::AclDataFrameHeader::IntrinsicSizeInBytes() -
                 emboss::BasicL2capHeader::IntrinsicSizeInBytes() -
                 emboss::AttHandleValueNtf::MinSizeInBytes() + 1>
      attribute_value_too_large;
  GattNotifyChannel channel = BuildGattNotifyChannel(proxy, {});
  EXPECT_EQ(channel.Write(MultiBufFromArray(attribute_value_too_large)).status,
            PW_STATUS_INVALID_ARGUMENT);
}

TEST_F(GattNotifyTest, ChannelIsNotConstructedIfParametersInvalid) {
  pw::Function<void(H4PacketWithHci && packet)>&& send_to_host_fn(
      []([[maybe_unused]] H4PacketWithHci&& packet) {});
  pw::Function<void(H4PacketWithH4 && packet)> send_to_controller_fn(
      []([[maybe_unused]] H4PacketWithH4 packet) { FAIL(); });

  ProxyHost proxy = ProxyHost(std::move(send_to_host_fn),
                              std::move(send_to_controller_fn),
                              /*le_acl_credits_to_reserve=*/0,
                              /*br_edr_acl_credits_to_reserve=*/0);

  // attribute value is zero
  EXPECT_EQ(
      BuildGattNotifyChannelWithResult(proxy, {.attribute_handle = 0}).status(),
      PW_STATUS_INVALID_ARGUMENT);

  // connection_handle too large
  EXPECT_EQ(
      BuildGattNotifyChannelWithResult(proxy, {.handle = 0x0FFF}).status(),
      PW_STATUS_INVALID_ARGUMENT);
}

TEST_F(GattNotifyTest, PayloadIsReturnedOnError) {
  pw::Function<void(H4PacketWithHci && packet)>&& send_to_host_fn(
      []([[maybe_unused]] H4PacketWithHci&& packet) {});
  pw::Function<void(H4PacketWithH4 && packet)> send_to_controller_fn(
      []([[maybe_unused]] H4PacketWithH4 packet) { FAIL(); });

  ProxyHost proxy = ProxyHost(std::move(send_to_host_fn),
                              std::move(send_to_controller_fn),
                              /*le_acl_credits_to_reserve=*/0,
                              /*br_edr_acl_credits_to_reserve=*/0);

  const std::array<const uint8_t, 2> attribute_value = {5};

  GattNotifyChannel channel = BuildGattNotifyChannel(proxy, {});
  StatusWithMultiBuf result =
      channel.Write(MultiBufFromSpan(pw::span{attribute_value}));
  ASSERT_EQ(result.status, Status::FailedPrecondition());
  auto s = result.buf->ContiguousSpan();
  ASSERT_TRUE(s.has_value());
  EXPECT_EQ(s.value().size(), attribute_value.size());
  EXPECT_EQ((std::byte)attribute_value[0], s.value().data()[0]);
}

}  // namespace
}  // namespace pw::bluetooth::proxy
