import { catchUserErrors } from '../../tools/catchUserErrors'
import { DOCS_ORIGIN, MORE_DETAILS, display } from '../../tools/display'
import type { RawTelemetryConfiguration } from '../telemetry'
import { isPercentage } from '../../tools/utils/numberUtils'
import { objectHasValue } from '../../tools/utils/objectUtils'
import { selectSessionStoreStrategyType } from '../session/sessionStore'
import type { SessionStoreStrategyType } from '../session/storeStrategies/sessionStoreStrategy'
import { TrackingConsent } from '../trackingConsent'
import type { SessionPersistence } from '../session/sessionConstants'
import type { MatchOption } from '../../tools/matchOption'
import { isAllowedTrackingOrigins } from '../allowedTrackingOrigins'
import type { Site } from '../intakeSites'
import { isWorkerEnvironment } from '../../tools/globalObject'
import type { TransportConfiguration } from './transportConfiguration'
import { computeTransportConfiguration } from './transportConfiguration'

/**
 * Default privacy level for the browser SDK.
 *
 * [Replay Privacy Options](https://docs.datadoghq.com/real_user_monitoring/session_replay/browser/privacy_options) for further information.
 */
export const DefaultPrivacyLevel = {
  ALLOW: 'allow',
  MASK: 'mask',
  MASK_USER_INPUT: 'mask-user-input',
  MASK_UNLESS_ALLOWLISTED: 'mask-unless-allowlisted',
} as const
export type DefaultPrivacyLevel = (typeof DefaultPrivacyLevel)[keyof typeof DefaultPrivacyLevel]

/**
 * Trace context injection option.
 *
 * See [Connect RUM and Traces](https://docs.datadoghq.com/real_user_monitoring/platform/connect_rum_and_traces/?tab=browserrum) for further information.
 */
export const TraceContextInjection = {
  ALL: 'all',
  SAMPLED: 'sampled',
} as const

/**
 * Trace context injection option.
 *
 * See [Connect RUM and Traces](https://docs.datadoghq.com/real_user_monitoring/platform/connect_rum_and_traces/?tab=browserrum) for further information.
 *
 */
export type TraceContextInjection = (typeof TraceContextInjection)[keyof typeof TraceContextInjection]

export interface InitConfiguration {
  /**
   * The client token for Datadog. Required for authenticating your application with Datadog.
   *
   * @category Authentication
   */
  clientToken: string

  /**
   * A callback function that can be used to modify events before they are sent to Datadog.
   *
   * @category Data Collection
   */
  beforeSend?: GenericBeforeSendCallback | undefined

  /**
   * The percentage of sessions tracked. A value between 0 and 100.
   *
   * @category Data Collection
   * @defaultValue 100
   */
  sessionSampleRate?: number | undefined

  /**
   * The percentage of telemetry events sent. A value between 0 and 100.
   *
   * @category Data Collection
   * @defaultValue 20
   */
  telemetrySampleRate?: number | undefined

  /**
   * Initialization fails silently if the RUM Browser SDK is already initialized on the page.
   *
   * @defaultValue false
   */
  silentMultipleInit?: boolean | undefined

  /**
   * Which storage strategy to use for persisting sessions. Can be 'cookie', 'local-storage', or 'memory'.
   * When an array is provided, the SDK will attempt each persistence type in the order specified.
   *
   * Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values
   *
   * Note: 'memory' option is only for use with single-page applications. All page loads will start a new session, likely resulting in an increase in total number of RUM sessions
   *
   * @category Session Persistence
   * @defaultValue "cookie"
   */
  sessionPersistence?: SessionPersistence | SessionPersistence[] | undefined

  /**
   * Allows the use of localStorage when cookies cannot be set. This enables the RUM Browser SDK to run in environments that do not provide cookie support.
   *
   * Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values
   * See [Monitor Electron Applications Using the Browser SDK](https://docs.datadoghq.com/real_user_monitoring/guide/monitor-electron-applications-using-browser-sdk) for further information.
   *
   * @category Session Persistence
   * @deprecated use `sessionPersistence: local-storage` where you want to use localStorage instead
   */
  allowFallbackToLocalStorage?: boolean | undefined

