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

#include "chrome/browser/media/router/media_router_feature.h"

#include <stdint.h>

#include <string>
#include <utility>

#include "base/base64.h"
#include "base/command_line.h"
#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/media_router/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "crypto/random.h"
#include "extensions/buildflags/buildflags.h"
#include "ui/base/buildflags.h"

#if !BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
#include "components/prefs/pref_registry_simple.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#endif

// NOTE: Consider separating out UI-only features that are not consumed by the
// Media Router itself into their own file in chrome/browser/ui/media_router.

namespace media_router {

#if !BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
BASE_FEATURE(kMediaRouter, base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kCastAllowAllIPsFeature,
             "CastAllowAllIPs",
             base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kAllowAllSitesToInitiateMirroring,
             base::FEATURE_DISABLED_BY_DEFAULT);
BASE_FEATURE(kDelayMediaSinkDiscovery, base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kShowCastPermissionRejectedError,
             base::FEATURE_ENABLED_BY_DEFAULT);
BASE_FEATURE(kCastMessageLogging, base::FEATURE_DISABLED_BY_DEFAULT);

#if BUILDFLAG(ENABLE_MEDIA_REMOTING_REDIRECTION)
// If enabled, the redirection (MMR) Media Route Provider is registered.
BASE_FEATURE(kRedirectionMediaRouteProvider, base::FEATURE_DISABLED_BY_DEFAULT);
#endif  // BUILDFLAG(ENABLE_MEDIA_REMOTING_REDIRECTION)

// TODO(crbug.com/1486680): Remove once stopping mirroring routes in the global
// media controls is implemented on ChromeOS.
BASE_FEATURE(kFallbackToAudioTabMirroring,
#if BUILDFLAG(IS_CHROMEOS)
             base::FEATURE_DISABLED_BY_DEFAULT);
#else
             base::FEATURE_ENABLED_BY_DEFAULT);
#endif  // BUILDFLAG(IS_CHROMEOS)

#endif  // !BUILDFLAG(IS_ANDROID) ||
        // BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)

namespace {
const PrefService::Preference* GetMediaRouterPref(
    content::BrowserContext* context) {
  return user_prefs::UserPrefs::Get(context)->FindPreference(
      ::prefs::kEnableMediaRouter);
}

base::flat_map<content::BrowserContext*, bool>& GetStoredPrefValues() {
  static base::NoDestructor<base::flat_map<content::BrowserContext*, bool>>
      stored_pref_values;

  return *stored_pref_values;
}

#if !BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
// TODO(mfoltz): Add full implementation for validating playout delay value.
bool IsValidMirroringPlayoutDelayMs(int delay_ms) {
  return delay_ms <= 1000 && delay_ms >= 1;
}
#endif  // !BUILDFLAG(IS_ANDROID) ||
        // BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
}  // namespace

void ClearMediaRouterStoredPrefsForTesting() {
  GetStoredPrefValues().clear();
}

bool MediaRouterEnabled(content::BrowserContext* context) {
#if !BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
  if (!base::FeatureList::IsEnabled(kMediaRouter)) {
    return false;
  }
#endif  // !BUILDFLAG(IS_ANDROID) ||
        // BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
#if BUILDFLAG(IS_CHROMEOS)
  // TODO(crbug.com/1380828): Make the Media Router feature configurable via a
  // policy for non-user profiles, i.e. sign-in and lock screen profiles.
  if (!ash::IsUserBrowserContext(context)) {
    return false;
  }
#endif  // BUILDFLAG(IS_CHROMEOS)

  // If the Media Router was already enabled or disabled for |context|, then it
  // must remain so.  The Media Router does not support dynamic
  // enabling/disabling.
  base::flat_map<content::BrowserContext*, bool>& pref_values =
      GetStoredPrefValues();
  auto const it = pref_values.find(context);
  if (it != pref_values.end()) {
    return it->second;
  }

  // Check the enterprise policy.
  const PrefService::Preference* pref = GetMediaRouterPref(context);
  if (pref->IsManaged() && !pref->IsDefaultValue()) {
    CHECK(pref->GetValue()->is_bool());
    bool allowed = pref->GetValue()->GetBool();
    pref_values.insert(std::make_pair(context, allowed));
    return allowed;
  }
  return true;
}

#if !BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)
void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
  registry->RegisterBooleanPref(prefs::kMediaRouterCastAllowAllIPs, false,
                                PrefRegistry::PUBLIC);
  registry->RegisterBooleanPref(prefs::kSuppressLocalDiscoveryPermissionError,
                                false);
}

void RegisterProfilePrefs(PrefRegistrySimple* registry) {
  registry->RegisterStringPref(prefs::kMediaRouterReceiverIdHashToken, "",
                               PrefRegistry::PUBLIC);
  registry->RegisterBooleanPref(
      media_router::prefs::kMediaRouterMediaRemotingEnabled, true);
  registry->RegisterBooleanPref(
      media_router::prefs::kMediaRouterShowCastSessionsStartedByOtherDevices,
      true);
}

bool GetCastAllowAllIPsPref(PrefService* pref_service) {
  auto* pref = pref_service->FindPreference(prefs::kMediaRouterCastAllowAllIPs);

  // Only use the pref value if it is set from a mandatory policy.
  bool allow_all_ips = false;
  if (pref->IsManaged() && !pref->IsDefaultValue()) {
    CHECK(pref->GetValue()->is_bool());
    allow_all_ips = pref->GetValue()->GetBool();
  } else {
    allow_all_ips = base::FeatureList::IsEnabled(kCastAllowAllIPsFeature);
  }

  return allow_all_ips;
}

std::string GetReceiverIdHashToken(PrefService* pref_service) {
  static constexpr size_t kHashTokenSize = 64;
  std::string token =
      pref_service->GetString(prefs::kMediaRouterReceiverIdHashToken);
  if (token.empty()) {
    std::array<uint8_t, kHashTokenSize> rand_token;
    crypto::RandBytes(rand_token);
    token = base::Base64Encode(rand_token);
    pref_service->SetString(prefs::kMediaRouterReceiverIdHashToken, token);
  }
  return token;
}

bool RedirectionMediaRouteProviderEnabled() {
#if BUILDFLAG(ENABLE_MEDIA_REMOTING_REDIRECTION)
  return base::FeatureList::IsEnabled(kRedirectionMediaRouteProvider);
#else
  return false;
#endif  // BUILDFLAG(ENABLE_MEDIA_REMOTING_REDIRECTION)
}

std::optional<base::TimeDelta> GetCastMirroringPlayoutDelay() {
  std::optional<base::TimeDelta> target_playout_delay;

  // The default playout delay can be overridden with the command line flag
  // `cast-mirroring-target-playout-delay`.
  const base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
  if (cl->HasSwitch(switches::kCastMirroringTargetPlayoutDelay)) {
    int switch_playout_delay = 0;
    if (base::StringToInt(
            cl->GetSwitchValueASCII(switches::kCastMirroringTargetPlayoutDelay),
            &switch_playout_delay) &&
        IsValidMirroringPlayoutDelayMs(switch_playout_delay)) {
      target_playout_delay = base::Milliseconds(switch_playout_delay);
    }
  }

  return target_playout_delay;
}

bool IsCastMessageLoggingEnabled() {
  return base::FeatureList::IsEnabled(kCastMessageLogging);
}
#endif  // !BUILDFLAG(IS_ANDROID) ||
        // BUILDFLAG(ENABLE_DESKTOP_ANDROID_EXTENSIONS)

}  // namespace media_router
