import SHA1 from 'crypto-js/sha1';
import {
  ExperimentMetadata,
  ExperimentGroups,
  ExperimentOverrideMapping,
} from 'mweb/common/actions/experiments';

interface RealSHA1 {
  (source: string): { words: number[]; sigBytes: number };
}

/**
 * Generates a random number between 0 and 1 using UUID & device ID as a seed.
 * @param UUID Part of the seed for the random number generator.
 * @param deviceID Part of the seed for the random number generator.
 */
export function getRandomValue(UUID: string, deviceID: string): number {
  const hashedRNGseed = ((SHA1 as any) as RealSHA1)(UUID + deviceID);
  /* tslint:disable-next-line:no-bitwise */
  return (hashedRNGseed.words[0] >>> 0) / Math.pow(2, 32);
}

/**
 * Determines if a user is in the treatment group for an experiment.
 *
 * @param metadata Experiment metadata containing weights for each experiment
 *   group that can be used to determine to which group the bucket belongs.
 * @param bucketId The currently active CDN bucket.
 */
export function isTreatment(
  metadata: ExperimentMetadata,
  bucketId: string,
): boolean {
  return isTreatmentBasedOnRoll(
    metadata,
    getRandomValue(metadata.uuid, bucketId),
  );
}

/**
 * Inner function for isTreatment. Exposed for testing.
 *
 * @param metadata Experiment metadata containing weights for each experiment
 *   group that can be used to determine to which group the bucket belongs.
 * @param userRoll User's random die roll between 0 and 1.
 */
export function isTreatmentBasedOnRoll(
  metadata: ExperimentMetadata,
  userRoll: number,
): boolean {
  const weights = metadata.groups.reduce(
    (prevWeights, group) => ({
      totalWeight: prevWeights.totalWeight + group.weight,
      treatmentWeight:
        group.value === ExperimentGroups.Treatment
          ? group.weight
          : prevWeights.treatmentWeight,
    }),
    { totalWeight: 0, treatmentWeight: 0 },
  );

  const treatmentChance = weights.treatmentWeight / weights.totalWeight || 0;
  return userRoll < treatmentChance;
}

/**
 * Get the experiment group to which the current CDN bucket belongs or the group
 * specified by the overrides if specified.
 *
 * @param metadata Experiment metadata containing weights for each experiment
 *   group that can be used to determine to which group the bucket belongs.
 * @param bucketId The currently active CDN bucket.
 * @param experimentOverrides A mapping of ExperimentUUIDs to ExperimentGroups
 *   that will be used to override the natural bucket experimentation groups.
 */
export function getActiveExperimentGroup(
  metadata: ExperimentMetadata,
  bucketId: string,
  experimentOverrides: ExperimentOverrideMapping,
): ExperimentGroups {
  if (metadata.uuid in experimentOverrides) {
    return experimentOverrides[metadata.uuid];
  }
  return isTreatment(metadata, bucketId)
    ? ExperimentGroups.Treatment
    : ExperimentGroups.Control;
}