  /**
   * Allow listening to DOM events dispatched programmatically ([untrusted events](https://developer.mozilla.org/en-US/docs/Web/API/Event/isTrusted)). Enabling this option can be useful if you heavily rely on programmatic events, such as in an automated UI test environment.
   *
   * @defaultValue false
   */
  allowUntrustedEvents?: boolean | undefined

  /**
   * Store global context and user context in localStorage to preserve them along the user navigation.
   * See [Contexts life cycle](https://docs.datadoghq.com/real_user_monitoring/browser/advanced_configuration/?tab=npm#contexts-life-cycle) for further information.
   *
   * @defaultValue false
   */
  storeContextsAcrossPages?: boolean | undefined

  /**
   * Set the initial user tracking consent state.
   * See [User tracking consent](https://docs.datadoghq.com/real_user_monitoring/browser/advanced_configuration/?tab=npm#user-tracking-consent) for further information.
   *
   * @category Privacy
   * @defaultValue granted
   */
  trackingConsent?: TrackingConsent | undefined

  /**
   * List of origins where the SDK is allowed to run when used in a browser extension context.
   * Matches urls against the extensions origin.
   * If not provided and the SDK is running in a browser extension, the SDK will not run.
   */
  allowedTrackingOrigins?: MatchOption[] | undefined

  // transport options
  /**
   * Optional proxy URL, for example: https://www.proxy.com/path.
   * See [Proxy Your Browser RUM Data](https://docs.datadoghq.com/real_user_monitoring/guide/proxy-rum-data) for further information.
   *
   * @category Transport
   */
  proxy?: string | ProxyFn | undefined

  /**
   * The Datadog [site](https://docs.datadoghq.com/getting_started/site) parameter of your organization.
   *
   * @category Transport
   * @defaultValue datadoghq.com
   */
  site?: Site | undefined

  // tag and context options
  /**
   * The service name for your application. Follows the [tag syntax requirements](https://docs.datadoghq.com/getting_started/tagging/#define-tags).
   *
   * @category Data Collection
   */
  service?: string | undefined | null

  /**
   * The application’s environment, for example: prod, pre-prod, and staging. Follows the [tag syntax requirements](https://docs.datadoghq.com/getting_started/tagging/#define-tags).
   *
   * @category Data Collection
   */
  env?: string | undefined | null

  /**
   * The application’s version, for example: 1.2.3, 6c44da20, and 2020.02.13. Follows the [tag syntax requirements](https://docs.datadoghq.com/getting_started/tagging/#define-tags).
   *
   * @category Data Collection
   */
  version?: string | undefined | null

  // cookie options
  /**
   * Use a partitioned secure cross-site session cookie. This allows the RUM Browser SDK to run when the site is loaded from another one (iframe). Implies `useSecureSessionCookie`.
   *
   * Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values
   *
   * @category Session Persistence
   * @defaultValue false
   */
  usePartitionedCrossSiteSessionCookie?: boolean | undefined

  /**
   * Use a secure session cookie. This disables RUM events sent on insecure (non-HTTPS) connections.
   *
   * Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values
   *
   * @category Session Persistence
   * @defaultValue false
   */
  useSecureSessionCookie?: boolean | undefined

  /**
   * Preserve the session across subdomains for the same site.
   *
   * Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values
   *
   * @category Session Persistence
   * @defaultValue false
   */
  trackSessionAcrossSubdomains?: boolean | undefined

  /**
   * Track anonymous user for the same site and extend cookie expiration date
   *
   * @category Data Collection
   * @defaultValue true
   */
  trackAnonymousUser?: boolean | undefined

  /**
   * Encode cookie options in the cookie value. This can be used as a mitigation for microssession issues.
   * ⚠️ This is a beta feature and may be changed or removed in the future.
   *
   * @category Beta
   * @defaultValue false
   */
  betaEncodeCookieOptions?: boolean | undefined

  // internal options
  /**
   * [Internal option] Enable experimental features
   *
   * @internal
   */
  enableExperimentalFeatures?: string[] | undefined

