class ApiUsersController < ApiController

  before_filter :api_signed_in_user, :except => [:create, :signup_confirm, :auth_session_create, :complete, :finalize_update_email, :isp_scoring]
  before_filter :auth_user, :only => [:session_settings_show, :session_history_index, :session_user_history_index, :update, :delete,
                :like_create, :like_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]
  respond_to :json

  def index
    # don't return users that aren't yet confirmed
    @users = User.where('email_confirmed=TRUE').paginate(page: params[:page])
    respond_with @users, responder: ApiResponder, :status => 200
  end

  def show
    # don't return users that aren't yet confirmed
    @user = User.where('email_confirmed=TRUE').find(params[:id])
    respond_with @user, responder: ApiResponder, :status => 200
  end

  # this API call is disabled by virtue of it being commented out in routes.rb
  # the reason is that it has no captcha, and is therefore a bit abuseable
  # if someone wants to use it, please add in captcha or some other bot-protector
  def create
    # sends email to email account for confirmation
    @user = UserManager.new.signup(params[:first_name],
                                params[:last_name],
                                params[:email],
                                params[:password],
                                params[:password_confirmation],
                                params[:city],
                                params[:state],
                                params[:country],
                                params[:instruments],
                                params[:photo_url],
                                ApplicationHelper.base_uri(request) + "/confirm")

    # check for errors
    unless @user.errors.any?
      render :json => {}, :status => :ok # an empty response, but 200 OK
    else
      response.status = :unprocessable_entity
      respond_with @user, responder: ApiResponder
    end
  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)
    @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)

    @user.save

    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 @user.my_session_settings, 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 like_index
    @user = User.find(params[:id])
  end

  def band_like_index
    @user = User.find(params[:id])
  end

  def like_create
    id = params[:id]

    if !params[:user_id].nil?
      User.create_user_like(params[:user_id], id)
      respond_with @user, responder: ApiResponder, :location => api_user_like_index_url(@user)

    elsif !params[:band_id].nil?
      User.create_band_like(params[:band_id], id)
      respond_with @user, responder: ApiResponder, :location => api_band_like_index_url(@user)
    end
  end

  def like_destroy
    if !params[:user_id].nil?
      User.delete_like(params[:user_id], nil, params[:id])

    elsif !params[:band_id].nil?
      User.delete_like(nil, params[:band_id], params[:id])
    end

    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_show
    @following = UserFollowing.find_by_user_id_and_follower_id(params[:user_id], params[:id])
  end

  def band_following_index
    @user = User.find(params[:id])
  end

  def band_following_show
    @following = BandFollowing.find_by_band_id_and_follower_id(params[:band_id], params[:id])
  end

  def following_create
    id = params[:id]

    if !params[:user_id].nil?
      User.create_user_following(params[:user_id], id)
      respond_with @user, responder: ApiResponder, :location => api_user_following_index_url(@user)

    elsif !params[:band_id].nil?
      User.create_band_following(params[:band_id], id)
      respond_with @user, responder: ApiResponder, :location => api_band_following_index_url(@user)
    end
  end

  def following_destroy
    if !params[:user_id].nil?
      User.delete_following(params[:user_id], nil, params[:id])

    elsif !params[:band_id].nil?
      User.delete_following(nil, params[:band_id], params[:id])
    end

    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])
    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
    @notifications = @user.notifications
    respond_with @notifications, responder: ApiResponder, :status => 200
  end

  def notification_destroy
    Notification.delete(params[:notification_id])
    respond_with responder: ApiResponder, :status => 204
  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
    if request.post?
      data = request.body.read
      User.connection.execute("INSERT INTO isp_score_batch(json_scoring_data) VALUES ('#{data}')")
      render :text => 'scoring recorded'
      return
    end
    render :nothing => true
  end

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

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

    # public bucket to allow images to be available to public
    @user.update_avatar(original_fpfile, cropped_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::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/users/dump.json?client_type=[MACOSX/Win32/JamBox]&client_version=[VERSION]&client_id=[CLIENT_ID]&session_id=[SESSION_ID]&timestamp=[TIMESTAMP]
    # user_id is deduced if possible from the user's cookie.
    @dump = CrashDump.new

    @dump.client_type = params[:client_type] 
    @dump.client_version = params[:client_version]
    @dump.client_id = params[:client_id]
    @dump.user_id = current_user 
    @dump.session_id = params[:session_id] 
    @dump.timestamp = params[:timestamp]

    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 SampleApp::Application.config.storage_type == :fog
      s3 = AWS::S3.new(:access_key_id => SampleApp::Application.config.aws_access_key_id,
                       :secret_access_key => SampleApp::Application.config.aws_secret_access_key)
      # Fixme: Should we use the same bucket for everything?
      bucket = s3.buckets[SampleApp::Application.config.aws_bucket]
      url = bucket.objects[@dump.uri].url_for(:write, :expires => SampleApp::Application.config.crash_dump_data_signed_url_timeout, :'response_content_type' => 'application/octet-stream').to_s

      logger.debug("crash_dump can upload to url #{url}")

      redirect_to url
    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)
    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

  ###################### 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
