module JamRuby
  class Review < ActiveRecord::Base
    include HtmlSanitize
    html_sanitize strict: [:description]

    attr_accessible :target, :rating, :description, :user, :user_id, :target_id, :target_type
    belongs_to :target, polymorphic: true
    belongs_to :user, foreign_key: 'user_id', class_name: "JamRuby::User"
    belongs_to :deleted_by_user, foreign_key: 'deleted_by_user_id', class_name: "JamRuby::User"

    scope :available, -> { where("deleted_at iS NULL") }

    validates :description, length: {maximum: 16000}, no_profanity: true, :allow_blank => true
    validates :rating, presence: true, numericality: {only_integer: true, minimum: 1, maximum: 5}

    validates :target, presence: true
    validates :user_id, presence: true
    validates :target_id, uniqueness: {scope: :user_id, message: "There is already a review for this User and Target."}
    validate :requires_lesson

    after_save :reduce

    def requires_lesson
      if target_type == 'JamRuby::User'

        # you are rating a student
        lesson = LessonSession.joins(:music_session).where('music_sessions.user_id = ?', target.id).where(teacher_id: user.id).first
        if lesson.nil?
          errors.add(:target, "You must have at least scheduled or been in a lesson with this student")
        end

      elsif target_type == "JamRuby::Teacher"

        # you are rating a teacher
        lesson = LessonSession.joins(:music_session).where('music_sessions.user_id = ?', user.id).where(teacher_id: target.user.id).first
        if lesson.nil?
          errors.add(:target, "You must have at least scheduled or been in a lesson with this teacher")
        end
      end
    end

    def self.create_or_update(params)
      review = Review.where(user_id: params[:user].id).where(target_id: params[:target].id).where(target_type: params[:target].class.to_s).first

      if review
        review.description = params[:description]
        review.rating = params[:rating]
        review.save
      else
        review = Review.create(params)
      end

      review
    end
    def self.create(params)
      review = Review.new
      review.target = params[:target]
      review.user = params[:user]
      review.rating = params[:rating]
      review.description = params[:description]
      review.target_type = params[:target].class.to_s
      review.save
      review
    end

    def self.index(options={})
      if options.key?(:include_deleted)
        arel = Review.all
      else
        arel = Review.available
      end

      if options.key?(:target_id)
        arel = arel.where("target_id=?", options[:target_id])
      end

      if options.key?(:user_id)
        arel = arel.where("user_id=?", options[:user_id])
      end

      arel
    end

    # Create review_summary records by grouping reviews
    def self.reduce_all
      ReviewSummary.transaction do
        ReviewSummary.destroy_all
        Review.select("target_id, target_type AS target_type, AVG(rating) as avg_rating, count(*) as review_count, SUM(CASE WHEN rating>=3.0 THEN 1 ELSE 0 END) AS pos_count")
            .where("deleted_at IS NULL")
            .group("target_type, target_id")
            .each do |r|
          wilson_score = ci_lower_bound(r.pos_count, r.review_count)
          ReviewSummary.create!(
              target_id: r.target_id,
              target_type: r.target_type,
              avg_rating: r.avg_rating,
              wilson_score: wilson_score,
              review_count: r.review_count
          )
        end
      end
    end

    # http://www.evanmiller.org/how-not-to-sort-by-average-rating.html
    def self.ci_lower_bound(pos, n, confidence=0.95)
      pos=pos.to_f
      n=n.to_f
      return 0 if n == 0
      z = 1.96 # Statistics2.pnormaldist(1-(1-confidence)/2)
      phat = 1.0*pos/n
      (phat + z*z/(2*n) - z * Math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)
    end

    def reduce
      ReviewSummary.transaction do
        ReviewSummary.where(target_type: target_type, target_id: target_id).delete_all

        Review.select("target_id, target_type AS target_type, AVG(rating) as avg_rating, count(*) as review_count, SUM(CASE WHEN rating>=3.0 THEN 1 ELSE 0 END) AS pos_count")
            .where("deleted_at IS NULL")
            .where(target_type: target_type, target_id: target_id)
            .group("target_type, target_id")
            .each do |r|
          wilson_score = Review.ci_lower_bound(r.pos_count, r.review_count)

          summary = ReviewSummary.create(
              target_id: r.target_id,
              target_type: r.target_type,
              avg_rating: r.avg_rating,
              wilson_score: wilson_score,
              review_count: r.review_count
          )
          if summary.errors.any?
            puts "review summary unable to be created #{summary.errors.inspect}"
            raise "review summary unable to be created #{summary.errors.inspect}"
          end
        end
      end

      return true
    end
  end
end