  /**
   * [Internal option] Configure the dual shipping to another datacenter
   *
   * @internal
   */
  replica?: ReplicaUserConfiguration | undefined

  /**
   * [Internal option] Set the datacenter from where the data is dual shipped
   *
   * @internal
   */
  datacenter?: string

  /**
   * [Internal option] Datadog internal analytics subdomain
   *
   * @internal
   */
  // TODO next major: remove this option and replace usages by proxyFn
  internalAnalyticsSubdomain?: string

  /**
   * [Internal option] The percentage of telemetry configuration sent. A value between 0 and 100.
   *
   * @internal
   * @defaultValue 5
   */
  telemetryConfigurationSampleRate?: number

  /**
   * [Internal option] The percentage of telemetry usage sent. A value between 0 and 100.
   *
   * @internal
   * @defaultValue 5
   */
  telemetryUsageSampleRate?: number

  /**
   * [Internal option] Additional configuration for the SDK.
   *
   * @internal
   */
  source?: 'browser' | 'flutter' | 'unity' | undefined

  /**
   * [Internal option] Additional configuration for the SDK.
   *
   * @internal
   */
  sdkVersion?: string | undefined

  /**
   * [Internal option] Additional configuration for the SDK.
   *
   * @internal
   */
  variant?: string | undefined
}

// This type is only used to build the core configuration. Logs and RUM SDKs are using a proper type
// for this option.
type GenericBeforeSendCallback = (event: any, context?: any) => unknown

/**
 * path: /api/vX/product
 * parameters: xxx=yyy&zzz=aaa
 */
export type ProxyFn = (options: { path: string; parameters: string }) => string

/**
 * @internal
 */
export interface ReplicaUserConfiguration {
  applicationId?: string
  clientToken: string
}

export interface Configuration extends TransportConfiguration {
  // Built from init configuration
  beforeSend: GenericBeforeSendCallback | undefined
  sessionStoreStrategyType: SessionStoreStrategyType | undefined
  sessionSampleRate: number
  telemetrySampleRate: number
  telemetryConfigurationSampleRate: number
  telemetryUsageSampleRate: number
  service?: string | undefined
  version?: string | undefined
  env?: string | undefined
  silentMultipleInit: boolean
  allowUntrustedEvents: boolean
  trackingConsent: TrackingConsent
  storeContextsAcrossPages: boolean
  trackAnonymousUser?: boolean
  betaEncodeCookieOptions: boolean

  // internal
  sdkVersion: string | undefined
  source: 'browser' | 'flutter' | 'unity'
  variant: string | undefined
}

function isString(tag: unknown, tagName: string): tag is string | undefined | null {
  if (tag !== undefined && tag !== null && typeof tag !== 'string') {
    display.error(`${tagName} must be defined as a string`)
    return false
  }
  return true
}

function isDatadogSite(site: unknown) {
  if (site && typeof site === 'string' && !/(datadog|ddog|datad0g|dd0g)/.test(site)) {
    display.error(`Site should be a valid Datadog site. ${MORE_DETAILS} ${DOCS_ORIGIN}/getting_started/site/.`)
    return false
  }
  return true
}

export function isSampleRate(sampleRate: unknown, name: string) {
  if (sampleRate !== undefined && !isPercentage(sampleRate)) {
    display.error(`${name} Sample Rate should be a number between 0 and 100`)
    return false
  }
  return true
}

