// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/boca/session_api/join_session_request.h"

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "base/functional/bind.h"
#include "base/strings/string_util.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "chromeos/ash/components/boca/proto/bundle.pb.h"
#include "chromeos/ash/components/boca/proto/roster.pb.h"
#include "chromeos/ash/components/boca/proto/session.pb.h"
#include "chromeos/ash/components/boca/session_api/constants.h"
#include "chromeos/ash/components/boca/session_api/update_session_request.h"
#include "google_apis/common/api_error_codes.h"
#include "google_apis/common/dummy_auth_service.h"
#include "google_apis/common/request_sender.h"
#include "google_apis/common/test_util.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/gaia_urls_overrider_for_testing.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/test/test_shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::net::test_server::BasicHttpResponse;
using ::net::test_server::HttpMethod;
using ::net::test_server::HttpRequest;
using ::net::test_server::HttpResponse;
using ::testing::_;
using ::testing::AllOf;
using ::testing::ByMove;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::Truly;

namespace {
const char kTestUserAgent[] = "test-user-agent";

class MockRequestHandler {
 public:
  static std::unique_ptr<HttpResponse> CreateSuccessfulResponse() {
    auto response = std::make_unique<BasicHttpResponse>();
    response->set_code(net::HTTP_OK);
    response->set_content(
        R"(
          {
            "sessionId": "111",
            "duration": {
              "seconds": 120
            },
            "studentStatuses": {},
            "roster": {
              "studentGroups": []
            },
            "sessionState": "ACTIVE",
            "studentGroupConfigs": {
              "main": {
                "captionsConfig": {},
                "onTaskConfig": {
                  "activeBundle": {
                    "contentConfigs": []
                  }
                }
              }
            },
            "teacher": {
              "gaiaId": "1"
            }
          }
    )");
    return response;
  }

  static std::unique_ptr<HttpResponse> CreateResponseWithUrlType(
      const std::string& url_type) {
    auto response = std::make_unique<BasicHttpResponse>();
    response->set_code(net::HTTP_OK);
    response->set_content(base::ReplaceStringPlaceholders(
        R"(
          {
            "sessionId": "111",
            "duration": {
              "seconds": 120
            },
            "sessionState": "ACTIVE",
            "studentGroupConfigs": {
              "main": {
                "onTaskConfig": {
                  "activeBundle": {
                    "contentConfigs": [
                      {
                        "title": "gemini",
                        "url": "https://gemini.google.com",
                        "urlType": "$1"
                      }
                    ]
                  }
                }
              }
            },
            "teacher": {
              "gaiaId": "1"
            }
          }
        )",
        {url_type}, nullptr));
    return response;
  }

  static std::unique_ptr<HttpResponse> CreateFailedResponse() {
    auto response = std::make_unique<BasicHttpResponse>();
    response->set_code(net::HTTP_INTERNAL_SERVER_ERROR);
    return response;
  }

  MOCK_METHOD(std::unique_ptr<HttpResponse>,
              HandleRequest,
              (const HttpRequest&));
};

}  // namespace

namespace ash::boca {

class JoinSessionTest : public testing::Test {
 public:
  using SessionResult = base::expected<std::unique_ptr<::boca::Session>,
                                       google_apis::ApiErrorCode>;

  JoinSessionTest() = default;
  void SetUp() override {
    test_shared_loader_factory_ =
        base::MakeRefCounted<network::TestSharedURLLoaderFactory>(
            /*network_service=*/nullptr,
            /*is_trusted=*/true);
    request_sender_ = std::make_unique<google_apis::RequestSender>(
        std::make_unique<google_apis::DummyAuthService>(),
        test_shared_loader_factory_,
        task_environment_.GetMainThreadTaskRunner(), kTestUserAgent,
        TRAFFIC_ANNOTATION_FOR_TESTS);

    test_server_.RegisterRequestHandler(
        base::BindRepeating(&MockRequestHandler::HandleRequest,
                            base::Unretained(&request_handler_)));

    ASSERT_TRUE(test_server_.Start());

    student_.set_gaia_id("1");
    student_.set_full_name("Student one");
    student_.set_email("user@gmail.com");
    student_.set_photo_url("http://photo");
  }

  MockRequestHandler& request_handler() { return request_handler_; }
  google_apis::RequestSender* request_sender() { return request_sender_.get(); }

  void ExpectHandleRequestCallAndReturn(
      net::test_server::HttpRequest* http_request,
      std::unique_ptr<net::test_server::HttpResponse> response) {
    EXPECT_CALL(request_handler(), HandleRequest(_))
        .WillOnce(DoAll(SaveArg<0>(http_request),
                        Return(ByMove(std::move(response)))));
  }

 protected:
  // net::test_server::HttpRequest http_request;
  net::EmbeddedTestServer test_server_;
  ::boca::UserIdentity student_;

 private:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::MainThreadType::IO};
  std::unique_ptr<google_apis::RequestSender> request_sender_;
  testing::StrictMock<MockRequestHandler> request_handler_;
  std::unique_ptr<GaiaUrlsOverriderForTesting> urls_overrider_;
  scoped_refptr<network::TestSharedURLLoaderFactory>
      test_shared_loader_factory_;
};

