// Copyright 2022 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_profiles/device_info_service.h"

#include <string_view>

#include "pw_bluetooth/gatt/server.h"
#include "pw_unit_test/framework.h"

using namespace std::string_view_literals;

namespace pw::bluetooth_profiles {
namespace {

class FakeGattServer final : public bluetooth::gatt::Server {
 public:
  // Server overrides:
  void PublishService(
      const bluetooth::gatt::LocalServiceInfo& info,
      bluetooth::gatt::LocalServiceDelegate* delegate,
      Function<void(PublishServiceResult)>&& result_callback) override {
    ASSERT_EQ(delegate_, nullptr);
    delegate_ = delegate;
    ASSERT_EQ(published_info_, nullptr);
    published_info_ = &info;
    local_service_.emplace(this);
    result_callback(
        PublishServiceResult(std::in_place, &local_service_.value()));
  }

  // PublishService call argument getters:
  const bluetooth::gatt::LocalServiceInfo* published_info() const {
    return published_info_;
  }
  bluetooth::gatt::LocalServiceDelegate* delegate() const { return delegate_; }

 private:
  class FakeLocalService final : public bluetooth::gatt::LocalService {
   public:
    explicit FakeLocalService(FakeGattServer* fake_server)
        : fake_server_(fake_server) {}

    // LocalService overrides:
    void NotifyValue(
        const ValueChangedParameters& /* parameters */,
        ValueChangedCallback&& /* completion_callback */) override {
      FAIL();  // Unimplemented
    }
    void IndicateValue(
        const ValueChangedParameters& /* parameters */,
        Function<void(
            bluetooth::Result<bluetooth::gatt::Error>)>&& /* confirmation */)
        override {
      FAIL();  // Unimplemented
    }

   private:
    void UnpublishService() override { fake_server_->local_service_.reset(); }

    FakeGattServer* fake_server_;
  };

  // The LocalServiceInfo passed when PublishService was called.
  const bluetooth::gatt::LocalServiceInfo* published_info_ = nullptr;

  bluetooth::gatt::LocalServiceDelegate* delegate_ = nullptr;

  std::optional<FakeLocalService> local_service_;
};

TEST(DeviceInfoServiceTest, PublishAndReadTest) {
  FakeGattServer fake_server;

  constexpr auto kUsedFields = DeviceInfo::Field::kModelNumber |
                               DeviceInfo::Field::kSerialNumber |
                               DeviceInfo::Field::kSoftwareRevision;
  DeviceInfo device_info = {};
  const auto kModelNumber = "model"sv;
  device_info.model_number = as_bytes(span{kModelNumber});
  device_info.serial_number = as_bytes(span{"parallel_number"sv});
  device_info.software_revision = as_bytes(span{"rev123"sv});

  DeviceInfoService<kUsedFields, bluetooth::gatt::Handle{123}>
      device_info_service(device_info);

  bool called = false;
  device_info_service.PublishService(
      &fake_server,
      [&called](
          bluetooth::Result<bluetooth::gatt::Server::PublishServiceError> res) {
        EXPECT_TRUE(res.ok());
        called = true;
      });
  // The FakeGattServer calls the PublishService callback right away so our
  // callback should have been called already.
  EXPECT_TRUE(called);

  ASSERT_NE(fake_server.delegate(), nullptr);
  ASSERT_NE(fake_server.published_info(), nullptr);

  // Test that the published info looks correct.
  EXPECT_EQ(3u, fake_server.published_info()->characteristics.size());

  // Test that we can read the characteristics.
  for (auto& characteristic : fake_server.published_info()->characteristics) {
    bool read_callback_called = false;
    fake_server.delegate()->ReadValue(
        bluetooth::PeerId{1234},
        characteristic.handle,
        /*offset=*/0,
        [&read_callback_called](bluetooth::Result<bluetooth::gatt::Error,
                                                  span<const std::byte>> res) {
          EXPECT_TRUE(res.ok());
          EXPECT_NE(0u, res.value().size());
          read_callback_called = true;
        });
    // The DeviceInfoService always calls the callback from within ReadValue().
    EXPECT_TRUE(read_callback_called);
  }

  // Check the actual values.
  // The order of the characteristics in the LocalServiceInfo must be the order
  // in which the fields are listed in kCharacteristicFields, so the first
  // characteristic is the Model Number.
  span<const std::byte> read_value;
  fake_server.delegate()->ReadValue(
      bluetooth::PeerId{1234},
      fake_server.published_info()->characteristics[0].handle,
      /*offset=*/0,
      [&read_value](bluetooth::Result<bluetooth::gatt::Error,
                                      span<const std::byte>> res) {
        EXPECT_TRUE(res.ok());
        read_value = res.value();
      });
  EXPECT_EQ(read_value.size(), kModelNumber.size());  // "model" string.
  // DeviceInfoService keeps references to the values provides in the
  // DeviceInfo struct, not copies.
  EXPECT_EQ(read_value.data(),
            reinterpret_cast<const std::byte*>(kModelNumber.data()));

  // Read with an offset.
  const size_t kReadOffset = 3;
  fake_server.delegate()->ReadValue(
      bluetooth::PeerId{1234},
      fake_server.published_info()->characteristics[0].handle,
      kReadOffset,
      [&read_value](bluetooth::Result<bluetooth::gatt::Error,
                                      span<const std::byte>> res) {
        EXPECT_TRUE(res.ok());
        read_value = res.value();
      });
  EXPECT_EQ(read_value.size(), kModelNumber.size() - kReadOffset);
  EXPECT_EQ(
      read_value.data(),
      reinterpret_cast<const std::byte*>(kModelNumber.data()) + kReadOffset);
}

}  // namespace
}  // namespace pw::bluetooth_profiles
