require 'spec_helper'
require 'split/alternative'

describe Split::Alternative do

  let(:alternative) {
    Split::Alternative.new('Basket', 'basket_text')
  }

  let(:alternative2) {
    Split::Alternative.new('Cart', 'basket_text')
  }

  let(:experiment) {
    Split::Experiment.find_or_create({"basket_text" => ["purchase", "refund"]}, "Basket", "Cart")
  }

  let(:goal1) { "purchase" }
  let(:goal2) { "refund" }

  # setup experiment
  before do
    experiment
  end

  it "should have goals" do
    alternative.goals.should eql(["purchase", "refund"])
  end

  it "should have a name" do
    alternative.name.should eql('Basket')
  end

  it "return only the name" do
    alternative.name.should eql('Basket')
  end

  describe 'weights' do

    it "should set the weights" do
      experiment = Split::Experiment.new('basket_text', :alternatives => [{'Basket' => 0.6}, {"Cart" => 0.4}])
      first = experiment.alternatives[0]
      first.name.should == 'Basket'
      first.weight.should == 0.6

      second = experiment.alternatives[1]
      second.name.should == 'Cart'
      second.weight.should == 0.4
    end

    it "accepts probability on alternatives" do
      Split.configuration.experiments = {
        :my_experiment => {
          :alternatives => [
            { :name => "control_opt", :percent => 67 },
            { :name => "second_opt", :percent => 10 },
            { :name => "third_opt", :percent => 23 },
          ]
        }
      }
      experiment = Split::Experiment.new(:my_experiment)
      first = experiment.alternatives[0]
      first.name.should == 'control_opt'
      first.weight.should == 0.67

      second = experiment.alternatives[1]
      second.name.should == 'second_opt'
      second.weight.should == 0.1
    end

    it "accepts probability on some alternatives" do
      Split.configuration.experiments = {
        :my_experiment => {
          :alternatives => [
            { :name => "control_opt", :percent => 34 },
            "second_opt",
            { :name => "third_opt", :percent => 23 },
            "fourth_opt",
          ],
        }
      }
      experiment = Split::Experiment.new(:my_experiment)
      alts = experiment.alternatives
      [
        ["control_opt", 0.34],
        ["second_opt", 0.215],
        ["third_opt", 0.23],
        ["fourth_opt", 0.215]
      ].each do |h|
        name, weight = h
        alt = alts.shift
        alt.name.should == name
        alt.weight.should == weight
      end
    end
    #
    it "allows name param without probability" do
      Split.configuration.experiments = {
        :my_experiment => {
          :alternatives => [
            { :name => "control_opt" },
            "second_opt",
            { :name => "third_opt", :percent => 64 },
          ],
        }
      }
      experiment = Split::Experiment.new(:my_experiment)
      alts = experiment.alternatives
      [
        ["control_opt", 0.18],
        ["second_opt", 0.18],
        ["third_opt", 0.64],
      ].each do |h|
        name, weight = h
        alt = alts.shift
        alt.name.should == name
        alt.weight.should == weight
      end
    end
  end

  it "should have a default participation count of 0" do
    alternative.participant_count.should eql(0)
  end

  it "should have a default completed count of 0 for each goal" do
    alternative.completed_count.should eql(0)
    alternative.completed_count(goal1).should eql(0)
    alternative.completed_count(goal2).should eql(0)
  end

  it "should belong to an experiment" do
    alternative.experiment.name.should eql(experiment.name)
  end

  it "should save to redis" do
    alternative.save
    Split.redis.exists('basket_text:Basket').should be true
  end

  it "should increment participation count" do
    old_participant_count = alternative.participant_count
    alternative.increment_participation
    alternative.participant_count.should eql(old_participant_count+1)
  end

  it "should increment completed count for each goal" do
    old_default_completed_count = alternative.completed_count
    old_completed_count_for_goal1 = alternative.completed_count(goal1)
    old_completed_count_for_goal2 = alternative.completed_count(goal2)

    alternative.increment_completion
    alternative.increment_completion(goal1)
    alternative.increment_completion(goal2)

    alternative.completed_count.should eql(old_default_completed_count+1)
    alternative.completed_count(goal1).should eql(old_completed_count_for_goal1+1)
    alternative.completed_count(goal2).should eql(old_completed_count_for_goal2+1)
  end

  it "can be reset" do
    alternative.participant_count = 10
    alternative.set_completed_count(4, goal1)
    alternative.set_completed_count(5, goal2)
    alternative.set_completed_count(6)
    alternative.reset
    alternative.participant_count.should eql(0)
    alternative.completed_count(goal1).should eql(0)
    alternative.completed_count(goal2).should eql(0)
    alternative.completed_count.should eql(0)
  end

  it "should know if it is the control of an experiment" do
    alternative.control?.should be_true
    alternative2.control?.should be_false
  end

  describe 'unfinished_count' do
    it "should be difference between participant and completed counts" do
      alternative.increment_participation
      alternative.unfinished_count.should eql(alternative.participant_count)
    end

    it "should return the correct unfinished_count" do
      alternative.participant_count = 10
      alternative.set_completed_count(4, goal1)
      alternative.set_completed_count(3, goal2)
      alternative.set_completed_count(2)

      alternative.unfinished_count.should eql(1)
    end
  end

  describe 'conversion rate' do
    it "should be 0 if there are no conversions" do
      alternative.completed_count.should eql(0)
      alternative.conversion_rate.should eql(0)
    end

    it "calculate conversion rate" do
      alternative.stub(:participant_count).and_return(10)
      alternative.stub(:completed_count).and_return(4)
      alternative.conversion_rate.should eql(0.4)

      alternative.stub(:completed_count).with(goal1).and_return(5)
      alternative.conversion_rate(goal1).should eql(0.5)

      alternative.stub(:completed_count).with(goal2).and_return(6)
      alternative.conversion_rate(goal2).should eql(0.6)
    end
  end

  describe 'z score' do

    it "should return an error string when the control has 0 people" do
      alternative2.z_score.should eql("Needs 30+ participants.")
      alternative2.z_score(goal1).should eql("Needs 30+ participants.")
      alternative2.z_score(goal2).should eql("Needs 30+ participants.")
    end

    it "should return an error string when the data is skewed or incomplete as per the np > 5 test" do
      control = experiment.control
      control.participant_count = 100
      control.set_completed_count(50)

      alternative2.participant_count = 50
      alternative2.set_completed_count(1)

      alternative2.z_score.should eql("Needs 5+ conversions.")
    end

    it "should return a float for a z_score given proper data" do
      control = experiment.control
      control.participant_count = 120
      control.set_completed_count(20)

      alternative2.participant_count = 100
      alternative2.set_completed_count(25)

      alternative2.z_score.should be_kind_of(Float)
      alternative2.z_score.should_not eql(0)
    end

    it "should correctly calculate a z_score given proper data" do
      control = experiment.control
      control.participant_count = 126
      control.set_completed_count(89)

      alternative2.participant_count = 142
      alternative2.set_completed_count(119)
      
      alternative2.z_score.round(2).should eql(2.58)
    end

    it "should be N/A for the control" do
      control = experiment.control
      control.z_score.should eql('N/A')
      control.z_score(goal1).should eql('N/A')
      control.z_score(goal2).should eql('N/A')
    end
  end
end