TEST_F(JoinSessionTest, JoinSessionWithAndSucceed) {
  net::test_server::HttpRequest http_request;
  ExpectHandleRequestCallAndReturn(
      &http_request, MockRequestHandler::CreateSuccessfulResponse());

  base::test::TestFuture<SessionResult> future;

  std::unique_ptr<JoinSessionRequest> request =
      std::make_unique<JoinSessionRequest>(request_sender(), "https://test",
                                           student_, "deviceId", "code",
                                           future.GetCallback());
  request->OverrideURLForTesting(test_server_.base_url().spec());

  request_sender()->StartRequestWithAuthRetry(std::move(request));

  ASSERT_TRUE(future.Wait());
  auto result = future.Take();
  EXPECT_EQ(net::test_server::METHOD_POST, http_request.method);

  EXPECT_EQ("/v1/students/1/session:join", http_request.relative_url);
  EXPECT_EQ("application/json", http_request.headers["Content-Type"]);
  auto* contentData =
      "{\"deviceInfo\":{\"deviceId\":\"deviceId\"},\"sessionJoinCode\":"
      "\"code\",\"student\":{\"email\":\"user@gmail.com\",\"fullName\":"
      "\"Student one\",\"gaiaId\":\"1\",\"photoUrl\":\"http://photo\"}}";
  ASSERT_TRUE(http_request.has_content);
  EXPECT_EQ(contentData, http_request.content);
  EXPECT_TRUE(result.has_value());
}

TEST_F(JoinSessionTest, JoinSessionAndFail) {
  net::test_server::HttpRequest http_request;
  ExpectHandleRequestCallAndReturn(&http_request,
                                   MockRequestHandler::CreateFailedResponse());

  base::test::TestFuture<SessionResult> future;

  std::unique_ptr<JoinSessionRequest> request =
      std::make_unique<JoinSessionRequest>(request_sender(), "https://test",
                                           student_, "deviceId", "code",
                                           future.GetCallback());
  request->OverrideURLForTesting(test_server_.base_url().spec());

  request_sender()->StartRequestWithAuthRetry(std::move(request));

  ASSERT_TRUE(future.Wait());
  auto result = future.Take();
  EXPECT_EQ(net::test_server::METHOD_POST, http_request.method);

  EXPECT_EQ("/v1/students/1/session:join", http_request.relative_url);
  EXPECT_EQ("application/json", http_request.headers["Content-Type"]);
  auto* contentData =
      "{\"deviceInfo\":{\"deviceId\":\"deviceId\"},\"sessionJoinCode\":"
      "\"code\",\"student\":{\"email\":\"user@gmail.com\",\"fullName\":"
      "\"Student one\",\"gaiaId\":\"1\",\"photoUrl\":\"http://photo\"}}";
  ASSERT_TRUE(http_request.has_content);
  EXPECT_EQ(contentData, http_request.content);
  EXPECT_EQ(google_apis::HTTP_INTERNAL_SERVER_ERROR, result.error());
}

struct JoinSessionUrlTypeTestParam {
  std::string test_name;
  std::string url_type_str;
  ::boca::UrlType expected_url_type;
};

class JoinSessionUrlTypeTest
    : public JoinSessionTest,
      public testing::WithParamInterface<JoinSessionUrlTypeTestParam> {};

TEST_P(JoinSessionUrlTypeTest, JoinSessionAndVerifyUrlType) {
  net::test_server::HttpRequest http_request;
  ExpectHandleRequestCallAndReturn(
      &http_request,
      MockRequestHandler::CreateResponseWithUrlType(GetParam().url_type_str));

  base::test::TestFuture<SessionResult> future;

  std::unique_ptr<JoinSessionRequest> request =
      std::make_unique<JoinSessionRequest>(request_sender(), "https://test",
                                           student_, "deviceId", "code",
                                           future.GetCallback());
  request->OverrideURLForTesting(test_server_.base_url().spec());

  request_sender()->StartRequestWithAuthRetry(std::move(request));

  auto result = future.Take();
  ASSERT_TRUE(result.has_value());
  std::unique_ptr<::boca::Session> session = std::move(result.value());
  ASSERT_TRUE(session->student_group_configs().contains(kMainStudentGroupName));
  auto content_config = std::move(session->student_group_configs()
                                      .at(kMainStudentGroupName)
                                      .on_task_config()
                                      .active_bundle()
                                      .content_configs());
  ASSERT_EQ(1, content_config.size());

  EXPECT_EQ("gemini", content_config[0].title());
  EXPECT_EQ("https://gemini.google.com", content_config[0].url());
  EXPECT_EQ(GetParam().expected_url_type, content_config[0].url_type());
}

INSTANTIATE_TEST_SUITE_P(
    JoinSessionUrlTypeTests,
    JoinSessionUrlTypeTest,
    testing::Values(
        JoinSessionUrlTypeTestParam{"GeminiRegular", "URL_TYPE_GEMINI_REGULAR",
                                    ::boca::URL_TYPE_GEMINI_REGULAR},
        JoinSessionUrlTypeTestParam{"GeminiGuidedLearning",
                                    "URL_TYPE_GEMINI_GUIDED_LEARNING",
                                    ::boca::URL_TYPE_GEMINI_GUIDED_LEARNING},
        JoinSessionUrlTypeTestParam{"UrlTypeUnspecified",
                                    "URL_TYPE_UNSPECIFIED",
                                    ::boca::URL_TYPE_UNSPECIFIED},
        JoinSessionUrlTypeTestParam{"UrlTypeInvalid", "INVALID_TYPE",
                                    ::boca::URL_TYPE_UNSPECIFIED}),
    [](const testing::TestParamInfo<JoinSessionUrlTypeTest::ParamType>& info) {
      return info.param.test_name;
    });

}  // namespace ash::boca
