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

    include HtmlSanitize
    html_sanitize strict: [:description, :cancel_message]

    include ActiveModel::Dirty

    @@log = Logging.logger[LessonBooking]

    attr_accessor :accepting, :countering, :canceling, :autocanceling, :countered_slot, :countered_lesson, :current_purchase, :current_lesson, :expected_session_times, :adjustment_in_cents

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

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

    LESSON_TYPE_FREE = 'single-free'
    LESSON_TYPE_TEST_DRIVE = 'test-drive'
    LESSON_TYPE_PAID = 'paid'

    LESSON_TYPES = [LESSON_TYPE_FREE, LESSON_TYPE_TEST_DRIVE, LESSON_TYPE_PAID]

    PAYMENT_STYLE_ELSEWHERE = 'elsewhere'
    PAYMENT_STYLE_SINGLE = 'single'
    PAYMENT_STYLE_WEEKLY = 'weekly'
    PAYMENT_STYLE_MONTHLY = 'monthly'

    ENGAGED = "status = '#{STATUS_APPROVED}' OR status = '#{STATUS_REQUESTED}' OR status = '#{STATUS_SUSPENDED}'"


    PAYMENT_STYLES = [PAYMENT_STYLE_ELSEWHERE, PAYMENT_STYLE_SINGLE, PAYMENT_STYLE_WEEKLY, PAYMENT_STYLE_MONTHLY]

    belongs_to :user, class_name: "JamRuby::User"
    belongs_to :teacher, class_name: "JamRuby::User"
    belongs_to :accepter, class_name: "JamRuby::User"
    belongs_to :canceler, class_name: "JamRuby::User"
    belongs_to :counterer, class_name: "JamRuby::User", foreign_key: :counterer_id
    belongs_to :default_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :default_slot_id, inverse_of: :defaulted_booking, :dependent => :destroy
    belongs_to :counter_slot, class_name: "JamRuby::LessonBookingSlot", foreign_key: :counter_slot_id, inverse_of: :countered_booking, :dependent => :destroy
    belongs_to :school, class_name: "JamRuby::School"
    belongs_to :retailer, class_name: "JamRuby::Retailer"
    belongs_to :test_drive_package_choice, class_name: "JamRuby::TestDrivePackageChoice"
    belongs_to :posa_card, class_name: "JamRuby::PosaCard"
    has_many :lesson_booking_slots, class_name: "JamRuby::LessonBookingSlot", :dependent => :destroy
    has_many :lesson_sessions, class_name: "JamRuby::LessonSession", :dependent => :destroy
    has_many :lesson_package_purchases, class_name: "JamRuby::LessonPackagePurchase", :dependent => :destroy

    validates :user, presence: true
    validates :teacher, presence: true
    validates :lesson_type, inclusion: {in: LESSON_TYPES}
    validates :status, presence: true, inclusion: {in: STATUS_TYPES}
    validates :recurring, inclusion: {in: [true, false]}
    validates :sent_notices, inclusion: {in: [true, false]}
    validates :card_presumed_ok, inclusion: {in: [true, false]}
    validates :same_school, inclusion: {in: [true, false]}
    validates :active, inclusion: {in: [true, false]}
    validates :lesson_length, inclusion: {in: [30, 45, 60, 90, 120]}
    validates :payment_style, inclusion: {in: PAYMENT_STYLES}
    validates :booked_price, presence: true
    validates :description, no_profanity: true, length: {minimum: 10, maximum: 20000}

    validate :validate_user, on: :create
    validate :validate_recurring
    validate :validate_lesson_booking_slots
    validate :validate_lesson_length
    validate :validate_payment_style
    validate :validate_uncollectables, on: :create
    validate :validate_accepted, :if => :accepting
    validate :validate_canceled, :if => :canceling


    before_save :before_save
    before_validation :before_validation
    after_create :after_create
    around_save :around_update

    scope :test_drive, -> { where(lesson_type: LESSON_TYPE_TEST_DRIVE) }
    scope :active, -> { where(active: true) }
    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 :engaged, -> { where(ENGAGED) }
    scope :engaged_or_successful, -> { where("(" + ENGAGED + ") OR (lesson_bookings.status = '#{STATUS_COMPLETED}' AND lesson_bookings.success = true)") }

    def before_validation
      if self.booked_price.nil?
        self.booked_price = compute_price
      end
    end

    def after_create
      if ((posa_card && posa_card.purchased) || card_presumed_ok || !payment_if_school_on_school?) && !sent_notices
        send_notices
      end
    end

    def before_save
      automatically_default_slot
    end

    def around_update

      @default_slot_did_change = self.default_slot_id_changed?

      yield

      sync_lessons
      sync_remaining_test_drives

      @default_slot_did_change = nil
      @accepting = nil
      @countering = nil
    end

    # here for shopping_cart
    def product_info
      if is_test_drive?
        real_price = 0
      elsif is_monthly_payment?
        raise "no purchase assigned to lesson booking for monthly payment!" if current_purchase.nil?
        real_price = self.current_purchase.teacher_distribution.jamkazam_margin
      else
        if current_lesson.nil?
          puts "OHOHOMOOMG #{self.inspect}"
          raise "no purchase assigned to lesson booking for lesson!"
        end

        real_price = self.current_lesson.teacher_distribution.jamkazam_margin
      end
      {price: real_price, real_price: real_price, total_price: real_price}
    end

    # here for shopping_cart
    def price
      booked_price
    end

    def is_countered?
      has_recurring_counter?
    end

    def has_recurring_counter?
      !!self.counter_slot && self.counter_slot.is_recurring?
    end

    def ever_accepted?
      !!self.accepter
    end

    def no_slots
      default_slot.from_package
    end

    def alt_slot
      found = nil
      lesson_booking_slots.each do |slot|
        if slot.id != default_slot.id
          found = slot
          break
        end
      end
      found
    end

    def student
      user
    end

    def next_lesson
      if recurring
        session = lesson_sessions.joins(:music_session).where("scheduled_start is not null").where("scheduled_start > ?", Time.now).order(:created_at).first
        if session.nil?
          session = lesson_sessions[0]
        end
        LessonSession.find(session.id) if session
      else
        lesson_sessions[0]
      end
    end

    def accept(lesson_session, slot, accepter)
      if !is_active?
        self.accepting = true
      end

      self.active = true
      if slot.is_recurring?
        if self.recurring
          self.counter_slot = nil
          self.status = STATUS_APPROVED
          self.default_slot = slot
          self.accepter = accepter
        else
          # should never happen because u shouldn't be able to set a recurring slot on a single lesson
        end
      else
        self.status = STATUS_APPROVED
        self.default_slot = slot
        self.accepter = accepter
      end

      success = self.save

      if !success
        puts "unable to accept lesson booking #{errors.inspect}"
      else
         # ok, now we have to update the slots of our lesson_sessions
        self.lesson_sessions.each do |lesson_session|
          if !lesson_session.is_countered?
            lesson_session.slot = slot
            lesson_session.save
          end

        end
        end
      success
    end

    def counter(lesson_session, proposer, slot)

      if slot.is_recurring?
        self.lesson_booking_slots << slot
        self.countering = true
        self.counter_slot = slot
        self.counterer = proposer
        self.countered_at = Time.now
        self.sent_counter_reminder = false
        self.status = STATUS_COUNTERED
      end

      if self.default_slot.from_package
        self.default_slot = slot
      end
      self.save
    end

    def automatically_default_slot
      if is_requested? && default_slot.nil?
        if lesson_booking_slots.length > 0
          self.default_slot = lesson_booking_slots[0]
        end
      end
    end

    def sync_remaining_test_drives
      if is_test_drive? || is_single_free?
        if (posa_card || card_presumed_ok) && !user_decremented
          self.user_decremented = true
          self.save(validate: false)
          if is_single_free?
            user.remaining_free_lessons = user.remaining_free_lessons - 1
          elsif is_test_drive?
            if posa_card
              user.jamclass_credits = user.jamclass_credits - 1
            else
              user.remaining_test_drives = user.remaining_test_drives - 1
            end

          end
          user.save(validate: false)
        end
      end
    end

    def create_minimum_booking_time
      # trying to be too smart
      #(Time.now + APP_CONFIG.minimum_lesson_booking_hrs * 60*60)
      Time.now
    end

    def sync_lessons

      if is_canceled? || is_completed?
        # don't create new sessions if cancelled
        return
      end


      if @default_slot_did_change

      end
      # Here we go; let's create a lesson(s) as needed

      # we need to make lessons into the future a bit, to give time for everyone involved
      minimum_start_time = create_minimum_booking_time

      # get all sessions that are already scheduled for this booking ahead of the minimum time

      sessions= MusicSession.joins(:lesson_session).where("lesson_sessions.lesson_booking_id = ?", id).where("scheduled_start is not null").order(:created_at)
      if recurring
        # only want times ahead of this for recurring
        sessions = sessions.where("scheduled_start > ?", minimum_start_time)
      end

      if @default_slot_did_change
        # # adjust all session times

        offset = 0
        sessions.each_with_index do |item, i|
          item.lesson_session.slot = default_slot
          result = item.lesson_session.update_next_available_time(offset)
          if result
            offset = result
            offset += 1
          end
        end
      end

      needed_sessions = determine_needed_sessions(sessions)

      # if the latest scheduled session is after the minimum start time, then bump up minimum start time
      last_session = sessions.last
      last_session.reload if last_session # because of @default_slot_did_change logic above, this can be necessary

      if last_session && last_session.scheduled_start && last_session.scheduled_start > minimum_start_time
        minimum_start_time = last_session.scheduled_start
      end
      times = default_slot.scheduled_times(needed_sessions, minimum_start_time)

      scheduled_lessons(times)
    end

    # sensitive to current time
    def predicted_times_for_month(year, month)
      first_day = Date.new(year, month, 1)
      last_day = Date.new(year, month, -1)
      sessions = MusicSession.joins(:lesson_session).where("lesson_sessions.lesson_booking_id = ?", id).where("scheduled_start >= ?", first_day).where("scheduled_start <= ?", last_day).order(:created_at)

      times = []

      sessions.each do |session|
        times << session.scheduled_start
      end
      last_session = sessions.last

      start_day = first_day
      if last_session
        start_day = last_session.scheduled_start.to_date + 1
      end

      # now flesh out the rest of the month with predicted times

      more_times = default_slot.scheduled_times(5, start_day)

      more_times.each do |time|
        if time.to_date >= first_day && time.to_date <= last_day
          times << time
        end
      end
      {times: times, session: sessions.first}
    end

    def determine_needed_sessions(sessions)
      needed_sessions = 0
      if is_requested?
        # in the case of a requested booking (not approved) only make one, even if it's recurring.   This is for UI considerations
        if sessions.count == 0
          needed_sessions = 1
        end
      elsif is_active?
        expected_num_sessions = recurring ? 2 : 1
        needed_sessions = expected_num_sessions - sessions.count
      end
      needed_sessions
    end

    def scheduled_lessons(times)
      times.each do |time|

        lesson_session = LessonSession.create(self)

        if lesson_session.errors.any?
          puts "JamClass lesson session creation errors #{lesson_session.errors.inspect}"
          @@log.error("JamClass lesson session creation errors #{lesson_session.errors.inspect}")
          raise ActiveRecord::Rollback
        end
        ms_tz = ActiveSupport::TimeZone.new(default_slot.timezone)
        ms_tz = "#{ms_tz.name},#{default_slot.timezone}"

        rsvps = [{instrument_id: 'other', proficiency_level: 0, approve: true}]

        music_session = MusicSession.create(student, {
                                                       name: "#{display_type2} JamClass taught by #{teacher.name}",
                                                       description: "This is a #{lesson_length}-minute #{display_type2} lesson with #{teacher.name}.",
                                                       musician_access: false,
                                                       fan_access: false,
                                                       genres: ['other'],
                                                       approval_required: false,
                                                       fan_chat: false,
                                                       legal_policy: "standard",
                                                       language: 'eng',
                                                       duration: lesson_length,
                                                       recurring_mode: false,
                                                       timezone: ms_tz,
                                                       create_type: MusicSession::CREATE_TYPE_LESSON,
                                                       is_unstructured_rsvp: true,
                                                       scheduled_start: time,
                                                       invitations: [teacher.id],
                                                       lesson_session: lesson_session,
                                                       rsvp_slots: rsvps
                                                   })

        if music_session.errors.any?
          puts "JamClass lesson scheduling errors #{music_session.errors.inspect}"
          @@log.error("JamClass lesson scheduling errors #{music_session.errors.inspect}")
          raise ActiveRecord::Rollback
        end

        if lesson_session.is_active?
          # send out email to student to act as something they can add to their calendar
          Notification.send_student_jamclass_invitation(music_session, student)
        end

      end
    end

    def is_weekly_payment?
      payment_style == PAYMENT_STYLE_WEEKLY
    end

    def is_monthly_payment?
      payment_style == PAYMENT_STYLE_MONTHLY
    end

    def requires_per_session_billing?
      is_normal? && !is_monthly_payment?
    end

    def requires_teacher_distribution?(target)
      if no_school_on_school_payment?
        return false
      elsif target.is_a?(JamRuby::LessonSession)
        is_test_drive? || (is_normal? && !is_monthly_payment?)
      elsif target.is_a?(JamRuby::LessonPackagePurchase)
        is_monthly_payment?
      else
        raise "unable to determine object type of #{target}"
      end
    end

    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_active?
      active
    end

    def has_access?(user)
      user.id == student.id || user.id == teacher.id || (self.school.nil? ? false : self.school.user.id == user.id)
    end

    def validate_accepted
      # accept is multipe purpose; either accept the initial request, or a counter slot
      if self.status_was != STATUS_REQUESTED && self.status_was != STATUS_COUNTERED
        self.errors.add(:status, "This lesson is already #{self.status}.")
      end

      if self.accepter.nil?
        self.errors.add(:accepter, "No one has been indicated as accepting the lesson")
      end
      self.accepting = false
    end

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

      self.canceling = false
    end

    def send_notices
      UserMailer.student_lesson_request(self).deliver_now
      UserMailer.teacher_lesson_request(self).deliver_now
      Notification.send_lesson_message('requested', lesson_sessions[0], false) # TODO: this isn't quite an 'accept'
      self.sent_notices = true
      self.sent_notices_at = Time.now
      self.save
    end

    def resolved_test_drive_package
      result = nil
       # posa card is best indicator of lesson package type
      if posa_card
        return posa_card.lesson_package_type
      end

      purchase = student.most_recent_test_drive_purchase
      if purchase
        # for lessons already packaged
        result = purchase.lesson_package_type
      else
        # for unbooked lessons
        result = student.desired_package
      end
      if result.nil?
        result = LessonPackageType.test_drive_4
      end
      result
    end

    def lesson_package_type
      if is_single_free?
        LessonPackageType.single_free
      elsif is_test_drive?
        resolved_test_drive_package
      elsif is_normal?
        LessonPackageType.single
      end
    end

    def display_type2
      if is_single_free?
        "Free"
      elsif is_test_drive?
        "TestDrive"
      elsif is_normal?
        "Single"
      end
    end

    def display_type
      if is_single_free?
        "Free"
      elsif is_test_drive?
        "TestDrive"
      elsif is_normal?
        if recurring
          "recurring"
        else
          "single"
        end

      end
    end

    # determine the price of this booking based on what the user wants, and the teacher's pricing

    def compute_price
      if is_single_free?
        0
      elsif is_test_drive?
        resolved_test_drive_package.price
      elsif is_normal?
        teacher.teacher.booking_price(lesson_length, payment_style != PAYMENT_STYLE_MONTHLY)
      end
    end

    def distribution_price_in_cents(target, education, split = nil)


      if split
        distribution = teacher_distribution_price_in_cents(target, split)
        (distribution * split).round
      elsif education
        distribution = teacher_distribution_price_in_cents(target, 0.0625)
        (distribution * 0.0625).round # 0.0625 is 1/4th of 25%
      else
        distribution = teacher_distribution_price_in_cents(target)
        distribution
      end
    end

    def teacher_distribution_price_in_cents(target, split = nil)
      if is_single_free?
        0
      elsif is_test_drive?
        10 * 100
      elsif is_normal?
        if is_monthly_payment?
          raise "not a LessonPackagePurchase: #{target.inspect}" if !target.is_a?(LessonPackagePurchase)

          today = Date.today

          start_date = Date.new(target.year, target.month, 1)
          if today.year == target.year && today.month == target.month
            # we are in the month being billed.  we should set the start date based on today
            start_date = today
          end
          price, times = LessonSessionMonthlyPrice.price(self, start_date)

          price_in_cents = (price * 100).round
          # OK, we have a suggested price based on date, but we need to now adjust if previous lessons have been unsuccessful

          adjusted_price_in_cents = LessonSessionMonthlyPrice.adjust_for_missed_lessons(self, price_in_cents, split)

          self.expected_session_times = times # save for later
          self.adjustment_in_cents = price_in_cents - adjusted_price_in_cents
          adjusted_price_in_cents
        else
          booked_price * 100
        end
      end
    end

    # find any lesson package purchases for this lesson booking for previous months that have not had adjustments
    # we have to base this on 'now', meaning we do not consider months until they are fully closed
    # this does mean, because we collect up to a week in advance of a new month starting, that a student will likely not see adjustments until 2 cycles forward
    def self.previous_needing_adjustment
      now = Time.now.utc
      year = now.year
      month = now.month

      if month == 1
        previous_year = year - 1
        previous_month = 12
      else
        previous_year = year
        previous_month = month - 1
      end

      LessonPackagePurchase.where(recurring: true).where('month <= ?', previous_month).where('year <= ?', previous_year).where('actual_session_times is null').where('expected_session_times is not null')
          .limit(500)
    end

    def self.adjust_for_missed_sessions

      # Go to previous lesson_package_purchase month, and see if we need to adjust for a roll forward due to missed sessions
      previous_purchases = LessonBooking.previous_needing_adjustment

      previous_purchases.each do |previous_purchase|

        # XXX other monthly code uses session start time. should we be doing that?
        successful_lessons = LessonSession.where(lesson_booking: previous_purchase.lesson_booking).where(success: true).where('analysed_at >= ? AND analysed_at < ?', previous_purchase.beginning_of_month_at, previous_purchase.end_of_month_at)

        previous_purchase.actual_session_times = successful_lessons.count
        # find out how many actual lessons were had, and then we can adjust price of this current distribution (amount_in_cents) accordingly

        ratio = previous_purchase.actual_session_times.to_f / previous_purchase.expected_session_times.to_f

        if ratio < 1
          # discount next month for student
          amount_paid_last_month_in_cents = previous_purchase.price_in_cents # this does not include tax. It's just the expected price of the booking
          previous_purchase.remaining_roll_forward_amount_in_cents = previous_purchase.total_roll_forward_amount_in_cents = (amount_paid_last_month_in_cents * ratio).round

          # if there is a roll forward, add it to the lesson booking
          previous_purchase.lesson_booking.remaining_roll_forward_amount_in_cents += previous_purchase.remaining_roll_forward_amount_in_cents
          previous_purchase.lesson_booking.save!

        else
          previous_purchase.total_roll_forward_amount_in_cents = 0
          #previous_purchase.applied_roll_forward_amount_in_cents = 0
        end

        previous_purchase.save
      end
    end


    def is_single_free?
      lesson_type == LESSON_TYPE_FREE
    end

    def is_test_drive?
      lesson_type == LESSON_TYPE_TEST_DRIVE
    end

    def is_normal?
      lesson_type == LESSON_TYPE_PAID
    end

    def dayWeekDesc(slot = default_slot)
      day = case slot.day_of_week
              when 0 then
                "Sunday"
              when 1 then
                "Monday"
              when 2 then
                "Tuesday"
              when 3 then
                "Wednesday"
              when 4 then
                "Thursday"
              when 5 then
                "Friday"
              when 6 then
                "Saturday"
            end


      if slot.hour > 11
        hour = slot.hour - 12
        if hour == 0
          hour = 12
        end
        am_pm = 'pm'
      else
        hour = slot.hour
        if hour == 0
          hour = 12
        end
        am_pm = 'am'
      end

      "#{day} at #{hour}:#{slot.minute.to_s.rjust(2, "0")}#{am_pm}"

    end

    def status_as_verb
      if is_requested?
        'requested'
      else
        'scheduled'
      end

    end
    def approved_before?
      !self.accepter_id.nil?
    end

    def autocancel
      self.autocanceling = true
      self.active = false
      self.status = STATUS_UNCONFIRMED
      if save
        if is_test_drive?
          user.jamclass_credits = user.jamclass_credits + 1
          user.save(validate:false)
        end
      end
      self
    end

    def cancel_tracking(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
        self.canceled_by_admin = Time.now
      elsif canceled_by_student
        self.student_canceled = true
        self.student_canceled_at = Time.now
        self.student_canceled_reason = message
      else
        self.teacher_canceled = true
        self.teacher_canceled_at = Time.now
        self.teacher_canceled_reason = message
      end
    end

    def cancel(canceler, other, message, canceled_by_admin = false)

      cancel_tracking(canceler, message, canceled_by_admin)
      self.active = false

      success = save
      if success
        lesson_sessions.upcoming.each do |lesson_session|
          lesson_session = LessonSession.find(lesson_session.id) # because .upcoming creates ReadOnly records
          lesson_session.cancel_lesson(canceler, message, canceled_by_admin)
          if !lesson_session.save
            return lesson_session
          end
        end
        if approved_before?
          # just tell both people it's cancelled, to act as confirmation
          Notification.send_lesson_message('canceled', next_lesson, false)
          Notification.send_lesson_message('canceled', next_lesson, true)
          UserMailer.student_lesson_booking_canceled(self, message).deliver_now
          UserMailer.teacher_lesson_booking_canceled(self, message).deliver_now
          purpose = "Lesson Canceled"
        else
          if canceled_by_admin
            # meh.  no two way comm here.  just bail
            #Notification.send_lesson_message('canceled', next_lesson, true)
            UserMailer.student_lesson_booking_declined(self, message).deliver_now
            UserMailer.teacher_lesson_booking_canceled(self, message).deliver_now
          elsif canceler == student
            # if it's the first time acceptance student canceling, we call it a 'cancel'
            Notification.send_lesson_message('canceled', next_lesson, false)
            UserMailer.teacher_lesson_booking_canceled(self, message).deliver_now
            purpose = "Lesson Canceled"
          else
            # if it's the first time acceptance teacher, it was declined
            UserMailer.student_lesson_booking_declined(self, message).deliver_now
            Notification.send_lesson_message('declined', next_lesson, true)
            purpose = "Lesson Declined"
          end
        end

        message = '' if message.nil?
        if !canceled_by_admin
          msg = ChatMessage.create(canceler, nil, message, ChatMessage::CHANNEL_LESSON, nil, other, next_lesson, purpose)
        end
      else
      end


      self
    end

    def card_approved
      self.card_presumed_ok = true
      if posa_card_id
        self.posa_card_purchased = true
      end
      if self.save && !sent_notices
        send_notices
      end
    end

    def validate_user
      if card_presumed_ok && is_single_free?
        if !user.has_free_lessons?
          errors.add(:user, 'have no remaining free lessons')
        end

        #if !user.has_stored_credit_card?
        #  errors.add(:user, 'has no credit card stored')
        #end
      elsif is_test_drive?
        if posa_card
          if !user.has_posa_credits?
            errors.add(:user, "have no remaining jamclass credits")
          end
        else
          if !user.has_test_drives? && !user.can_buy_test_drive?
            errors.add(:user, "have no remaining test drives")
          elsif teacher.has_booked_test_drive_with_student?(user) && !user.admin
            # we don't want to restrict this anymore.  just let'em go to same teacher
            # errors.add(:user, "have an in-progress or successful TestDrive with this teacher already")
          end
        end


      elsif is_normal?
        #if !user.has_stored_credit_card?
        #  errors.add(:user, 'has no credit card stored')
        #end
      end
    end

    def validate_teacher
      # shouldn't we check if the teacher already has a booking in this time slot, or at least warn the user
    end

    def validate_recurring
      if is_single_free? || is_test_drive?
        if recurring
          errors.add(:recurring, "can not be true for this type of lesson")
        end
      end

      false
    end

    def validate_lesson_booking_slots
      if test_drive_package_choice.nil?
        if lesson_booking_slots.length == 0 || lesson_booking_slots.length == 1
          errors.add(:lesson_booking_slots, "must have two times specified")
        end
      end
    end

    def validate_lesson_length
      if is_single_free? || is_test_drive?
        if lesson_length != 30
          errors.add(:lesson_length, "must be 30 minutes")
        end
      end
    end

    def validate_payment_style
      if is_normal?
        if payment_style.nil?
          errors.add(:payment_style, "can't be blank")
        end
      end
    end

    def validate_uncollectables
      if user.uncollectables.count > 0
        errors.add(:user, 'have unpaid lessons.')
      end
    end

    def school_owned?
      !!school
    end

    def self.book_packaged_test_drive(user, teacher, description, test_drive_package_choice)
      book_test_drive(user, teacher, LessonBookingSlot.packaged_slots, description, test_drive_package_choice)
    end

    def self.book_free(user, teacher, lesson_booking_slots, description)
      self.book(user, teacher, LessonBooking::LESSON_TYPE_FREE, lesson_booking_slots, false, 30, PAYMENT_STYLE_ELSEWHERE, description)
    end

    def self.book_test_drive(user, teacher, lesson_booking_slots, description, test_drive_package_choice = nil)
      self.book(user, teacher, LessonBooking::LESSON_TYPE_TEST_DRIVE, lesson_booking_slots, false, 30, PAYMENT_STYLE_ELSEWHERE, description, test_drive_package_choice)
    end

    def self.book_normal(user, teacher, lesson_booking_slots, description, recurring, payment_style, lesson_length)
      self.book(user, teacher, LessonBooking::LESSON_TYPE_PAID, lesson_booking_slots, recurring, lesson_length, payment_style, description)
    end

    def self.book(user, teacher, lesson_type, lesson_booking_slots, recurring, lesson_length, payment_style, description, test_drive_package_choice = nil)

      lesson_booking = nil
      LessonBooking.transaction do

        lesson_booking = LessonBooking.new
        lesson_booking.user = user
        lesson_booking.card_presumed_ok = user.has_stored_credit_card?
        lesson_booking.sent_notices = false
        lesson_booking.teacher = teacher
        lesson_booking.lesson_type = lesson_type
        lesson_booking.recurring = recurring
        lesson_booking.lesson_length = lesson_length
        lesson_booking.payment_style = payment_style
        lesson_booking.description = description
        lesson_booking.status = STATUS_REQUESTED
        if lesson_type == LESSON_TYPE_TEST_DRIVE
          # if the user has any jamclass credits, then we should get their most recent posa purchase
          if user.jamclass_credits > 0
            lesson_booking.posa_card = user.most_recent_posa_card
            if lesson_booking.posa_card
              lesson_booking.posa_card_purchased = lesson_booking.posa_card.purchased
            end
          else
            # otherwise, it's a normal test drive, and we should honor test_drive_package_choice if specified
            lesson_booking.test_drive_package_choice = test_drive_package_choice
          end
        end


        if lesson_booking.teacher && lesson_booking.teacher.teacher.school
          lesson_booking.school = lesson_booking.teacher.teacher.school
        end

        # copy payment settings from retailer into lesson booking
        if lesson_booking.teacher && lesson_booking.teacher.teacher.retailer
          lesson_booking.retailer = lesson_booking.teacher.teacher.retailer
          lesson_booking.payment = lesson_booking.teacher.teacher.retailer.payment_details.to_json
          lesson_booking.same_retailer = lesson_booking.teacher.teacher.retailer.affiliate_partner == user.affiliate_referral
        end

        if user
          lesson_booking.same_school = !!(lesson_booking.school && user.school && (lesson_booking.school.id == user.school.id))
          if lesson_booking.same_school
            lesson_booking.same_school_free = false # !user.school.education # non-education schools (music schools) are 'free' when school-on-school
          end
          user.update_timezone(lesson_booking_slots[0].timezone) if lesson_booking_slots.length > 0
        else
          lesson_booking.same_school = false
          lesson_booking.same_school_free = false
        end

        # two-way association slots, for before_validation loic in slot to work
        lesson_booking.lesson_booking_slots = lesson_booking_slots
        lesson_booking_slots.each do |slot|
          slot.lesson_booking = lesson_booking
          slot.message = description
        end if lesson_booking_slots

        if lesson_booking.save
            description = '' if description.nil?
          msg = ChatMessage.create(user, lesson_booking.lesson_sessions[0], description, ChatMessage::CHANNEL_LESSON, nil, teacher, lesson_booking.lesson_sessions[0], 'Lesson Requested')
        end
      end
      lesson_booking
    end

    def self.unprocessed(current_user)
      LessonBooking.where(user_id: current_user.id).where(card_presumed_ok: false).where(same_school_free: false).where('posa_card_id is null OR (posa_card_id is not null AND posa_card_purchased = false)')
    end

    def self.requested(current_user)
      LessonBooking.where(user_id: current_user.id).where(status: STATUS_REQUESTED)
    end

    def school_on_school?
      same_school
    end

    def school_on_school_payment?
      #!!(same_school && (school.education || school.is_guitar_center?))
      same_school
    end

    def no_school_on_school_payment?
      !!(school_on_school? && !school_on_school_payment?)
    end

    # if this is school-on-school, is payment required?
    def payment_if_school_on_school?
      !!(!school_on_school? || school_on_school_payment?)
    end

    def school_and_teacher
      if school && school.scheduling_comm?
        [school.communication_email, teacher.email]
      else
        [teacher.email]
      end
    end

    def school_and_teacher_ids
      if school && school.scheduling_comm?
        [school.owner.id, teacher.id]
      else
        [teacher.id]
      end
    end


    def school_over_teacher
      if school && school.scheduling_comm?
        [school.communication_email]
      else
        [teacher.email]
      end
    end

    def school_over_teacher_ids
      if school && school.scheduling_comm?
        [school.owner.id]
      else
        [teacher.id]
      end
    end

    def self.find_bookings_needing_sessions(minimum_start_time)
      MusicSession.select([:lesson_booking_id]).joins(:lesson_session => :lesson_booking).where("lesson_bookings.active = true").where('lesson_bookings.recurring = true').where("scheduled_start is not null").where("scheduled_start > ?", minimum_start_time).group(:lesson_booking_id).having('count(lesson_booking_id) < 2')
    end

    # check for any recurring sessions where there are not at least 2 sessions into the future.  If not, we need to make sure they get made
    def self.hourly_check
      schedule_upcoming_lessons

      # order matters: bill_monthly code will use the adjustments made in here to correct billing roll forward
      adjust_for_missed_sessions

      # needs to come after 'adjust_for_missed_sessions'
      bill_monthlies
    end


    def self.bill_monthlies
      now = Time.now
      billable_monthlies(now).each do |lesson_booking|
        lesson_booking.bill_monthly(now)
      end

      today = now.to_date
      seven_days_in_future = today + 7


      is_different_month = seven_days_in_future.month != today.month
      if is_different_month
        next_month = seven_days_in_future.to_time
        billable_monthlies(next_month).each do |lesson_booking|
          lesson_booking.bill_monthly(next_month)
        end
      end
    end

    def self.billable_monthlies(now)
      current_month_first_day = Date.new(now.year, now.month, 1)
      current_month_last_day = Date.new(now.year, now.month, -1)
      #next_month_last_day = now.month == 12 ? Date.new(now.year + 1, 1, -1) : Date.new(now.year, now.month + 1, -1)

      LessonBooking
          .joins(:lesson_sessions => :music_session)
          .joins("LEFT JOIN lesson_package_purchases ON (lesson_package_purchases.lesson_booking_id = lesson_bookings.id AND (lesson_package_purchases.year = #{current_month_first_day.year} AND lesson_package_purchases.month = #{current_month_first_day.month}))")
          .where("lesson_package_purchases.id IS NULL OR (lesson_package_purchases.id IS NOT NULL AND lesson_package_purchases.post_processed = false)")
          .where(payment_style: PAYMENT_STYLE_MONTHLY)
          .where(same_school_free: false)
          .active
          .where('music_sessions.scheduled_start >= ?', current_month_first_day)
          .where('music_sessions.scheduled_start <= ?', current_month_last_day).uniq

=begin
      today = now.to_date
      seven_days_in_future = today + 7

      is_different_month = seven_days_in_future.month != today.month
      if is_different_month
        condition = "(((lesson_package_purchases.year = #{current_month_first_day.year} AND lesson_package_purchases.month = #{current_month_first_day.month}) AND (  (EXTRACT(YEAR FROM lesson_sessions.created_at)) = #{current_month_first_day.year} AND (EXTRACT(MONTH FROM lesson_sessions.created_at)) = #{current_month_first_day.month} ) )
                      OR ((lesson_package_purchases.year = #{seven_days_in_future.year} AND lesson_package_purchases.month = #{seven_days_in_future.month}) AND ( (EXTRACT(YEAR FROM lesson_sessions.created_at)) = #{seven_days_in_future.year} AND (EXTRACT(MONTH FROM lesson_sessions.created_at)) = #{seven_days_in_future.month} ) ) )"
      else
        condition = "((lesson_package_purchases.year = #{current_month_first_day.year} AND lesson_package_purchases.month = #{current_month_first_day.month}) AND (  (EXTRACT(YEAR FROM lesson_sessions.created_at)) = #{current_month_first_day.year} AND (EXTRACT(MONTH FROM lesson_sessions.created_at)) = #{current_month_first_day.month} ) )"
      end


      # .where("(lesson_package_purchases.year = #{current_month_first_day.year} AND lesson_package_purchases.month = #{current_month_first_day.month}) OR (lesson_package_purchases.year = #{next_month_last_day.year} AND lesson_package_purchases.month = #{next_month_last_day.month})")

      # find any monthly-billed bookings that have a session coming up within 7 days, and if so, attempt to bill them
      LessonBooking
          .joins(:lesson_sessions)
          .joins("LEFT JOIN lesson_package_purchases ON (lesson_package_purchases.lesson_booking_id = lesson_bookings.id AND #{condition})")
          .where("lesson_package_purchases.id IS NULL OR (lesson_package_purchases.id IS NOT NULL AND lesson_package_purchases.post_processed = false)")
          .where(payment_style: PAYMENT_STYLE_MONTHLY)
          .where(status: STATUS_APPROVED)
          .where('lesson_sessions.created_at >= ?', current_month_first_day)
          .where('lesson_sessions.created_at <= ?', seven_days_in_future).uniq

=end
    end

    def self.bookings(student, teacher, since_at = nil)
      bookings = LessonBooking.where(user_id: student.id, teacher_id: teacher.id)

      if since_at
        bookings = bookings.where('created_at >= ?', since_at)
      end

      bookings
    end

    def self.not_failed

    end

    def self.engaged_bookings(student, teacher, since_at = nil)
      bookings = bookings(student, teacher, since_at)
      bookings.engaged_or_successful
    end

    def bill_monthly(now)
      LessonBooking.transaction do
        self.lock!

        current_month = Date.new(now.year, now.month, 1)

        bill_for_month(current_month)

        today = now.to_date
        seven_days_in_future = today + 7
        is_different_month = seven_days_in_future.month != today.month
        if is_different_month
          bill_for_month(seven_days_in_future)
        end
      end
    end

    def bill_for_month(day_in_month)
      # try to find lesson package purchase for this month, and last month, and see if they need processing
      current_month_purchase = lesson_package_purchases.where(lesson_booking_id: self.id, user_id: student.id, year: day_in_month.year, month: day_in_month.month).first
      if current_month_purchase.nil?
        current_month_purchase = LessonPackagePurchase.create(user, self, lesson_package_type, day_in_month.year, day_in_month.month)
      end
      current_month_purchase.bill_monthly
    end

    def suspend!
      # when this is called, the calling code sends out a email to let the student and teacher know (it feels unnatural it's not here, though)
      self.status = STATUS_SUSPENDED
      self.active = false
      if self.save
        future_sessions.each do |lesson_session|
          LessonSession.find(lesson_session.id).suspend!
        end
      end
    end

    def unsuspend!
      if self.status == STATUS_SUSPENDED
        self.status = STATUS_APPROVED
        self.active = true
        if self.save
          future_sessions.each do |lesson_session|
            LessonSession.find(lesson_session.id).unsuspend!
          end
        end
      end
    end

    def future_sessions
      lesson_sessions.joins(:music_session).where('scheduled_start > ?', Time.now).order(:created_at)
    end

    def self.schedule_upcoming_lessons
      minimum_start_time = (Time.now + APP_CONFIG.minimum_lesson_booking_hrs * 60*60)

      lesson_bookings = find_bookings_needing_sessions(minimum_start_time)

      lesson_bookings.each do |data|
        lesson_booking = LessonBooking.find(data["lesson_booking_id"])
        lesson_booking.sync_lessons
      end
    end


    def scheduling_email
      school_scheduling_comm? ? school.communication_email : teacher.email
    end

    # when you need to email potentially both school and teacher for same email
    def teacher_school_emails
      if school_comm?
        [school.communication_email, teacher.email]
      else
        [teacher.email]
      end

    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_bookings/" + id
    end

    private
    def school_scheduling_comm?
      school ? school.school_comm? : false
    end
  end
end
