# represenst the type of lesson package
module JamRuby
  class LessonSession < ActiveRecord::Base

    include HtmlSanitize
    html_sanitize strict: [:cancel_message]

    attr_accessor :accepting, :creating, :countering, :countering_flag, :autocanceling, :countered_slot, :countered_lesson, :canceling, :assigned_student


    @@log = Logging.logger[LessonSession]

    delegate :sent_billing_notices, :last_billing_attempt_at, :billing_attempts, :billing_should_retry, :billed_at, :billing_error_detail, :billing_error_reason, :is_card_declined?, :is_card_expired?, :last_billed_at_date, :sent_billing_notices, to: :lesson_payment_charge, allow_nil: true
    delegate :is_test_drive?, :is_single_free?, :is_normal?, :approved_before?, :is_active?, :recurring, :is_monthly_payment?, :school_on_school?, :school_on_school_payment?, :no_school_on_school_payment?, :payment_if_school_on_school?, :scheduling_email, :teacher_school_emails, :school_and_teacher, :school_over_teacher, :school_and_teacher_ids, :school_over_teacher_ids, :posa_card, :remaining_roll_forward_amount_in_cents, :has_recurring_counter?, :ever_accepted?, :has_access?, to: :lesson_booking
    delegate :pretty_scheduled_start, to: :music_session


    STATUS_REQUESTED = 'requested'
    STATUS_CANCELED = 'canceled'
    STATUS_MISSED = 'missed'
    STATUS_COMPLETED = 'completed'
    STATUS_APPROVED = 'approved'
    STATUS_SUSPENDED = 'suspended'
    STATUS_COUNTERED = 'countered'
    STATUS_UNCONFIRMED = 'unconfirmed'

    STATUS_TYPES = [STATUS_REQUESTED, STATUS_CANCELED, STATUS_MISSED, STATUS_COMPLETED, STATUS_APPROVED, STATUS_SUSPENDED, STATUS_COUNTERED, STATUS_UNCONFIRMED]

    LESSON_TYPE_SINGLE = 'paid'
    LESSON_TYPE_SINGLE_FREE = 'single-free'
    LESSON_TYPE_TEST_DRIVE = 'test-drive'
    LESSON_TYPES = [LESSON_TYPE_SINGLE, LESSON_TYPE_SINGLE_FREE, LESSON_TYPE_TEST_DRIVE]

    has_one :music_session, class_name: "JamRuby::MusicSession", :dependent => :destroy
    belongs_to :teacher, class_name: "JamRuby::User", foreign_key: :teacher_id, inverse_of: :taught_lessons
    belongs_to :canceler, class_name: "JamRuby::User", foreign_key: :canceler_id
    belongs_to :counterer, class_name: "JamRuby::User", foreign_key: :counterer_id
    belongs_to :lesson_package_purchase, class_name: "JamRuby::LessonPackagePurchase"
    belongs_to :lesson_booking, class_name: "JamRuby::LessonBooking"
    belongs_to :user, class_name: "JamRuby::User"
    belongs_to :slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :slot_id, :dependent => :destroy
    belongs_to :lesson_payment_charge, class_name: "JamRuby::LessonPaymentCharge", foreign_key: :charge_id
    belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_lesson, :dependent => :destroy
    has_many :teacher_distributions, class_name: "JamRuby::TeacherDistribution", dependent: :destroy
    has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot"
    has_many :notifications, :class_name => "JamRuby::Notification", :foreign_key => "lesson_session_id"
    has_many :chat_messages, :class_name => "JamRuby::ChatMessage", :foreign_key => "lesson_session_id"


    validates :duration, presence: true, numericality: {only_integer: true}
    validates :lesson_booking, presence: true
    validates :lesson_type, inclusion: {in: LESSON_TYPES}
    validates :booked_price, presence: true
    validates :status, presence: true, inclusion: {in: STATUS_TYPES}
    validates :teacher_complete, inclusion: {in: [true, false]}
    validates :student_complete, inclusion: {in: [true, false]}
    validates :teacher_canceled, inclusion: {in: [true, false]}
    validates :student_canceled, inclusion: {in: [true, false]}
    validates :success, inclusion: {in: [true, false]}
    validates :sent_notices, inclusion: {in: [true, false]}
    validates :post_processed, inclusion: {in: [true, false]}

    validate :validate_creating, :if => :creating
    validate :validate_countering, :if => :countering_flag
    validate :validate_accepted, :if => :accepting
    validate :validate_canceled, :if => :canceling
    validate :validate_autocancel, :if => :autocanceling

    after_save :manage_slot_changes
    after_create :create_charge

    scope :approved, -> { where(status: STATUS_APPROVED) }
    scope :requested, -> { where(status: STATUS_REQUESTED) }
    scope :canceled, -> { where(status: STATUS_CANCELED) }
    scope :suspended, -> { where(status: STATUS_SUSPENDED) }
    scope :completed, -> { where(status: STATUS_COMPLETED) }
    scope :missed, -> { where(status: STATUS_MISSED) }
    scope :upcoming, -> { joins(:music_session).where('music_sessions.scheduled_start > ?', Time.now) }
    scope :past_cancel_window, -> { joins(:music_session).where('music_sessions.scheduled_start > ?', 24.hours.from_now) }
    # show all requested/countered sessions where the student was the last to communicate
    scope :slow_responses, -> { joins(:lesson_booking).where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED)
                                    .where("(? - (COALESCE(lesson_sessions.countered_at, lesson_bookings.sent_notices_at))) > INTERVAL '72 hours'", Time.now)
                                    .order('(COALESCE(lesson_sessions.countered_at, lesson_bookings.sent_notices_at)) ASC') }

    def create_charge
      if payment_if_school_on_school? && !is_test_drive? && !is_monthly_payment?
        self.lesson_payment_charge = LessonPaymentCharge.new
        lesson_payment_charge.user = @assigned_student
        lesson_payment_charge.amount_in_cents = 0
        lesson_payment_charge.fee_in_cents = 0
        lesson_payment_charge.lesson_session = self
        lesson_payment_charge.save!
      end
    end

    def teacher_distribution
      teacher_distributions.where(education: false).where('retailer_id is null').first
    end

    def education_distribution
      teacher_distributions.where(education: true).first
    end

    def retailer_distribution
      teacher_distributions.where('retailer_id is not null').first
    end

    def manage_slot_changes
      # if this slot changed, we need to update the time.  But LessonBooking does this for us, for requested/accepted .
      # TODO: what to do, what to do.

    end

    def suspend!
      self.status = STATUS_SUSPENDED
      self.save
    end

    def unsuspend!
      self.status = STATUS_APPROVED
      self.save
    end

    def music_session_id
      music_session.id
    end

    def self.hourly_check
      auto_cancel
      analyse_sessions
      complete_sessions
      remind_counters
      remind_counters_recurring
      onboarding_email_reminders
    end

    def self.minutely_check
      upcoming_sessions_reminder
      upcoming_sessions_reminder_early
    end

    def self.remind_counters
      MusicSession.joins(lesson_session: :lesson_booking)
          .where('lesson_bookings.recurring = FALSE')
          .where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED)
          .where("? > (COALESCE(lesson_sessions.sent_counter_reminder_at, lesson_sessions.countered_at, lesson_bookings.sent_notices_at) + (INTERVAL '24 hours'))", Time.now).each do |music_session|
        lesson_session = music_session.lesson_session
        if lesson_session.student_last_proposed?
          relevant_time = [lesson_session.countered_at, lesson_session.lesson_booking.sent_notices_at].find { |x| !x.nil? }

          UserMailer.student_no_comm_other(lesson_session.lesson_booking, relevant_time < 3.days.ago).deliver_now
          UserMailer.teacher_counter_reminder(lesson_session).deliver_now
        else
          UserMailer.teacher_no_comm_other(lesson_session.lesson_booking).deliver_now
          UserMailer.student_counter_reminder(lesson_session).deliver_now
        end
        lesson_session.sent_counter_reminder_at = Time.now
        lesson_session.save(validate: false)
      end
    end

    def self.remind_counters_recurring
      MusicSession.joins(lesson_session: :lesson_booking)
          .where("lesson_bookings.status = ? AND lesson_bookings.sent_counter_reminder = false AND lesson_bookings.recurring = TRUE", LessonBooking::STATUS_COUNTERED)
          .where("? > (COALESCE(lesson_sessions.countered_at, lesson_bookings.sent_notices_at) + (INTERVAL '24 hours'))", Time.now).each do |music_session|
        lesson_session = music_session.lesson_session
        if lesson_session.student_last_proposed?
          UserMailer.teacher_counter_reminder(lesson_session).deliver_now
        else
          UserMailer.student_counter_reminder(lesson_session).deliver_now
        end
        lesson_session.lesson_booking.sent_counter_reminder = true
        lesson_session.lesson_booking.save(validate: false)
      end
    end

    def self.remindable_onboarding_users
      User.where('first_onboarding_paid_lesson_at IS NULL AND onboarding_onboarded_at IS NOT NULL')
          .where('stuck_take_flesson = FALSE and stuck_take_2nd_flesson = FALSE and stuck_take_plesson = FALSE')
    end

    def self.onboarding_email_reminders
      remindable_onboarding_users.each do |user|
        if user.second_onboarding_free_lesson_at && user.sent_take_plesson_email_times < 4

          last_sent = user.sent_take_plesson_email_at || user.second_onboarding_free_lesson_at
          if last_sent < 2.days.ago
            if user.sent_take_plesson_email_times == 3
              User.where(id: user.id).update_all(stuck_take_plesson: true)
            else
              UserMailer.take_paid_lesson(user).deliver_now
              User.where(id: user.id).update_all(sent_take_plesson_email_times: user.sent_take_plesson_email_times + 1, sent_take_plesson_email_at: Time.now)
            end
          end

        elsif user.first_onboarding_free_lesson_at && user.sent_take_2nd_flesson_email_times < 4
          last_sent = user.sent_take_2nd_flesson_email_at || user.first_onboarding_free_lesson_at
          if last_sent < 2.days.ago
            if user.sent_take_2nd_flesson_email_times == 3
              User.where(id: user.id).update_all(stuck_take_2nd_flesson: true)
            else
              UserMailer.take_second_free_lesson(user).deliver_now
              User.where(id: user.id).update_all(sent_take_2nd_flesson_email_times: user.sent_take_2nd_flesson_email_times + 1, sent_take_2nd_flesson_email_at: Time.now)
            end
          end

        elsif user.sent_take_flesson_email_times < 4
          last_sent = user.sent_take_flesson_email_at || user.onboarding_onboarded_at
          if last_sent < 2.days.ago
            if user.sent_take_flesson_email_times == 3
              User.where(id: user.id).update_all(stuck_take_flesson: true)
            else
              UserMailer.take_first_free_lesson(user).deliver_now
              User.where(id: user.id).update_all(sent_take_flesson_email_times: user.sent_take_flesson_email_times + 1, sent_take_flesson_email_at: Time.now)
            end
          end
        end
      end
    end


    # give 2 days to auto-cancel; this lets people just jump in a session and mark it done
    def self.auto_cancel
      MusicSession.joins(lesson_session: :lesson_booking).where('lesson_sessions.status = ? OR lesson_bookings.status = ?', LessonSession::STATUS_REQUESTED, LessonBooking::STATUS_COUNTERED).where("? > scheduled_start + (INTERVAL '2 days') + (INTERVAL '1 minutes' * (duration))", Time.now).each do |music_session|
        lesson_session = music_session.lesson_session
        lesson_session.autocancel
      end
    end

    def self.analyse_sessions
      MusicSession.joins(lesson_session: :lesson_booking).where('(lesson_sessions.status = ? OR lesson_sessions.status = ?) AND lesson_bookings.status != ?', LessonSession::STATUS_APPROVED, LessonBooking::STATUS_REQUESTED, LessonBooking::STATUS_COUNTERED).where("? > scheduled_start + (INTERVAL '1 minutes' * (duration))", Time.now).where('analysed = false').each do |music_session|
        lesson_session = music_session.lesson_session
        lesson_session.analyse
      end
    end

    def self.complete_sessions
      # this will find any paid session (recurring monthly paid, recurring single paid, single paid)
      MusicSession.joins(lesson_session: [:lesson_booking, :lesson_payment_charge]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where("? > scheduled_start + (INTERVAL '1 minutes' * (duration))", Time.now).where('analysed = true').where('lesson_sessions.post_processed = false').where('billing_should_retry = true').each do |music_session|
        lesson_session = music_session.lesson_session
        lesson_session.session_completed
      end

      # test drives don't have a lesson_payment_charge, so we don't join against them
      MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_COMPLETED).where('lesson_sessions.lesson_type = ?', LESSON_TYPE_TEST_DRIVE).where("? > scheduled_start + (INTERVAL '1 minutes' * duration)", Time.now).where('analysed = true').where('lesson_sessions.post_processed = false').each do |music_session|
        lession_session = music_session.lesson_session
        lession_session.session_completed
      end
    end

    def self.upcoming_sessions_reminder
      now = Time.now
      half_hour_from_now = 30.minutes.from_now
      if Time.zone
        now = Time.zone.local_to_utc(now)
        half_hour_from_now = Time.zone.local_to_utc(half_hour_from_now)
      end



      MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where('sent_starting_notice = false').where('(scheduled_start > ? and scheduled_start < ?)', now, half_hour_from_now).each do |music_session|
        lession_session = music_session.lesson_session
        lession_session.send_starting_notice(false)
      end
    end

    def self.upcoming_sessions_reminder_early
      now = Time.now
      two_hours_from_now = 120.minutes.from_now
      if Time.zone
        now = Time.zone.local_to_utc(now)
        two_hours_from_now = Time.zone.local_to_utc(two_hours_from_now)
      end

      MusicSession.joins(lesson_session: [:lesson_booking]).where('lesson_sessions.status = ?', LessonSession::STATUS_APPROVED).where('sent_starting_notice = false').where('sent_early_starting_notice = false').where('(scheduled_start > ? and scheduled_start < ?)', now, two_hours_from_now).each do |music_session|
        lession_session = music_session.lesson_session
        lession_session.send_starting_notice(true)
      end
    end

    def student_last_proposed?
      counterer_id.nil? || counterer_id == student_id
    end

    def late_responder
      if student_last_proposed?
        teacher
      else
        student
      end
    end

    def last_proposer
      if student_last_proposed?
        student
      else
        teacher
      end
    end

    def lesson_is_free?
      lesson_booking.booked_price == 0.00
    end

    def mark_lesson(success, administratively_marked = false)
      if !self.success && (is_requested? || is_unconfirmed?)
        # what's going on here is that we will just mark a session as a success if the the people got in in case of requested sessions
        # but otherwise, leave it alone, so that the people can after-the-fact reschedule it (don't close it out and treat as a failure)
        return
      end

      self.success = success
      self.analysed_at = Time.now
      self.analysed = true
      self.status = STATUS_COMPLETED
      self.admin_marked = administratively_marked


      if success && lesson_booking.requires_teacher_distribution?(self)

        if lesson_booking.posa_card.nil? && lesson_booking.payment
          # if there is a payment object, it will describe how everything gets doled out
          payment = JSON.parse(lesson_booking.payment)

          teacher_split = payment["teacher"]
          retailer_split = payment["retailer"]

          retailer_rate = teacher.teacher.retailer.jamkazam_rate + APP_CONFIG.stripe[:charge_fee] # add 0.03 to account for stripe deduction

          # but we must also take the correct rate out of the

          if is_test_drive?
            # test drives do not get subject to split behavior, per quick convo with David
            self.teacher_distributions << TeacherDistribution.create_for_lesson(self, false)
          else

            if teacher_split && teacher_split > 0
              teacher_dist = TeacherDistribution.create_for_lesson(self, false, (teacher_split / 100.0).round(2), retailer_rate)
              self.teacher_distributions << teacher_dist
            end

            if retailer_split && retailer_split > 0
              teacher_dist = TeacherDistribution.create_for_lesson(self, false, (retailer_split / 100.0).round(2), retailer_rate)
              teacher_dist.retailer = teacher.teacher.retailer
              teacher_dist.save
            end
          end
        else
          is_school_on_school = lesson_booking.school_on_school_payment?

          self.teacher_distributions << TeacherDistribution.create_for_lesson(self, false)
          if is_school_on_school && lesson_booking.school.education
            self.teacher_distributions << TeacherDistribution.create_for_lesson(self, true)
          end
        end
      end

      if self.save
        # send out emails appropriate for this type of session
        session_completed
      end
    end

    def analyse
      if self.analysed
        return
      end

      analysis = LessonSessionAnalyser.analyse(self)

      self.analysis = LessonSession.analysis_to_json(analysis)

      if analysis[:reason] == LessonSessionAnalyser::SESSION_ONGOING
        # extra protection against bad code somewhere
        return
      end


      # record how it went
      if analysis[:student_analysis] && analysis[:student_analysis][:total_time] > 0
        if lesson_is_free?
          if student.first_onboarding_free_lesson_at.nil?
            student.first_onboarding_free_lesson_at = Time.now
            student.save
          elsif student.second_onboarding_free_lesson_at.nil?
            student.second_onboarding_free_lesson_at = Time.now
            student.save
          end
        else
          if student.first_onboarding_paid_lesson_at.nil?
            student.first_onboarding_paid_lesson_at = Time.now
            student.save
          end
        end
      end

      mark_lesson(analysis[:bill])
    end

    def billed
      if lesson_booking.is_test_drive?
        false
      elsif lesson_payment_charge
        lesson_payment_charge.billed
      else
        false
      end
    end

    def amount_charged
      if lesson_payment_charge
        lesson_payment_charge.amount_in_cents / 100.0
      else
        0.0
      end
    end

    def self.analysis_to_json(analysis, preserve_object = false)
      json = {}

      analysis.each do |k, v|
        if v.is_a?(Array)
          array = []
          v.each do |item|
            if item.is_a?(Range)
              array << {begin: item.begin, end: item.end}
            else
              raise "expected range"
            end
          end
          json[k] = array
        else
          json[k] = v
        end
      end
      if preserve_object
        json
      else
        json.to_json
      end
    end

    def send_starting_notice(early_notice)
      UserMailer.lesson_starting_soon_student(self, early_notice, student.local_hour_stmt(scheduled_start)).deliver_now
      UserMailer.lesson_starting_soon_teacher(self, early_notice, teacher.local_hour_stmt(scheduled_start)).deliver_now

      if early_notice
        self.sent_early_starting_notice = true
      else
        self.sent_starting_notice = true
      end


      self.save(validate: false)
    end

    def video_uploaded(user, recording, data)

      claim = recording.claim_for_user(user)
      msg = ChatMessage.create(user, nil, "'#{claim.name}'", ChatMessage::CHANNEL_LESSON, nil, other_user(user), self, "Video Uploaded", nil, claim)
      Notification.send_lesson_message('video uploaded', self, false)
      Notification.send_lesson_message('video uploaded', self, true)
    end

    def other_user(user)
      if user.id == student.id
        teacher
      else
        student
      end
    end

    def session_completed
      LessonSession.transaction do
        self.lock!

        if post_processed
          # nothing to do.  because this is an async job, it's possible this was queued up with another session_completed fired
          return
        end

        if lesson_booking.is_test_drive?
          test_drive_completed
        elsif lesson_booking.is_normal?
          if lesson_booking.is_weekly_payment? || lesson_booking.is_monthly_payment?
            recurring_completed
          else
            normal_lesson_completed
          end
        end

      end
    end

    def bill_lesson
      if no_school_on_school_payment?
        success = true
      else
        lesson_payment_charge.charge
        success = lesson_payment_charge.billed
      end

      if success
        self.sent_notices = true
        self.sent_notices_at = Time.now
        self.post_processed = true
        self.post_processed_at = Time.now
        self.save(:validate => false)
      end
    end

    def student_missed
      analysed && !success && analysis_json["student_analysis"]["missed"]
    end

    def teacher_missed
      analysed && !success && analysis_json["teacher_analysis"]["missed"]
    end

    def error_display
      if analysed
        if !success
          if student_missed && teacher_missed
            "both the student and teacher missed the lesson"
          elsif teacher_missed
            "the teacher missed the lesson"
          elsif student_missed
            "the student missed the lesson"
          else
            "unknown error reason"
          end

        else
          "because no error"
        end
      else
        "not yet analysed"
      end

    end


    def test_drive_completed

      if !sent_notices
        if success
          teacher_distributions.update_all(ready: true) # possibly there are 0 distributions on this lesson
          student.test_drive_succeeded(self)
        else
          student.test_drive_failed(self)
        end

        self.lesson_booking.success = success
        self.lesson_booking.status = STATUS_COMPLETED
        self.lesson_booking.save(:validate => false)
        self.sent_notices = true
        self.sent_notices_at = Time.now
        self.post_processed = true
        self.post_processed_at = Time.now
        self.save(:validate => false)
      end
    end

    def recurring_completed
      if success

        if lesson_booking.is_monthly_payment?
          # monthly payments are handled at beginning of month; just poke with email, and move on

          if !sent_notices
            # not in spec; just poke user and tell them we saw it was successfully completed
            UserMailer.monthly_recurring_done(self).deliver_now

            self.sent_notices = true
            self.sent_notices_at = Time.now
            self.post_processed = true
            self.post_processed_at = Time.now
            self.save(:validate => false)
          end
        else
          bill_lesson
        end
      else
        if lesson_booking.is_monthly_payment?
          if !sent_notices
            if payment_if_school_on_school?
              # bad session; just poke user
              UserMailer.monthly_recurring_no_bill(self).deliver_now
            end

            self.sent_notices = true
            self.sent_notices_at = Time.now
            self.post_processed = true
            self.post_processed_at = Time.now
            self.save(:validate => false)
          end

        else
          if !sent_notices
            if payment_if_school_on_school?
              # bad session; just poke user
              UserMailer.student_lesson_normal_no_bill(self).deliver_now
            end

            self.sent_notices = true
            self.sent_notices_at = Time.now
            self.post_processed = true
            self.post_processed_at = Time.now
            self.save(:validate => false)
          end

        end
      end
    end

    def normal_lesson_completed
      if success
        bill_lesson
      else
        if !sent_notices
          if payment_if_school_on_school?
            UserMailer.student_lesson_normal_no_bill(self).deliver_now
            UserMailer.teacher_lesson_normal_no_bill(self).deliver_now
          end

          self.sent_notices = true
          self.sent_notices_at = Time.now
          self.post_processed = true
          self.post_processed_at = Time.now
          self.save(:validate => false)
        end
      end

      self.lesson_booking.success = success
      self.lesson_booking.status = STATUS_COMPLETED
      self.lesson_booking.save(:validate => false)
    end

    def scheduled_start
      if music_session
        music_session.scheduled_start
      else
        raise "lesson session #{id} has no music session in scheduled_start"
      end

    end

    def send_counter_for_lesson(countered_lesson, countered_slot)
      if !lesson_booking.errors.any?
        if countered_slot.is_teacher_created?
          UserMailer.student_lesson_counter(countered_lesson, countered_slot).deliver_now
        else
          UserMailer.teacher_lesson_counter(countered_lesson, countered_slot).deliver_now
        end
      end
      self.countering = false
    end

    def send_counter_for_lesson_recurring(countered_booking, countered_slot)
      if !countered_booking.errors.any?
        if countered_slot.is_teacher_created?
          UserMailer.student_lesson_counter_recurring(countered_booking, countered_slot).deliver_now
        else
          UserMailer.teacher_lesson_counter_recurring(countered_booking, countered_slot).deliver_now
        end
      end
      self.countering = false
    end

    default_scope { order('lesson_sessions.created_at') }

    def is_requested?
      status == STATUS_REQUESTED
    end

    def is_canceled?
      status == STATUS_CANCELED
    end

    def is_completed?
      status == STATUS_COMPLETED
    end

    def is_approved?
      status == STATUS_APPROVED
    end

    def is_suspended?
      status == STATUS_SUSPENDED
    end

    def is_countered?
      status == STATUS_COUNTERED
    end

    def is_unconfirmed?
      status == STATUS_UNCONFIRMED
    end

    def analysis_json
      @parsed_analysis || analysis
    end

    def validate_creating
      if !is_requested? && !is_approved?
        self.errors.add(:status, "is not valid for a new lesson session.")
      end

      if is_approved? && lesson_booking && lesson_booking.is_test_drive? && lesson_package_purchase.nil?
        self.errors.add(:lesson_package_purchase, "must be specified for a test drive purchase")
      end
    end

    def validate_countering

      if counter_slot.nil?
        errors.add(:counter_slot, "must be specified")
      elsif !approved_before? && (status == STATUS_REQUESTED || status == STATUS_COUNTERED)
        if recurring && !counter_slot.update_all
          errors.add(:counter_slot, "Only 'update all' counter-proposals are allowed for un-approved, recurring lessons")
        end

        if recurring && !counter_slot.is_recurring?
          errors.add(:counter_slot, "Only 'recurring' counter-proposals are allowed for un-approved, recurring lessons")
        end
      end

      self.countering_flag = false
    end

    def validate_accepted
      if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED
        self.errors.add(:status, "This session is already #{self.status_was}.")
      end

      # if this is a single lesson (testdrive, paid), but the scheduled time is before now, then it's in the past, and we have to reject
      if !slot.is_recurring? && self.slot.scheduled_time(0) <= duration.minutes.ago # let someone accept a very late meeting so that they can just hope in the session and do it
        self.errors.add(:slot, "is in the past")
      end

      if approved_before?
        # only checking for this on 1st time through acceptance
        if lesson_booking && lesson_booking.is_test_drive? && lesson_package_purchase.nil?
          self.errors.add(:lesson_package_purchase, "must be specified for a test drive purchase")
        end
      end

      self.accepting = false
    end

    def validate_autocancel

      if scheduled_start + (duration * 60) > Time.now
        self.errors.add(:status, "This session is not in the past.")
      end

      if self.status_was != STATUS_REQUESTED && !self.lesson_booking.is_countered?
        self.errors.add(:status, "This session is #{self.status_was}/booking=#{lesson_booking.status} and can not be autocanceled")
      end

      self.autocanceling = false
    end

    def validate_canceled
      if !is_canceled?
        self.errors.add(:status, "This session is already #{self.status}.")
      end

      # check 24 hour window

      if ever_accepted? && !student_short_canceled && !teacher_short_canceled && scheduled_start.to_i - Time.now.to_i < 24 * 60 * 60
        self.errors.add(:base, "This session is due to start within 24 hours and can not be canceled.")
      end

      self.canceling = false
    end

    def self.create(booking)
      lesson_session = LessonSession.new
      lesson_session.creating = true
      lesson_session.duration = booking.lesson_length
      lesson_session.lesson_type = booking.lesson_type
      lesson_session.lesson_booking = booking
      lesson_session.booked_price = booking.booked_price
      lesson_session.teacher = booking.teacher
      lesson_session.status = booking.status
      lesson_session.slot = booking.default_slot
      lesson_session.assigned_student = booking.student
      lesson_session.user = booking.student
      if booking.is_test_drive?
        if booking.student.jamclass_credits > 0
          lesson_session.lesson_package_purchase = booking.student.most_recent_posa_purchase
        elsif booking.student.remaining_test_drives > 0
          lesson_session.lesson_package_purchase = booking.student.most_recent_test_drive_purchase
        end
      end
      lesson_session.save

      if lesson_session.errors.any?
        puts "Lesson Session errors #{lesson_session.errors.inspect}"
      else
        begin
        lesson_type = 'unknown'
        if booking.is_test_drive?
          if booking.student.first_onboarding_free_lesson_at
            lesson_type = 'second free lesson'
          else
            lesson_type = 'first free lesson'
          end
        elsif lesson_session.slot.is_recurring?
          lesson_type = 'monthly lesson'
        else
          lesson_type = 'single paid lesson'
        end

        updated = User.where(id: booking.student.id).where('first_lesson_booked_at is NULL').update_all(first_lesson_booked_at: Time.now)

        if updated == 1
          UserMailer.amazon_first_lesson_scheduled(booking.student).deliver_now
        end

        body = "Student has requested a #{lesson_type}!\n\nUser: #{booking.student.name}, #{booking.student.email}\nUser Admin URL:#{booking.student.admin_url}\n\n"
        body += "Lesson Session URL: #{lesson_session.admin_url}"
        AdminMailer.ugly({to: APP_CONFIG.email_support_alias,
                          subject:"Student '#{booking.student.name}' requested #{lesson_type}!",
                          body: body}).deliver_now
        rescue => e
          puts "Unable to alert that student requested lesson"
          puts e
        end

      end

      lesson_session
    end

    def student
      music_session.creator
    end

    def student_id
      music_session.creator.id
    end

    def self.index(user, params = {})
      limit = params[:per_page]
      limit ||= 100
      limit = limit.to_i

      school_owner = user.owned_school && user.owned_school.scheduling_comm?

      is_teacher = !user.teacher.nil?

      query = LessonSession.unscoped.joins([:music_session, :lesson_booking]).joins(music_session: :creator)
      #query = query.includes([:teacher, :music_session])
      query = query.includes([:music_session])
      query = query.order('music_sessions.scheduled_start DESC')


      if params[:as_teacher].present?
        if school_owner || params[:as_teacher] == true || params[:as_teacher] == "true"

          # if a school owner is viewing, grab his owned school ID.  Otherwise use a teacher's school ID, if applicable
          school_id = school_owner ? user.owned_school.id : (user.teacher ? user.teacher.school_id : nil)

          if school_id
            school_extra = "OR (lesson_bookings.school_id = '#{school_id}')"
          else
            school_extra = ''
          end

          if school_owner
            extra_teacher= ''
            if is_teacher
              # if the school owner is a teacher, show his bookings too
              extra_teacher = " OR lesson_sessions.teacher_id = '#{user.teacher.id}'"
            end
            query = query.where('lesson_sessions.teacher_id in (?)' + extra_teacher, user.owned_school.teachers.map { |t| t.user.id })
            query = query.where('lesson_sessions.status = ? OR lesson_sessions.status = ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED)
          else
            # this is a normal teacher (not a school owner)
            if school_id && user.teacher.school.scheduling_comm?
              # the school wants to control scheduling communication. So hide sessions in the requested/countered status from them
              query = query.where('lesson_sessions.status != ? AND lesson_sessions.status != ?', LessonSession::STATUS_REQUESTED, LessonSession::STATUS_COUNTERED)
            end
            query = query.where('lesson_sessions.teacher_id = ?', user.id)
          end

        else
          if user.school_id
            school_extra = "OR (lesson_bookings.school_id = '#{user.school_id}')"
          else
            school_extra = ''
          end


          query = query.where('music_sessions.user_id = ?', user.id)
        end
      else

        if user.school_id
          school_extra = "OR (lesson_bookings.school_id = '#{user.school_id}')"
        else
          school_extra = ''
        end

        query = query.where('(lesson_sessions.teacher_id = ? or music_sessions.user_id = ?)', user.id, user.id)
      end

      # only show 'fully booked lessons'; not those they can not possibly be paid for
      query = query.where('(lesson_bookings.posa_card_id IS NOT NULL AND lesson_bookings.posa_card_purchased) OR lesson_bookings.card_presumed_ok = true OR (music_sessions.user_id = ?) ' + school_extra, user.id)

      current_page = params[:page].nil? ? 1 : params[:page].to_i
      next_page = current_page + 1

      # will_paginate gem
      query = query.paginate(:page => current_page, :per_page => limit)

      if query.length == 0 # no more results
        {query: query, next_page: nil}
      elsif query.length < limit # no more results
        {query: query, next_page: nil}
      else
        {query: query, next_page: next_page}
      end
    end

    def update_scheduled_start(week_offset)
      music_session.scheduled_start = slot.scheduled_time(week_offset)
      music_session.save!
    end

    # grabs the next available time that's after the present, to avoid times being scheduled in the past
    def update_next_available_time(attempt = 0)
      max_attempts = attempt + 10
      while attempt < max_attempts
        test = slot.scheduled_time(attempt)

        if test >= Time.now
          time = test
          # valid time found!
          break
        end
        attempt += 1
      end

      if time
        music_session.scheduled_start = time
        music_session.save
      end

      time.nil? ? nil : attempt
    end

    def school_owner_id
      school = teacher.teacher.school
      if school
        school.owner.id
      end
    end

    def access?(user)
      user.id == music_session.user_id || user.id == teacher.id || user.id == school_owner_id
    end

    # teacher accepts the lesson
    def accept(params)
      response = self
      LessonSession.transaction do

        message = params[:message]
        slot = params[:slot]
        accepter = params[:accepter]
        raise "LessonBookingSlot" if slot.is_a?(LessonBookingSlot)
        slot = LessonBookingSlot.find(slot)

        self.slot.accept_message = message
        self.slot.save

        if !slot.is_recurring?
          self.slot = slot
          self.accepting = true
          self.status = STATUS_APPROVED
        end

        if slot.is_recurring? && !approved_before?
          # we need to approve the lessons, cuz the booking is going to be approved too with this.
          #self.slot = slot
          self.accepting = true
          self.status = STATUS_APPROVED
        end

        if !approved_before?
          # 1st time this has ever been approved; there are other things we need to do

          if lesson_package_purchase.nil? && lesson_booking.is_test_drive?
            if student.jamclass_credits > 0
              self.lesson_package_purchase = student.most_recent_posa_purchase
            elsif student.remaining_test_drives > 0
              self.lesson_package_purchase = student.most_recent_test_drive_purchase
            end
          end

          if self.save
            # also let the lesson_booking know we got accepted
            if !lesson_booking.accept(self, slot, accepter)
              response = lesson_booking
              raise ActiveRecord::Rollback
            end
            UserMailer.student_lesson_accepted(self, message, slot).deliver_now
            UserMailer.teacher_lesson_accepted(self, message, slot).deliver_now
            message = '' if message.nil?
            msg = ChatMessage.create(teacher, nil, message, ChatMessage::CHANNEL_LESSON, nil, student, self, "Lesson Approved")
            Notification.send_jamclass_invitation_teacher(music_session, teacher)
            Notification.send_student_jamclass_invitation(music_session, student)
            Notification.send_lesson_message('accept', self, true)

          else
            @@log.error("unable to accept slot #{slot.id} for lesson #{self.id}")
            puts("unable to accept slot #{slot.id} for lesson #{self.id}")
            response = self
            raise ActiveRecord::Rollback
          end
        else
          # this implies a new slot has been countered, and now approved

          if self.save
            if slot.is_recurring?
              if !lesson_booking.accept(self, slot, accepter)
                response = lesson_booking
                raise ActiveRecord::Rollback
              end
              message = '' if message.nil?
              msg = ChatMessage.create(slot.proposer, nil, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "All Lesson Times Updated")
              Notification.send_lesson_message('accept', self, true) # TODO: this isn't quite an 'accept'
              UserMailer.student_lesson_update_all(self, message, slot).deliver_now
              UserMailer.teacher_lesson_update_all(self, message, slot).deliver_now
            else
              # nothing to do with the original booking (since we are not changing all times), so we update just ourself
              time = update_next_available_time # XXX: week offset as 0? This *could* still be in the past.  But the user is approving it.   So do we just trust them and get out of their way?

              if time.nil?
                @@log.error("unable to accept slot #{slot.id} for lesson #{self.id} because it's in the past")
                puts("unable to accept slot #{slot.id} for lesson #{self.id} because it's in the past")
                raise ActiveRecord::Rollback
              end
              message = '' if message.nil?
              msg = ChatMessage.create(slot.proposer, nil, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "Lesson Updated Time Approved")
              UserMailer.student_lesson_accepted(self, message, slot).deliver_now
              UserMailer.teacher_lesson_accepted(self, message, slot).deliver_now
            end
          else
            @@log.error("unable to accept slot #{slot.id} for lesson #{self.id} #{errors.inspect}")
            puts("unable to accept slot #{slot.id} for lesson #{self.id} #{errors.inspect}")
            response = self
            raise ActiveRecord::Rollback
          end
        end
      end
      response
    end

    def counter(params)
      response = self
      LessonSession.transaction do
        proposer = params[:proposer]
        slot = params[:slot]
        message = params[:message]

        slot.proposer = proposer
        slot.lesson_session = self
        slot.message = message

        if !slot.is_recurring?
          self.countering = true
          self.countering_flag = true
          self.counterer = proposer
          self.countered_at = Time.now
          self.lesson_booking_slots << slot
          self.countered_lesson = self
          self.status = STATUS_COUNTERED
          self.sent_counter_reminder = false
          #if !update_all
          self.counter_slot = slot
          self.countered_slot = slot
        end

        #end
        if self.save
          proposer.update_timezone(slot.timezone) if proposer

          #if update_all && !lesson_booking.counter(self, proposer, slot)
          if !lesson_booking.counter(self, proposer, slot)
            response = lesson_booking
            raise ActiveRecord::Rollback
          end
        else
          response = self
          raise ActiveRecord::Rollback
        end

        if slot.is_recurring?
          send_counter_for_lesson_recurring(lesson_booking, lesson_booking.counter_slot)
          message = '' if message.nil?
          msg = ChatMessage.create(slot.proposer, music_session, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "New Time Proposed")
          Notification.send_lesson_message('counter', self, slot.is_teacher_created?)
        else

          send_counter_for_lesson(@countered_lesson, @countered_slot)
          message = '' if message.nil?
          msg = ChatMessage.create(slot.proposer, music_session, message, ChatMessage::CHANNEL_LESSON, nil, slot.recipient, self, "New Time Proposed")
          Notification.send_lesson_message('counter', self, slot.is_teacher_created?)
        end

      end

      response
    end

    def cancel_lesson(canceler, message, canceled_by_admin = false)
      canceled_by_student = canceler == student

      self.status = STATUS_CANCELED
      self.cancel_message = message
      self.canceler = canceler if !canceled_by_admin
      self.canceling = true

      if canceled_by_admin

      elsif canceled_by_student
        self.student_canceled = true
        self.student_canceled_at = Time.now
        self.student_canceled_reason = message
        self.student_short_canceled = in_no_cancel_window && ever_accepted?
      else
        self.teacher_canceled = true
        self.teacher_canceled_at = Time.now
        self.teacher_canceled_reason = message
        self.teacher_short_canceled = in_no_cancel_window && ever_accepted?
      end
    end


    def in_no_cancel_window
      Time.now > scheduled_start - 24.hours
    end

    # canceled by the system because it is requested, and the end time has gone by
    def autocancel
      response = self
      LessonSession.transaction do
        self.autocanceling = true
        self.status = LessonSession::STATUS_UNCONFIRMED
        if self.save
          if !lesson_booking.recurring
            response = lesson_booking.autocancel
            if response.errors.any?
              raise ActiveRecord::Rollback
            end
          end
        else
          @@log.error("unable to autocancel lesson #{self.id} #{self.errors.inspect}")
          raise ActiveRecord::Rollback
        end

        msg = ChatMessage.create(teacher, nil, '', ChatMessage::CHANNEL_LESSON, nil, student, self, "Lesson Timeout")
        msg = ChatMessage.create(student, nil, '', ChatMessage::CHANNEL_LESSON, nil, teacher, self, "Lesson Timeout")
      end
    end

    # teacher accepts the lesson
    def cancel(params)
      response = self
      LessonSession.transaction do

        self.status = LessonSession::STATUS_CANCELED
        canceler = params[:canceler]
        canceled_by_student = canceler == student
        other = canceled_by_student ? teacher : student
        message = params[:message]
        message = '' if message.nil?
        if params[:canceled_by_admin]
          self.canceled_by_admin = Time.now
        end


        if lesson_booking.recurring
          update_all = params[:update_all]
        else
          update_all = true
        end

        if lesson_booking.is_test_drive?
          student.test_drive_declined(self)
        end

        if update_all
          response = lesson_booking.cancel(canceler, other, message, params[:canceled_by_admin])
          if response.errors.any?
            raise ActiveRecord::Rollback
          end

          if !save
            response = self
            raise ActiveRecord::Rollback
          end
        else
          cancel_lesson(canceler, message, params[:canceled_by_admin])
          if !save
            response = self
            raise ActiveRecord::Rollback
          end

          if canceled_by_admin
            msg = ChatMessage.create(student, nil, message, ChatMessage::CHANNEL_LESSON, nil, teacher, self, "Lesson Canceled")
            msg = ChatMessage.create(teacher, nil, message, ChatMessage::CHANNEL_LESSON, nil, student, self, "Lesson Canceled")
          else
            msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, self, "Lesson Canceled")
          end


          Notification.send_lesson_message('canceled', self, false)
          Notification.send_lesson_message('canceled', self, true)
          UserMailer.student_lesson_canceled(self, message).deliver_now
          UserMailer.teacher_lesson_canceled(self, message).deliver_now
        end
      end

      response
    end

    def description(lesson_booking)
      lesson_booking.lesson_package_type.description(lesson_booking) + " with #{teacher.admin_name}"
    end

    def time_for_admin
      if self.scheduled_start
        self.scheduled_start.to_date.strftime('%B %d, %Y')
      else
        'TBD'
      end
    end

    def timed_description
      if is_test_drive?
        "TestDrive session with #{self.lesson_booking.student.name} on #{time_for_admin}"
      else
        if self.lesson_booking.is_monthly_payment?
          "Monthly Lesson with #{self.lesson_booking.student.name} on #{time_for_admin}"
        else
          "Lesson with #{self.lesson_booking.student.name} on #{time_for_admin}"
        end
      end
    end

    def cancel_by_admin
      self.cancel({
                      canceler: nil,
                      canceled_by_admin: true,
                      message: 'Canceled by JamKazam administrator',
                      update_all: false
                  })
    end

    def intervened
      self.intervened_at = Time.now
      self.save
    end

    def display_type
      if is_test_drive?
        'TestDrive'
      elsif recurring
        'Recurring'
      else
        'Single'
      end
    end

    def last_student_comm_date
      counterer_id == user_id ? countered_at : sent_notices_at
    end

    def last_response_time
      [countered_at, lesson_booking.sent_notices_at].find { |x| !x.nil? }
    end

    def stripe_description(lesson_booking)
      description(lesson_booking)
    end

    def home_url
      APP_CONFIG.external_root_url + "/client#/jamclass"
    end

    def web_url
      APP_CONFIG.external_root_url + "/client#/jamclass/lesson-booking/" + id
    end

    def update_payment_url
      APP_CONFIG.external_root_url + "/client#/jamclass/update-payment"
    end

    def admin_url
      APP_CONFIG.admin_root_url + "/admin/lesson_sessions/" + id
    end

    def chat_url
      APP_CONFIG.external_root_url + "/client#/jamclass/chat-dialog/d1=lesson_" + id
    end
  end
end
