// Copyright 2020-2022 Google LLC
//
// 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 "connections/implementation/internal_payload_factory.h"

#include <cstdint>
#include <memory>
#include <string>
#include <utility>

#include "gtest/gtest.h"
#include "connections/implementation/internal_payload.h"
#include "connections/implementation/proto/offline_wire_formats.pb.h"
#include "connections/payload.h"
#include "connections/payload_type.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
#include "internal/platform/file.h"
#include "internal/platform/pipe.h"

namespace nearby {
namespace connections {
namespace {

using ::location::nearby::connections::PayloadTransferFrame;
constexpr char kText[] = "data chunk";

TEST(InternalPayloadFactoryTest, CanCreateInternalPayloadFromBytePayload) {
  ByteArray data(kText);
  std::unique_ptr<InternalPayload> internal_payload =
      CreateOutgoingInternalPayload(Payload{data});
  EXPECT_NE(internal_payload, nullptr);
  Payload payload = internal_payload->ReleasePayload();
  EXPECT_EQ(payload.AsFile(), nullptr);
  EXPECT_EQ(payload.AsStream(), nullptr);
  EXPECT_EQ(payload.AsBytes(), ByteArray(kText));
}

TEST(InternalPayloadFactoryTest, CanCreateInternalPayloadFromStreamPayload) {
  auto [input, output] = CreatePipe();
  std::unique_ptr<InternalPayload> internal_payload =
      CreateOutgoingInternalPayload(Payload(std::move(input)));
  EXPECT_NE(internal_payload, nullptr);
  Payload payload = internal_payload->ReleasePayload();
  EXPECT_EQ(payload.AsFile(), nullptr);
  EXPECT_NE(payload.AsStream(), nullptr);
  EXPECT_EQ(payload.AsBytes(), ByteArray());
}

TEST(InternalPayloadFactoryTest, CanCreateInternalPayloadFromFilePayload) {
  Payload::Id payload_id = Payload::GenerateId();
  InputFile inputFile(payload_id, 512);
  std::unique_ptr<InternalPayload> internal_payload =
      CreateOutgoingInternalPayload(Payload{payload_id, std::move(inputFile)});
  EXPECT_NE(internal_payload, nullptr);
  Payload payload = internal_payload->ReleasePayload();
  EXPECT_NE(payload.AsFile(), nullptr);
  EXPECT_EQ(payload.AsStream(), nullptr);
  EXPECT_EQ(payload.AsBytes(), ByteArray());
  EXPECT_EQ(payload.GetId(), payload_id);
}

TEST(InternalPayloadFactoryTest, CanCreateInternalPayloadFromByteMessage) {
  PayloadTransferFrame frame;
  std::string path = "C:\\Downloads";
  frame.set_packet_type(PayloadTransferFrame::DATA);
  std::int64_t payload_chunk_offset = 0;
  ByteArray data(kText);
  PayloadTransferFrame::PayloadChunk payload_chunk;
  payload_chunk.set_offset(payload_chunk_offset);
  payload_chunk.set_body(std::string(std::move(data)));
  payload_chunk.set_flags(0);
  auto& header = *frame.mutable_payload_header();
  header.set_type(PayloadTransferFrame::PayloadHeader::BYTES);
  header.set_id(12345);
  header.set_total_size(512);
  *frame.mutable_payload_chunk() = std::move(payload_chunk);
  std::unique_ptr<InternalPayload> internal_payload =
      CreateIncomingInternalPayload(frame, path);
  EXPECT_NE(internal_payload, nullptr);
  Payload payload = internal_payload->ReleasePayload();
  EXPECT_EQ(payload.AsFile(), nullptr);
  EXPECT_EQ(payload.AsStream(), nullptr);
  EXPECT_EQ(payload.AsBytes(), ByteArray(kText));
}

TEST(InternalPayloadFactoryTest, CanCreateInternalPayloadFromStreamMessage) {
  PayloadTransferFrame frame;
  std::string path = "C:\\Downloads";
  frame.set_packet_type(PayloadTransferFrame::DATA);
  auto& header = *frame.mutable_payload_header();
  header.set_type(PayloadTransferFrame::PayloadHeader::STREAM);
  header.set_id(12345);
  header.set_total_size(0);
  std::unique_ptr<InternalPayload> internal_payload =
      CreateIncomingInternalPayload(frame, path);
  EXPECT_NE(internal_payload, nullptr);
  {
    Payload payload = internal_payload->ReleasePayload();
    EXPECT_EQ(payload.AsFile(), nullptr);
    EXPECT_NE(payload.AsStream(), nullptr);
    EXPECT_EQ(payload.AsBytes(), ByteArray());
    EXPECT_EQ(payload.GetType(), PayloadType::kStream);
  }
  // Verifies that we can close InternalPayload after releasing (and destroying)
  // the payload
  internal_payload->Close();
}

TEST(InternalPayloadFactoryTest, CanCreateInternalPayloadFromFileMessage) {
  PayloadTransferFrame frame;
  std::string path = "C:\\Downloads";
  frame.set_packet_type(PayloadTransferFrame::DATA);
  auto& header = *frame.mutable_payload_header();
  header.set_type(PayloadTransferFrame::PayloadHeader::FILE);
  header.set_id(12345);
  header.set_total_size(512);
  std::unique_ptr<InternalPayload> internal_payload =
      CreateIncomingInternalPayload(frame, path);
  EXPECT_NE(internal_payload, nullptr);
  Payload payload = internal_payload->ReleasePayload();
  EXPECT_NE(payload.AsFile(), nullptr);
  EXPECT_EQ(payload.AsStream(), nullptr);
  EXPECT_EQ(payload.AsBytes(), ByteArray());
  EXPECT_EQ(payload.GetType(), PayloadType::kFile);
}

TEST(InternalPayloadFactoryTest,
     InternalPayloadFromFileMessageWithoutIdReturnsNullptr) {
  PayloadTransferFrame frame;
  std::string path = "C:\\Downloads";
  frame.set_packet_type(PayloadTransferFrame::DATA);
  auto& header = *frame.mutable_payload_header();
  header.set_type(PayloadTransferFrame::PayloadHeader::FILE);
  header.set_total_size(512);
  std::unique_ptr<InternalPayload> internal_payload =
      CreateIncomingInternalPayload(frame, path);
  EXPECT_EQ(internal_payload, nullptr);
}

TEST(InternalPayloadFactoryTest,
     CanCreateInternalPayloadFromFileMessageWithFileNameNotSet) {
  PayloadTransferFrame frame;
  std::string path = "C:\\Downloads";
  frame.set_packet_type(PayloadTransferFrame::DATA);
  auto& header = *frame.mutable_payload_header();
  header.set_type(PayloadTransferFrame::PayloadHeader::FILE);
  header.set_id(12345);
  header.set_total_size(512);
  std::unique_ptr<InternalPayload> internal_payload =
      CreateIncomingInternalPayload(frame, path);
  EXPECT_NE(internal_payload, nullptr);
  Payload payload = internal_payload->ReleasePayload();
  EXPECT_EQ(payload.GetFileName(), "12345");
}
TEST(InternalPayloadFactoryTest,
     CanCreateInternalPayloadFromFileMessageWithFileNameSet) {
  PayloadTransferFrame frame;
  std::string path = "C:\\Downloads";
  frame.set_packet_type(PayloadTransferFrame::DATA);
  auto& header = *frame.mutable_payload_header();
  header.set_type(PayloadTransferFrame::PayloadHeader::FILE);
  header.set_id(12345);
  header.set_total_size(512);
  header.set_file_name("test.file.name");
  std::unique_ptr<InternalPayload> internal_payload =
      CreateIncomingInternalPayload(frame, path);
  EXPECT_NE(internal_payload, nullptr);
  auto test = internal_payload->GetFileName();
  Payload payload = internal_payload->ReleasePayload();
  EXPECT_EQ(payload.GetFileName(), "test.file.name");
}

void CreateFileWithContents(Payload::Id payload_id, const ByteArray& contents) {
  OutputFile file(payload_id);
  EXPECT_TRUE(file.Write(contents).Ok());
  EXPECT_TRUE(file.Close().Ok());
}

TEST(InternalPayloadFactoryTest,
     SkipToOffset_FilePayloadValidOffset_SkipsOffset) {
  ByteArray contents("0123456789");
  constexpr size_t kOffset = 4;
  size_t size_after_skip = contents.size() - kOffset;
  Payload::Id payload_id = Payload::GenerateId();
  CreateFileWithContents(payload_id, contents);
  InputFile inputFile(payload_id, contents.size());
  std::unique_ptr<InternalPayload> internal_payload =
      CreateOutgoingInternalPayload(Payload{payload_id, std::move(inputFile)});
  EXPECT_NE(internal_payload, nullptr);

  ExceptionOr<size_t> result = internal_payload->SkipToOffset(kOffset);

  EXPECT_TRUE(result.ok());
  EXPECT_EQ(result.GetResult(), kOffset);
  EXPECT_EQ(internal_payload->GetTotalSize(), contents.size());
  ByteArray contents_after_skip =
      internal_payload->DetachNextChunk(size_after_skip);
  EXPECT_EQ(contents_after_skip, ByteArray("456789"));
}

TEST(InternalPayloadFactoryTest,
     SkipToOffset_StreamPayloadValidOffset_SkipsOffset) {
  ByteArray contents("0123456789");
  constexpr size_t kOffset = 6;
  auto [input, output] = CreatePipe();
  std::unique_ptr<InternalPayload> internal_payload =
      CreateOutgoingInternalPayload(Payload(std::move(input)));
  EXPECT_NE(internal_payload, nullptr);
  output->Write(contents);

  ExceptionOr<size_t> result = internal_payload->SkipToOffset(kOffset);

  EXPECT_TRUE(result.ok());
  EXPECT_EQ(result.GetResult(), kOffset);
  EXPECT_EQ(internal_payload->GetTotalSize(), -1);
  ByteArray contents_after_skip = internal_payload->DetachNextChunk(512);
  EXPECT_EQ(contents_after_skip, ByteArray("6789"));
}

}  // namespace
}  // namespace connections
}  // namespace nearby