export function validateAndBuildConfiguration(
  initConfiguration: InitConfiguration,
  errorStack?: string
): Configuration | undefined {
  if (!initConfiguration || !initConfiguration.clientToken) {
    display.error('Client Token is not configured, we will not send any data.')
    return
  }

  if (
    initConfiguration.allowedTrackingOrigins !== undefined &&
    !Array.isArray(initConfiguration.allowedTrackingOrigins)
  ) {
    display.error('Allowed Tracking Origins must be an array')
    return
  }

  if (
    !isDatadogSite(initConfiguration.site) ||
    !isSampleRate(initConfiguration.sessionSampleRate, 'Session') ||
    !isSampleRate(initConfiguration.telemetrySampleRate, 'Telemetry') ||
    !isSampleRate(initConfiguration.telemetryConfigurationSampleRate, 'Telemetry Configuration') ||
    !isSampleRate(initConfiguration.telemetryUsageSampleRate, 'Telemetry Usage') ||
    !isString(initConfiguration.version, 'Version') ||
    !isString(initConfiguration.env, 'Env') ||
    !isString(initConfiguration.service, 'Service') ||
    !isAllowedTrackingOrigins(initConfiguration, errorStack ?? '')
  ) {
    return
  }

  if (
    initConfiguration.trackingConsent !== undefined &&
    !objectHasValue(TrackingConsent, initConfiguration.trackingConsent)
  ) {
    display.error('Tracking Consent should be either "granted" or "not-granted"')
    return
  }

  return {
    beforeSend:
      initConfiguration.beforeSend && catchUserErrors(initConfiguration.beforeSend, 'beforeSend threw an error:'),
    sessionStoreStrategyType: isWorkerEnvironment ? undefined : selectSessionStoreStrategyType(initConfiguration),
    sessionSampleRate: initConfiguration.sessionSampleRate ?? 100,
    telemetrySampleRate: initConfiguration.telemetrySampleRate ?? 20,
    telemetryConfigurationSampleRate: initConfiguration.telemetryConfigurationSampleRate ?? 5,
    telemetryUsageSampleRate: initConfiguration.telemetryUsageSampleRate ?? 5,
    service: initConfiguration.service ?? undefined,
    env: initConfiguration.env ?? undefined,
    version: initConfiguration.version ?? undefined,
    datacenter: initConfiguration.datacenter ?? undefined,
    silentMultipleInit: !!initConfiguration.silentMultipleInit,
    allowUntrustedEvents: !!initConfiguration.allowUntrustedEvents,
    trackingConsent: initConfiguration.trackingConsent ?? TrackingConsent.GRANTED,
    trackAnonymousUser: initConfiguration.trackAnonymousUser ?? true,
    storeContextsAcrossPages: !!initConfiguration.storeContextsAcrossPages,
    betaEncodeCookieOptions: !!initConfiguration.betaEncodeCookieOptions,

    /**
     * The source of the SDK, used for support plugins purposes.
     */
    variant: initConfiguration.variant,
    sdkVersion: initConfiguration.sdkVersion,

    ...computeTransportConfiguration(initConfiguration),
  }
}

export function serializeConfiguration(initConfiguration: InitConfiguration) {
  return {
    session_sample_rate: initConfiguration.sessionSampleRate,
    telemetry_sample_rate: initConfiguration.telemetrySampleRate,
    telemetry_configuration_sample_rate: initConfiguration.telemetryConfigurationSampleRate,
    telemetry_usage_sample_rate: initConfiguration.telemetryUsageSampleRate,
    use_before_send: !!initConfiguration.beforeSend,
    use_partitioned_cross_site_session_cookie: initConfiguration.usePartitionedCrossSiteSessionCookie,
    use_secure_session_cookie: initConfiguration.useSecureSessionCookie,
    use_proxy: !!initConfiguration.proxy,
    silent_multiple_init: initConfiguration.silentMultipleInit,
    track_session_across_subdomains: initConfiguration.trackSessionAcrossSubdomains,
    track_anonymous_user: initConfiguration.trackAnonymousUser,
    session_persistence: Array.isArray(initConfiguration.sessionPersistence)
      ? initConfiguration.sessionPersistence[0]
      : initConfiguration.sessionPersistence,
    allow_fallback_to_local_storage: !!initConfiguration.allowFallbackToLocalStorage,
    store_contexts_across_pages: !!initConfiguration.storeContextsAcrossPages,
    allow_untrusted_events: !!initConfiguration.allowUntrustedEvents,
    tracking_consent: initConfiguration.trackingConsent,
    use_allowed_tracking_origins: Array.isArray(initConfiguration.allowedTrackingOrigins),
    beta_encode_cookie_options: initConfiguration.betaEncodeCookieOptions,
    source: initConfiguration.source,
    sdk_version: initConfiguration.sdkVersion,
    variant: initConfiguration.variant,
  } satisfies RawTelemetryConfiguration
}
