require 'sanitize'
class ApiUsersController < ApiController

  before_filter :api_signed_in_user, :except => [:create, :calendar, :show, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring, :add_play, :crash_dump, :validate_data, :google_auth, :user_event, :onboardings, :update_onboarding, :show_onboarding]
  before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete, :authorizations, :test_drive_status,
                                      :liking_create, :liking_destroy, # likes
                                      :following_create, :following_show, :following_destroy, # followings
                                      :recording_update, :recording_destroy, # recordings
                                      :favorite_create, :favorite_destroy, # favorites
                                      :friend_request_index, :friend_request_show, :friend_request_create, :friend_request_update, # friend requests
                                      :friend_show, :friend_destroy, # friends
                                      :notification_index, :notification_destroy, # notifications
                                      :band_invitation_index, :band_invitation_show, :band_invitation_update, # band invitations
                                      :set_password, :begin_update_email, :update_avatar, :delete_avatar, :generate_filepicker_policy,
                                      :share_session, :share_recording,
                                      :affiliate_report, :audio_latency, :broadcast_notification, :redeem_giftcard]
  before_filter :ip_blacklist, :only => [:create, :redeem_giftcard]

  respond_to :json, :except => :calendar
  respond_to :ics, :only => :calendar

  def index
    @users = User.paginate(page: params[:page])
    respond_with @users, responder: ApiResponder, :status => 200
  end

  def calendar
    #@user=lookup_user
    #ics = CalendarManager.new.create_ics_feed(@user)
    #send_data ics, :filename => 'JamKazam', :disposition => 'inline', :type => "text/calendar"
    render :json => {}, :status => 200
  end

  def show
    @user=lookup_user

    @show_teacher = true
    @show_profile = params[:show_profile]
    @show_student = params[:show_student]

    respond_with @user, responder: ApiResponder, :status => 200
  end

  def authorizations

    @user = current_user
    respond_with @user, responder: ApiResponder, :status => 200
  end

  def jamblasters
    @user = current_user
    @connection = Connection.find_by_client_id(params[:client_id])

    respond_with @user, responder: ApiResponder, :status => 200
  end

  def google_auth
    @user = current_user
    respond_with @user, responder: ApiResponder, :status => 200
  end

  def profile_show
    if !guard_school_same_scope(current_user, User.find(params[:id]))
      raise JamPermissionError, ValidationMessages::CAN_ONLY_VIEW_SAME_SCHOOL
    end

    @profile = User.includes([{musician_instruments: :instrument},
                              {band_musicians: :user},
                              {genre_players: :genre},
                              :bands, :instruments, :genres,
                              :online_presences, :performance_samples])
                   .find(params[:id])

    @show_teacher_profile = params[:show_teacher]

    respond_with @profile, responder: ApiResponder, :status => 200
  end

  # in other words, a minimal signup
  def create
    # today, this only accepts a minimal registration; it could be made to take in more if we wanted
    signup_hint = nil
    if anonymous_user
      signup_hint = anonymous_user.signup_hint
      if signup_hint && signup_hint.jam_track.nil?
        signup_hint = nil # it doesn't make sense to pass in signup hints that are not free jam-track centric (at least, not today)
      end
    end

    # recaptcha_response: params['g-recaptcha-response']

    # try to autologin , because users will try to log in via signup link
    if(params[:email] && params[:password])
      test = User.authenticate(params[:email], params[:password])
      if test
        @user = test
        @autologin = true
        @user.school_interest = !!params[:school_interest]
        @user.education_interest = !!params[:education_interest]
        @user.retailer_interest = !!params[:retailer_interest]
        @user.save
        sign_in @user
        new_user(@user, signup_hint) # sets a cookie used for GA analytics (one-time new user stuff in JavaScript)
        respond_with_model(@user, new: true, location: lambda { return api_user_detail_url(@user.id) })
        return
      end
    end


    options = {
        first_name: params[:first_name],
        last_name: params[:last_name],
        email: params[:email],
        password: params[:password],
        password_confirmation: params[:password],
        terms_of_service: params[:terms],
        location: {:country => nil, :state => nil, :city => nil},
        signup_hint: signup_hint,
        gift_card: params[:gift_card],
        student: params[:student],
        teacher: params[:teacher],
        school_invitation_code: params[:school_invitation_code],
        school_id: params[:school_id],
        retailer_invitation_code: params[:retailer_invitation_code],
        retailer_id: params[:retailer_id],
        school_interest: params[:school_interest],
        retailer_interest: params[:retailer_interest],
        education_interest: params[:education_interest],
        affiliate_referral_id: cookies[:affiliate_visitor],
        origin: origin_cookie,
        test_drive_package: params[:test_drive_package],
        timezone: current_timezone
    }

    options = User.musician_defaults(request.remote_ip, ApplicationHelper.base_uri(request) + "/confirm", any_user, options)

    @user = UserManager.new.signup(options)

    if @user.errors.any?
      respond_with_model(@user)
    else
      sign_in @user
      new_user(@user, signup_hint) # sets a cookie used for GA analytics (one-time new user stuff in JavaScript)
      respond_with_model(@user, new: true, location: lambda { return api_user_detail_url(@user.id) })
    end
  end

  def profile_save
  end

  def update

    @user = User.find(params[:id])
    @user.first_name = params[:first_name] if params.has_key?(:first_name)
    @user.last_name = params[:last_name] if params.has_key?(:last_name)
    @user.gender = params[:gender] if params.has_key?(:gender)
    @user.birth_date = Date.strptime(params[:birth_date], '%m-%d-%Y') if (params.has_key?(:birth_date) && params[:birth_date])
    @user.city = params[:city] if params.has_key?(:city)
    @user.state = params[:state] if params.has_key?(:state)
    @user.country = params[:country] if params.has_key?(:country)
    @user.musician = params[:musician] if params.has_key?(:musician)
    @user.update_instruments(params[:instruments].nil? ? [] : params[:instruments]) if params.has_key?(:instruments)

    # genres
    @user.update_genres(params[:genres].nil? ? [] : params[:genres], GenrePlayer::PROFILE) if params.has_key?(:genres)
    @user.update_genres(params[:virtual_band_genres].nil? ? [] : params[:virtual_band_genres], GenrePlayer::VIRTUAL_BAND) if params.has_key?(:virtual_band_genres)
    @user.update_genres(params[:traditional_band_genres].nil? ? [] : params[:traditional_band_genres], GenrePlayer::TRADITIONAL_BAND) if params.has_key?(:traditional_band_genres)
    @user.update_genres(params[:paid_session_genres].nil? ? [] : params[:paid_session_genres], GenrePlayer::PAID_SESSION) if params.has_key?(:paid_session_genres)
    @user.update_genres(params[:free_session_genres].nil? ? [] : params[:free_session_genres], GenrePlayer::FREE_SESSION) if params.has_key?(:free_session_genres)
    @user.update_genres(params[:cowriting_genres].nil? ? [] : params[:cowriting_genres], GenrePlayer::COWRITING) if params.has_key?(:cowriting_genres)

    @user.show_whats_next = params[:show_whats_next] if params.has_key?(:show_whats_next)
    @user.show_whats_next_count = params[:show_whats_next_count] if params.has_key?(:show_whats_next_count)
    @user.subscribe_email = params[:subscribe_email] if params.has_key?(:subscribe_email)
    @user.biography = params[:biography] if params.has_key?(:biography)

    @user.website = params[:website] if params.has_key?(:website)
    @user.skill_level = params[:skill_level] if params.has_key?(:skill_level)
    @user.concert_count = params[:concert_count] if params.has_key?(:concert_count)
    @user.studio_session_count = params[:studio_session_count] if params.has_key?(:studio_session_count)

    # virtual band
    @user.virtual_band = params[:virtual_band] if params.has_key?(:virtual_band)
    @user.virtual_band_commitment = params[:virtual_band_commitment] if params.has_key?(:virtual_band_commitment)

    # traditional band
    @user.traditional_band = params[:traditional_band] if params.has_key?(:traditional_band)
    @user.traditional_band_commitment = params[:traditional_band_commitment] if params.has_key?(:traditional_band_commitment)
    @user.traditional_band_touring = params[:traditional_band_touring] if params.has_key?(:traditional_band_touring)

    # paid sessions
    @user.paid_sessions = params[:paid_sessions] if params.has_key?(:paid_sessions)
    @user.paid_sessions_hourly_rate = params[:paid_sessions_hourly_rate] if params.has_key?(:paid_sessions_hourly_rate)
    @user.paid_sessions_daily_rate = params[:paid_sessions_daily_rate] if params.has_key?(:paid_sessions_daily_rate)

    # free sessions
    @user.free_sessions = params[:free_sessions] if params.has_key?(:free_sessions)

    # co-writing
    @user.cowriting = params[:cowriting] if params.has_key?(:cowriting)
    @user.cowriting_purpose = params[:cowriting_purpose] if params.has_key?(:cowriting_purpose)

    @user.want_jamblaster = params[:want_jamblaster] if params.has_key?(:want_jamblaster)
    @user.mod_merge(params[:mods]) if params[:mods]

    # allow keyword of 'LATEST' to mean set the notification_seen_at to the most recent notification for this user
    if params.has_key?(:notification_seen_at)
      @user.update_notification_seen_at params[:notification_seen_at]
    end

    @user.update_online_presences(params[:online_presences]) if params.has_key?(:online_presences)
    @user.update_performance_samples(params[:performance_samples]) if params.has_key?(:performance_samples)
    @user.update_calendars(params[:calendars]) if params.has_key?(:calendars)
    @user.is_a_student = params[:student] if params.has_key?(:student)
    @user.is_a_teacher = params[:teacher] if params.has_key?(:teacher)
    @user.school_interest = !!params[:school_interest]
    @user.education_interest = !!params[:education_interest]
    @user.retailer_interest = !!params[:retailer_interest]
    @user.desired_package = LessonPackageType.find_by_package_type!(params[:desired_package]) if params.has_key?(:desired_package)
    if @user.save

      test_drive_package_details = params[:test_drive_package]
      test_drive_package = TestDrivePackage.find_by_name(test_drive_package_details[:name]) if test_drive_package_details
      @user.handle_test_drive_package(test_drive_package, test_drive_package_details) if test_drive_package
    end

    if @user.errors.any?
      respond_with @user, :status => :unprocessable_entity
    else
      respond_with @user, responder: ApiResponder, :status => 200
    end
  end

  # a user that is created administratively has an incomplete profile
  # when they first visit the confirmation page by clicking the link in their email.
  def complete

    signup_token = params[:signup_token]
    user = User.find_by_signup_token(signup_token)

    if user.nil?
      return
    end

    user.updating_password = true

    user.easy_save(
        params[:first_name],
        params[:last_name],
        nil, # email can't be edited at this phase.  We need to get them into the site, and they can edit on profile page if they really want
        params[:password],
        params[:password_confirmation],
        true, # musician
        params[:gender],
        params[:birth_date],
        params[:isp],
        params[:city],
        params[:state],
        params[:country],
        params[:instruments],
        params[:photo_url])

    if user.errors.any?
      render :json => user.errors.full_messages(), :status => :unprocessable_entity
    else
      # log the user in automatically
      user.signup_confirm
      sign_in(user)
      respond_with user, responder: ApiResponder, :status => 200
    end
  end

  def delete
    @user.destroy
    respond_with responder: ApiResponder, :status => 204
  end

  def signup_confirm
    @user = UserManager.new.signup_confirm(params[:signup_token])

    unless @user.errors.any?
      respond_with @user, responder: ApiResponder, :location => api_user_detail_url(@user)
    else
      response.status = :unprocessable_entity
      respond_with @user, responder: ApiResponder
    end
  end

  def set_password

    @user.set_password(params[:old_password], params[:new_password], params[:new_password_confirm])

    if @user.errors.any?
      response.status = :unprocessable_entity
      respond_with @user
    else
      sign_in(@user)
      respond_with @user, responder: ApiResponder, status: 200
    end
  end

  def reset_password
    begin
      User.reset_password(params[:email], ApplicationHelper.base_uri(request))
    rescue JamRuby::JamArgumentError
      render :json => {:message => ValidationMessages::EMAIL_NOT_FOUND}, :status => 403
    end
    respond_with responder: ApiResponder, :status => 204
  end

  def reset_password_token
    begin
      User.set_password_from_token(params[:email], params[:token], params[:new_password], params[:new_password_confirm])
    rescue JamRuby::JamArgumentError
      # FIXME
      # There are some other errors that can happen here, besides just EMAIL_NOT_FOUND
      render :json => {:message => ValidationMessages::EMAIL_NOT_FOUND}, :status => 403
    end
    set_remember_token(@user)
    respond_with responder: ApiResponder, :status => 204
  end

  ###################### AUTHENTICATION ###################
  def auth_session_create
    @user = User.authenticate(params[:email], params[:password])

    if @user.nil?
      render :json => {:success => false}, :status => 404
    else
      sign_in @user
      render :json => {:success => true}, :status => 200
    end
  end

  def auth_session_delete
    sign_out
    render :json => {:success => true}, :status => 200
  end

  ###################### SESSION SETTINGS ###################
  def session_settings_show
    respond_with :json => @user.my_session_settings.to_json, responder: ApiResponder
  end

  ###################### SESSION HISTORY ###################
  def session_history_index
    @session_history = @user.session_history(params[:id], params[:band_id], params[:genre])
  end

  def session_user_history_index
    @session_user_history = @user.session_user_history(params[:id], params[:session_id])
  end

  ###################### BANDS ########################
  def band_index
    @bands = User.band_index(params[:id])
  end

  ###################### LIKERS ########################
  def liker_index
    # NOTE: liker_index.rabl template references the likers property
    @user = User.find(params[:id])
  end

  ###################### LIKES #########################
  def liking_index
    @user = User.find(params[:id])
  end

  def liking_create
    @user = User.find(params[:id])
    if !params[:user_id].nil?
      @user.create_user_liking(params[:user_id])

    elsif !params[:band_id].nil?
      @user.create_band_liking(params[:band_id])
    end

    respond_with @user, responder: ApiResponder, :location => api_user_liking_index_url(@user)
  end

  def liking_destroy
    User.delete_liking(params[:id], params[:likable_id])
    respond_with responder: ApiResponder, :status => 204
  end

  ###################### FOLLOWERS ########################
  def follower_index
    # NOTE: follower_index.rabl template references the followers property
    @user = User.find(params[:id])
  end

  ###################### FOLLOWINGS #######################
  def following_index
    @user = User.find(params[:id])
  end

  def following_create
    @user = User.find(params[:id])
    if !params[:user_id].nil?
      @user.create_user_following(params[:user_id])

    elsif !params[:band_id].nil?
      @user.create_band_following(params[:band_id])
    end

    respond_with @user, responder: ApiResponder, :location => api_user_following_index_url(@user)
  end

  def following_destroy
    User.delete_following(params[:id], params[:followable_id])
    respond_with responder: ApiResponder, :status => 204
  end

  ###################### FAVORITES ########################
  def favorite_index
    @user = User.find(params[:id])
  end

  def favorite_create
    @favorite = UserFavorite.new()
    User.create_favorite(params[:id], params[:recording_id])

    @user = User.find(params[:id])
    respond_with @user, responder: ApiResponder, :location => api_favorite_index_url(@user)
  end

  def favorite_destroy
    User.delete_favorite(params[:id], params[:recording_id])
    respond_with responder: ApiResponder, :status => 204
  end

  ###################### FRIENDS ##########################
  def friend_request_index
    # get all outgoing and incoming friend requests
    @friend_requests = FriendRequest.where("(friend_id='#{params[:id]}' AND status is null) OR user_id='#{params[:id]}'")
  end

  def friend_request_show
    @friend_request = FriendRequest.find(params[:friend_request_id])
    raise JamRuby::JamPermissionError, 'not allowed to view someone else\'s friend request' if @friend_request.friend_id != @user.id && @friend_request.user_id != @user.id

    respond_with @friend_request, responder: ApiResponder, :status => 200
  end

  def friend_request_create
    @friend_request = FriendRequest.save(nil,
                                         params[:id],
                                         params[:friend_id],
                                         nil,
                                         params[:message])

    respond_with @friend_request, responder: ApiResponder, :status => 201, :location => api_friend_request_detail_url(@user, @friend_request)
  end

  def friend_request_update
    @friend_request = FriendRequest.save(params[:friend_request_id],
                                         params[:id],
                                         params[:friend_id],
                                         params[:status],
                                         nil)
    respond_with @friend_request, responder: ApiResponder, :status => 200
  end

  def friend_index
    # NOTE: friend_index.rabl template references the friends property
    @user = User.find(params[:id])
  end

  def friend_show
    @friend = Friendship.find_by_user_id_and_friend_id(params[:id], params[:friend_id])
  end

  def friend_destroy
    if current_user.id != params[:id] && current_user.id != params[:friend_id]
      render :json => {:message => "You are not allowed to delete this friendship."}, :status => 403
    end
    # clean up both records representing this "friendship"
    JamRuby::Friendship.delete_all "(user_id = '#{params[:id]}' AND friend_id = '#{params[:friend_id]}') OR (user_id = '#{params[:friend_id]}' AND friend_id = '#{params[:id]}')"
    respond_with responder: ApiResponder, :status => 204
  end

  ###################### NOTIFICATIONS ####################
  def notification_index
    if params[:type] == 'TEXT_MESSAGE'
      # you can ask for just text_message notifications

      raise JamArgumentError.new('can\'t be blank', 'receiver') if params[:receiver].blank?
      raise JamArgumentError.new('can\'t be blank', 'limit') if params[:limit].blank?
      raise JamArgumentError.new('can\'t be blank', 'offset') if params[:offset].blank?

      receiver_id = params[:receiver]
      limit = params[:limit].to_i
      limit = 20 if limit <= 0
      offset = params[:offset].to_i
      offset = 0 if offset < 0
      @notifications = Notification.joins(:source_user).where(description: 'TEXT_MESSAGE').where('(source_user_id = (?) AND target_user_id = (?)) OR (source_user_id = (?) AND target_user_id = (?))', @user.id, receiver_id, receiver_id, @user.id).offset(offset).limit(limit).order('created_at DESC')
    else
      limit = params[:limit].to_i
      limit = 20 if limit <= 0
      offset = params[:offset].to_i
      offset = 0 if offset < 0
      @notifications = @user.notifications.joins(:source_user).offset(offset).limit(limit)
    end

    respond_with @notifications, responder: ApiResponder, :status => 200
  end

  def notification_destroy
    Notification.delete(params[:notification_id])
    respond_with responder: ApiResponder, :status => 204
  end

  def notification_create
    @notification = Notification.send_text_message(Sanitize.fragment(params[:message], elements: HtmlSanitize::SAFE), current_user, User.find_by_id(params[:receiver]))
    respond_with_model(@notification, new: true)
  end


  ##################### BAND INVITATIONS ##################
  def band_invitation_index
    @invitations = @user.received_band_invitations
    respond_with @invitations, responder: ApiResponder, :status => 200
  end

  def band_invitation_show
    begin
      @invitation = BandInvitation.find(params[:invitation_id])
      respond_with @invitation, responder: ApiResponder, :status => 200

    rescue ActiveRecord::RecordNotFound
      render :json => {:message => ValidationMessages::BAND_INVITATION_NOT_FOUND}, :status => 404
    end
  end

  def band_invitation_update
    begin
      @invitation = BandInvitation.save(params[:invitation_id],
                                        nil,
                                        nil,
                                        nil,
                                        params[:accepted])

      respond_with @invitation, responder: ApiResponder, :status => 200

    rescue ActiveRecord::RecordNotFound
      render :json => {:message => ValidationMessages::BAND_INVITATION_NOT_FOUND}, :status => 404
    end
  end

  ###################### ACCOUNT SETTINGS #################
  def begin_update_email
    # begins email update by sending an email for the user to confirm their new email

    # NOTE: if you change confirm_email_link value below, you break outstanding email changes because links in user inboxes are broken
    confirm_email_link = confirm_email_url + "?token="

    current_user.begin_update_email(params[:update_email], params[:current_password], confirm_email_link)

    if current_user.errors.any?
      respond_with current_user, status: :unprocessable_entity
    else
      respond_with current_user, responder: ApiResponder, status: 200
    end
  end

  def finalize_update_email
    # used when the user goes to the confirmation link in their email
    @user = User.finalize_update_email(params[:token])

    sign_in(@user)

    respond_with current_user, responder: ApiResponder, status: 200
  end

  def isp_scoring
    data = request.body.read
    score = IspScoreBatch.new
    score.json_scoring_data = data
    if score.save
      render :text => 'scoring recorded'
    else
      render :text => "score invalid: #{score.errors.inspect}", status: 422
    end
  end

  ################# AVATAR #####################

  def update_avatar
    original_fpfile = params[:original_fpfile]
    cropped_fpfile = params[:cropped_fpfile]
    cropped_large_fpfile = params[:cropped_large_fpfile]
    crop_selection = params[:crop_selection]

    # public bucket to allow images to be available to public
    @user.update_avatar(original_fpfile, cropped_fpfile, cropped_large_fpfile, crop_selection, Rails.application.config.aws_bucket_public)

    if @user.errors.any?
      respond_with @user, status: :unprocessable_entity
    else
      respond_with @user, responder: ApiResponder, status: 200
    end
  end

  def delete_avatar
    @user.delete_avatar(Rails.application.config.aws_bucket_public)

    if @user.errors.any?
      respond_with @user, status: :unprocessable_entity
    else
      respond_with @user, responder: ApiResponder, status: 204
    end
  end

  def generate_filepicker_policy
    # generates a soon-expiring filepicker policy so that a user can only upload to their own folder in their bucket

    handle = params[:handle]

    call = 'pick,convert,store'

    policy = {:expiry => (DateTime.now + 5.minutes).to_i(),
              :call => call,
              #:path => 'avatars/' + @user.id + '/.*jpg'
    }

    # if the caller specifies a handle, add it to the hash
    unless handle.nil?
      start = handle.rindex('/') + 1
      policy[:handle] = handle[start..-1]
    end

    policy = Base64.urlsafe_encode64(policy.to_json)
    digest = OpenSSL::Digest.new('sha256')
    signature = OpenSSL::HMAC.hexdigest(digest, Rails.application.config.fp_secret, policy)

    render :json => {
               :signature => signature,
               :policy => policy
           }, :status => :ok
  end


  ###################### CRASH DUMPS #######################

  # This is very similar to api_music_sessions#perf_upload
  # This should largely be moved into a library somewhere in jam-ruby.
  def crash_dump
    # example of using curl to access this API:
    # curl -L -T some_file -X PUT http://localhost:3000/api/dumps?client_type=[MacOSX/Win32/JamBox]&client_version=[VERSION]&client_id=[CLIENT_ID]&session_id=[SESSION_ID]&timestamp=[TIMESTAMP]&fsize=10K&crash_context="Blahblahblab"
    # user_id is deduced if possible from the user's cookie.
    @dump = CrashDump.new

    user = User.find_by_id(params[:user_id])

    @dump.client_type = params[:client_type]
    @dump.client_version = params[:client_version]
    @dump.client_id = params[:client_id]
    @dump.user_id = user.id if user
    @dump.session_id = params[:session_id]
    @dump.timestamp = params[:timestamp]
    @dump.description = params[:description]
    @dump.fsize = params[:fsize]
    @dump.crash_context = params[:crash_context]
    if params.has_key?(:send_email)
      send_email = params[:send_email] == "true"
    else
      send_email = true
    end

    crash_date = params[:crash_date]

    # make sure client is passing version information - if not its too old
    unless (vdata = params[:version]).present?
      render(json: {message: "blank version data #{vdata}"}, status: :unprocessable_entity) && return
    end

    unless @dump.save
      # There are at least some conditions on valid dumps (need client_type)
      response.status = :unprocessable_entity
      respond_with @dump
      return
    end

    # This part is the piece that really needs to be decomposed into a library...
    if Rails.application.config.storage_type == :fog
      s3 = AWS::S3.new(:access_key_id => Rails.application.config.aws_access_key_id,
                       :secret_access_key => Rails.application.config.aws_secret_access_key)
      bucket = s3.buckets[Rails.application.config.aws_bucket]
      uri = @dump.uri
      expire = Time.now + 5.years
      read_url = bucket.objects[uri].url_for(:read,
                                             :expires => expire,
                                             :'response_content_type' => 'application/octet-stream').to_s
      #@dump.update_attribute(:uri, read_url)

      write_url = bucket.objects[uri].url_for(:write,
                                              :expires => Rails.application.config.crash_dump_data_signed_url_timeout,
                                              :'response_content_type' => 'application/octet-stream').to_s

      logger.debug("crash_dump can read from url #{read_url}")

      description = @dump.description
      description = 'crash' if description.nil?

      context = "#{description.capitalize} Context:\n\n#{@dump.crash_context}\n"
      subject = "#{description.capitalize} - #{@dump.session_id}"

      if description == 'crash'
        # preserve old behavior for crash dumps; Peter has filters for this format
        subject = "Crash for #{@dump.client_type} - #{user.email}, on #{crash_date}"
      end

      if user
        body = "Client #{description} for user #{user.email} (#{user.name})\n"
        body << "User admin url: #{user.admin_url}\n"
      else
        body = "Client crash for unknown user\n"
      end

      body << "Client type:    #{@dump.client_type}\n"
      body << "Client version: #{@dump.client_version}\n"
      body << "Download at:    #{read_url}\n"
      body << "Actual Crash Date: #{crash_date}\n"
      body << "File size: #{@dump.fsize}\n"
      body << context


      logger.debug("sending crash email with subject#{subject}")
      AdminMailer.crash_alert(subject: subject, body: body).deliver_now if send_email

      redirect_to write_url, status: 307
    else
      # we should store it here to aid in development, but we don't have to until someone wants the feature
      # so... just return 200
      render :json => {:id => @dump.id}, :status => 200
    end

  end


  # user progression tracking
  def downloaded_client
    @user = current_user
    @user.update_progression_field(:first_downloaded_client_at)

    if @user.errors.any?
      respond_with @user, :status => :unprocessable_entity
      return
    end
    render :json => {}, :status => 200

  end

  # user progression tracking
  def qualified_gear
    @user = current_user
    if params[:success]
      @user.update_progression_field(:first_certified_gear_at)

      connection = Connection.find_by_client_id(params[:client_id])
      # update last_jam location information
      @user.update_addr_loc(connection, User::JAM_REASON_FTUE) if connection

      if !@user.errors.any?
        # update audio gear latency information
        @user.update_audio_latency(connection, params[:audio_latency]) if params[:audio_latency]
      end
    else
      @user.failed_qualification(params[:reason])
    end

    if @user.errors.any?
      respond_with @user, :status => :unprocessable_entity
      return
    end

    render :json => {}, :status => 200
  end

  # user progression tracking
  def social_promoted
    @user = current_user
    @user.update_progression_field(:first_social_promoted_at)

    if @user.errors.any?
      respond_with @user, :status => :unprocessable_entity
      return
    end
    render :json => {}, :status => 200
  end

  def opened_jamtrack_web_player
    User.where(id: current_user.id).update_all(first_opened_jamtrack_web_player: Time.now)
    render :json => {}, :status => 200
  end

  def user_event
    event = UserEvent.new
    event.name = params[:name]
    event.detail = params[:detail].to_json if params[:detail]
    event.user_id = current_user.id if current_user
    event.save

    render :json => {}, :status => 200
  end

  # creates display-ready session data for sharing
  def share_session
    provider = params[:provider]
    music_session_id = params[:music_session]
    history = MusicSession.find(music_session_id)

    if provider == 'facebook'

      render json: {
                 description: view_context.description_for_music_session(history),
                 title: view_context.title_for_music_session(history, current_user),
                 photo_url: view_context.facebook_image_for_music_session(history),
                 url: share_token_url(history.share_token.token),
                 caption: 'www.jamkazam.com'
             }, status: 200

    elsif provider == 'twitter'

      render json: {
                 message: view_context.title_for_music_session(history, current_user)
             }, status: 200

    else
      render :json => {:errors => {:provider => ['not valid']}}, :status => 422
    end
  end

  # creates display-ready recording data for sharing
  def share_recording
    provider = params[:provider]
    claimed_recording_id = params[:claimed_recording]
    claimed_recording = ClaimedRecording.find(claimed_recording_id)

    if provider == 'facebook'

      render json: {
                 description: view_context.description_for_claimed_recording(claimed_recording),
                 title: view_context.title_for_claimed_recording(claimed_recording, current_user),
                 photo_url: view_context.facebook_image_for_claimed_recording(claimed_recording),
                 url: share_token_url(claimed_recording.share_token.token),
                 caption: 'www.jamkazam.com'
             }, status: 200

    elsif provider == 'twitter'

      render json: {
                 message: view_context.title_for_claimed_recording(history, current_user) + " at " + request.host_with_port
             }, status: 200


    else
      render :json => {:errors => {:provider => ['not valid']}}, :status => 422
    end
  end

  def affiliate_partner
    if oo = current_user.affiliate_partner
      if request.post?
        oo.address = params[:address]
        oo.tax_identifier = params[:tax_identifier]
        oo.paypal_id = params[:paypal_id]
        oo.save!
        render json: {}, status: 200

      elsif request.get?
        result = {}
        result['account'] = {
            'address' => oo.address.clone,
            'tax_identifier' => oo.tax_identifier,
            'paypal_id' => oo.paypal_id,
            'entity_type' => oo.entity_type,
            'partner_name' => oo.partner_name,
            'partner_id' => oo.partner_user_id,
            'id' => oo.id
        }
        if txt = oo.affiliate_legalese.try(:legalese)
          txt = ControllerHelp.instance.simple_format(txt)
        end
        result['agreement'] = {
            'legalese' => txt,
            'signed_at' => oo.signed_at
        }
        #result['signups'] = oo.referrals_by_date
        #result['earnings'] = [['April 2015', '1000 units', '$100']]
        render json: result.to_json, status: 200
      end
    else
      render :json => {:message => 'user not affiliate partner'}, :status => 400
    end
  end

  def affiliate_report
    begin
      affiliate = User
                      .where(:id => params[:id])
                      .includes(:affiliate_partner)
                      .limit(1)
                      .first
                      .affiliate_partner
      referrals_by_date = affiliate.referrals_by_date do |by_date|
        by_date.inject([]) { |rr, key| rr << key }
      end
      result = {
          :total_count => affiliate.referral_user_count,
          :by_date => referrals_by_date
      }
      render json: result.to_json, status: 200
    rescue
      render :json => {:message => $!.to_s}, :status => 400
    end
  end

  def add_play
    if params[:id].blank?
      render :json => {:message => "Playable ID is required"}, :status => 400
      return
    end

    play = PlayablePlay.new
    play.playable_id = params[:id]
    play.playable_type = params[:playable_type]
    play.player_id = params[:user_id]
    play.claimed_recording_id = params[:claimed_recording_id]
    play.ip_address = request.remote_ip
    play.save

    if play.errors.any?
      render :json => {:errors => play.errors}, :status => 422
    else
      render :json => {}, :status => 201
    end
  end

  # updates audio latency on the user, and associated connection
  def audio_latency
    Connection.transaction do
      @user.update_audio_latency(Connection.find_by_client_id(params[:client_id]), params[:audio_latency])
      respond_with_model(@user)
    end
  end

  def udp_reachable
    Connection.transaction do
      @connection = Connection.find_by_client_id!(params[:client_id])
      # deliberately don't updated_at on connection! only heartbeats do that
      Connection.where(:id => @connection.id).update_all(:udp_reachable => params[:udp_reachable])
      respond_with_model(@connection)
    end
  end

  def is_network_testing
    Connection.transaction do
      @connection = Connection.find_by_client_id!(params[:client_id])
      # deliberately don't updated_at on connection! only heartbeats do that
      Connection.where(:id => @connection.id).update_all(:is_network_testing => params[:is_network_testing])
      respond_with_model(@connection)
    end
  end

  def validate_data
    unless (data = params[:data]).present?
      render(json: {message: "blank data #{data}"}, status: :unprocessable_entity) && return
    end
    url = nil
    site = params[:sitetype]
    if site.blank? || 'url'==site
      url = data
    elsif Utils.recording_source?(site)
      rec_data = Utils.extract_recording_data(site, data)
      if rec_data
        render json: {message: 'Valid Site', recording_id: rec_data["id"], recording_title: rec_data["title"], data: data}, status: 200
        return
      else
        render json: {message: 'Invalid Site', data: data, errors: {site: ["Could not detect recording identifier"]}}, status: 200
        return
      end
    else
      url = Utils.username_url(data, site)
    end
    unless url.blank?
      if errmsg = Utils.site_validator(url, site)
        render json: {message: 'Invalid Site', data: data, errors: {site: [errmsg]}}, status: 200
      else
        render json: {message: 'Valid Site', data: data}, status: 200
      end
    else
      render json: {message: "unknown validation for data '#{params[:data]}', site '#{params[:site]}'"}, status: :unprocessable_entity
    end
  end

  def broadcast_notification
    @broadcast = BroadcastNotification.next_broadcast(current_user)

    if @broadcast
      # mark it as viewed
      @broadcast.did_view(current_user)
      respond_with_model(@broadcast)
    else
      render json: {}, status: 200
    end
  end

  # used to hide a broadcast notification from rotation temporarily
  def quiet_broadcast_notification
    @broadcast = BroadcastNotificationView.find_by_broadcast_notification_id_and_user_id(params[:broadcast_id], current_user.id)

    if @broadcast
      @broadcast.active_at = Date.today + 14 # 14 days in the future we'll re-instas
      @broadcast.save
    end

    render json: {}, status: 200
  end

  def lookup_user

    User.includes([{musician_instruments: :instrument},
                   {band_musicians: :user},
                   {genre_players: :genre},
                   :bands, :instruments, :genres, :jam_track_rights,
                   :affiliate_partner, :reviews, :review_summary, :recordings,
                   :teacher => [:subjects, :instruments, :languages, :genres, :teachers_languages, :experiences_teaching, :experiences_award, :experiences_education, :reviews, :review_summary]])
        .find(params[:id])
  end

  def try_posa_card
    @posa_card = PosaCard.find_by_code(params[:gift_card])

    if @posa_card.nil?
      return false
    end

    @posa_card.claim(current_user)

    if @posa_card.errors.any?
      respond_with_model(@posa_card)
    else
      if @posa_card.is_lesson_posa_card?
        render json: {gifted_jamclass: @posa_card.credits}, status: 200
      elsif @posa_card.card_type == PosaCard::JAM_TRACKS_10
        render json: {gifted_jamtracks: @posa_card.credits}, status: 200
      elsif @posa_card.card_type == PosaCard::JAM_TRACKS_5
        render json: {gifted_jamtracks: @posa_card.credits}, status: 200
      else
        raise 'unknown card_type ' + @posa_card.card_type
      end
    end

    return true
  end

  def try_gift_card

    @gift_card = GiftCard.find_by_code(params[:gift_card])

    if @gift_card.nil?
      render json: {errors: {gift_card: ['does not exist']}}, status: 422
      return
    end

    if current_user.gift_cards.count >= 5
      render json: {errors: {gift_card: ['has too many on account']}}, status: 422
      return
    end

    if @gift_card.user
      if @gift_card.user == current_user
        render json: {errors: {gift_card: ['already redeemed by you']}}, status: 422
        return
      else
        render json: {errors: {gift_card: ['already redeemed by another']}}, status: 422
        return
      end
    end

    @gift_card.user = current_user
    @gift_card.save

    if @gift_card.errors.any?
      respond_with_model(@gift_card)
      return
    else

      UserWhitelist.card_create(current_user, 'giftcard')
      # apply gift card items to everything in shopping cart
      current_user.reload
      ShoppingCart.apply_gifted_jamtracks(current_user)
      render json: {gifted_jamtracks: current_user.gifted_jamtracks}, status: 200
    end
  end

  def redeem_giftcard

    # first, try to find posa_card
    rendered = try_posa_card

    try_gift_card if !rendered

  end

  def test_drive_status
    @user = current_user
    @teacher = User.find(params[:teacher_id])
  end

  def onboardings
    @onboardings = current_user.onboarding_users.where('onboarding_status in (?)', [User::ONBOARDING_STATUS_ASSIGNED, User::ONBOARDING_STATUS_EMAILED]).order(:onboarder_assigned_at)
  end

  def update_onboarding
    user = User.find(params[:id])
    if params[:onboarding_lost_reason]
      user.onboarding_lost_reason = params[:onboarding_lost_reason]
      user.onboarding_lost_at = Date.today
    end
    if params[:onboarding_escalation_reason]
      user.onboarding_escalation_reason = params[:onboarding_escalation_reason]
      user.onboarding_escalated_at = Date.today
    end

    user.onboarding_email_1_sent_at = Date.today if params[:onboarding_email_1_sent_at]
    user.onboarding_email_2_sent_at = Date.today if params[:onboarding_email_2_sent_at]
    user.onboarding_email_3_sent_at = Date.today if params[:onboarding_email_3_sent_at]
    user.onboarding_email_4_sent_at = Date.today if params[:onboarding_email_4_sent_at]
    user.onboarding_email_5_sent_at = Date.today if params[:onboarding_email_5_sent_at]
    user.onboarding_test_session_scheduled_at = Date.today if params[:onboarding_test_session_scheduled_at]
    user.onboarding_test_session_at = params[:onboarding_test_session_at] if params[:onboarding_test_session_at]
    user.onboarding_test_session_outcome = params[:onboarding_test_session_outcome] if params[:onboarding_test_session_outcome]
    user.onboarding_onboarded_at = Date.today if params[:onboarding_onboarded_at]
    user.onboarding_onboarder_notes = params[:onboarding_onboarder_notes] if params[:onboarding_onboarder_notes]
    user.save
    user.reload
    @onboarding = user
  end

  def show_onboarding
    @onboarding = User.find(params[:id])
  end

  ###################### RECORDINGS #######################
  # def recording_index
  #   @recordings = User.recording_index(current_user, params[:id])
  #   respond_with @recordings, responder: ApiResponder, :status => 200
  # end

  # def recording_show
  #   hide_private = false

  #   # hide private recordings from anyone but the current user
  #   if current_user.id != params[:id]
  #     hide_private = true
  #   end

  #   @recording = Recording.find(params[:recording_id])
  #   if !@recording.public && hide_private
  #     render :json => { :message => "You are not allowed to access this recording." }, :status => 403
  #     #respond_with "You are not allowed to access this recording.", responder: ApiResponder, :status => 403
  #   else
  #     respond_with @recording, responder: ApiResponder, :status => 200
  #   end
  # end

  # def recording_create
  #   @recording = Recording.save(params[:recording_id],
  #                               params[:public],
  #                               params[:description],
  #                               params[:genres],
  #                               current_user.id,
  #                               params[:id],
  #                               false)

  #   @user = current_user
  #   respond_with @recording, responder: ApiResponder, :status => 201, :location => api_recording_detail_url(@user, @recording)
  # end

  # def recording_update
  #   @recording = Recording.save(params[:recording_id],
  #                               params[:public],
  #                               params[:description],
  #                               params[:genres],
  #                               current_user.id,
  #                               params[:id],
  #                               false)

  #   respond_with @recording, responder: ApiResponder, :status => 200
  # end

  # def recording_destroy
  #   @recording = Recording.find(params[:recording_id])
  #   @recording.delete
  #   respond_with responder: ApiResponder, :status => 204
  # end
end
