module JamRuby
  class Band < ActiveRecord::Base
    include HtmlSanitize
      html_sanitize strict: [:biography, :website, :name]

    attr_accessible :name, :website, :biography, :city, :state,
    :country, :original_fpfile_photo, :cropped_fpfile_photo, :cropped_large_fpfile_photo,
    :cropped_s3_path_photo, :cropped_large_s3_path_photo, :crop_selection_photo, :photo_url, :large_photo_url,
    :band_type, :band_status, :concert_count, :add_new_members, :play_commitment, :touring_option, :paid_gigs,
    :free_gigs, :hourly_rate, :gig_minimum

    attr_accessor :updating_photo, :skip_location_validation, :skip_genre_validation

    self.primary_key = 'id'

    before_save :stringify_photo_info , :if => :updating_photo
    validates :biography, no_profanity: true, presence:true, length: {maximum: 4000}
    validates :name, presence: true, no_profanity: true
    validates :country, presence: true, :unless => :skip_location_validation
    validates :state, presence: true, :unless => :skip_location_validation
    validates :city, presence: true, :unless => :skip_location_validation

    validate :validate_photo_info
    validate :require_at_least_one_genre, :unless => :skip_genre_validation
    validate :limit_max_genres
    validates_numericality_of :hourly_rate, greater_than:0, less_than:100000, :if => :paid_gigs
    validates_numericality_of :gig_minimum, greater_than:0, less_than:200000, :if => :paid_gigs

    before_save :check_lat_lng
    before_save :check_website_url

    # instruments
    has_many :musician_instruments, :class_name => "JamRuby::MusicianInstrument", :foreign_key=> 'player_id'
    has_many :instruments, :through => :musician_instruments, :class_name => "JamRuby::Instrument", :foreign_key=> 'player_id'

    has_many :online_presences, :class_name => "JamRuby::OnlinePresence", :foreign_key=> 'player_id'
    has_many :performance_samples, :class_name => "JamRuby::PerformanceSample", :foreign_key=> 'player_id'


    # musicians
    has_many :band_musicians, :class_name => "JamRuby::BandMusician", dependent: :destroy
    has_many :users, :through => :band_musicians, :class_name => "JamRuby::User"

    # genres
    has_many :genre_players, as: :player, class_name: "JamRuby::GenrePlayer", dependent: :destroy
    has_many :genres, through: :genre_players, class_name: "JamRuby::Genre"

    # recordings
    has_many :recordings, :class_name => "JamRuby::Recording", :foreign_key => "band_id", dependent: :destroy

    # self.id = likable_id in likes table
    has_many :likers, :as => :likable, :class_name => "JamRuby::Like", :dependent => :destroy

    # self.id = followable_id in follows table
    has_many :followers, :as => :followable, :class_name => "JamRuby::Follow", :dependent => :destroy

    # invitations
    has_many :invitations, :inverse_of => :band, :class_name => "JamRuby::BandInvitation", :foreign_key => "band_id", dependent: :destroy

    # music_sessions
    has_many :music_sessions, :class_name => "JamRuby::MusicSession", foreign_key: :band_id, :inverse_of => :band, dependent: :destroy

    # events
    has_many :event_sessions, :class_name => "JamRuby::EventSession", dependent: :destroy

    include Geokit::ActsAsMappable::Glue unless defined?(acts_as_mappable)
    acts_as_mappable

    def liker_count
      self.likers.size
    end

    def follower_count
      # FIXME:  this could be a lot of followers; calling size loads all the data into memory
      self.followers.size
    end

    def recording_count
      self.recordings.size
    end

    def session_count
      self.music_sessions.size
    end

    def latitude
      lat
    end

    def longitude
      lng
    end

    def recent_history(session_id, claimed_recording_id)

      recording_exclusion = "claimed_recordings.id != '#{claimed_recording_id}'" if claimed_recording_id
      recordings = Recording
        .joins(:claimed_recordings)
        .where(:band_id => self.id)
        .where('claimed_recordings.is_public=true')
        .where(recording_exclusion)
        .order('created_at DESC')
        .limit(10)

      session_exclusion = "music_sessions.id != '#{session_id}'" if session_id
      msh = MusicSession
        .where(:band_id => self.id)
        .where(:fan_access => true)
        .where(session_exclusion)
        .order('created_at DESC')
        .limit(10)

      result = recordings.concat(msh)
      result.sort! {|a,b| b.created_at <=> a.created_at}.first(5)
    end

    def location
      loc = self.city.blank? ? '' : self.city
      loc = loc.blank? ? self.state : "#{loc}, #{self.state}" unless self.state.blank?
      #loc = loc.blank? ? self.country : "#{loc}, #{self.country}" unless self.country.blank?
      loc
    end

    def validate_photo_info
      if updating_photo
        # we want to mak sure that original_fpfile and cropped_fpfile seems like real fpfile info objects (i.e, json objects from filepicker.io)
        errors.add(:original_fpfile_photo, ValidationMessages::INVALID_FPFILE) if self.original_fpfile_photo.nil? || self.original_fpfile_photo["key"].nil? || self.original_fpfile_photo["url"].nil?
        errors.add(:cropped_fpfile_photo, ValidationMessages::INVALID_FPFILE) if self.cropped_fpfile_photo.nil? || self.cropped_fpfile_photo["key"].nil? || self.cropped_fpfile_photo["url"].nil?
        errors.add(:cropped_large_fpfile_photo, ValidationMessages::INVALID_FPFILE) if self.cropped_large_fpfile_photo.nil? || self.cropped_large_fpfile_photo["key"].nil? || self.cropped_large_fpfile_photo["url"].nil?
      end
    end

    def add_member(user_id, admin)
      BandMusician.create(:band_id => self.id, :user_id => user_id, :admin => admin)
    end

    def self.musician_index(band_id)
      @musicians = User
        .select("users.*, bands_musicians.admin AS band_admin")
        .joins(:band_musicians)
        .where(:bands_musicians => {:band_id => "#{band_id}"})
    end

    def self.pending_musicians(band_id)
      @musicians = User
        .select("users.*, band_invitations.id AS invitation_id")
        .joins(:received_band_invitations)
        .where(:band_invitations => {:band_id => "#{band_id}"})
        .where(:band_invitations => {:accepted => nil})
    end

    def self.recording_index(current_user, band_id)
      hide_private = false
      band = Band.find(band_id)

      # hide private Recordings from anyone who's not in the Band
      unless band.users.exists? current_user
        hide_private = true
      end

      if hide_private
        recordings = Recording.joins(:band_recordings)
          .where(:bands_recordings => {:band_id => "#{band_id}"}, :public => true)

      else
        recordings = Recording.joins(:band_recordings)
          .where(:bands_recordings => {:band_id => "#{band_id}"})
      end

      return recordings
    end

    def self.build_band(user, params)

      id  = params[:id]

      # ensure person creating this Band is a Musician
      unless user.musician?
        raise JamPermissionError, "must be a musician"
      end

      band = id.blank? ? Band.new : Band.find(id)

      # ensure user updating Band details is a Band member
      unless band.new_record? || band.users.exists?(user.id)
        raise JamPermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
      end

      band.name = params[:name] if params.has_key?(:name)
      band.website = params[:website] if params.has_key?(:website)
      band.biography = params[:biography] if params.has_key?(:biography)
      band.city = params[:city] if params.has_key?(:city)
      band.state = params[:state] if params.has_key?(:state)
      band.country = params[:country] if params.has_key?(:country)
      band.photo_url = params[:photo_url] if params.has_key?(:photo_url)
      band.logo_url = params[:logo_url] if params.has_key?(:logo_url)

      band.paid_gigs = params[:paid_gigs] if params.has_key?(:paid_gigs)
      band.free_gigs = params[:free_gigs] if params.has_key?(:free_gigs)
      band.hourly_rate = (params.has_key?(:hourly_rate) && params[:hourly_rate].to_i > 0) ? params[:hourly_rate] : nil
      band.gig_minimum = (params.has_key?(:gig_minimum) && params[:hourly_rate].to_i > 0) ? params[:gig_minimum] : nil
      band.add_new_members = params[:add_new_members] if params.has_key?(:add_new_members)
      band.touring_option = params[:touring_option] if params.has_key?(:touring_option)
      band.band_type = params[:band_type] if params.has_key?(:band_type)
      band.band_status = params[:band_status] if params.has_key?(:band_status)
      band.concert_count = params[:concert_count] if params.has_key?(:concert_count)
      band.play_commitment = params[:play_commitment] if params.has_key?(:play_commitment)

      if params[:validate_genres] || params[:genres].present?
        # loop through each genre in the array and save to the db
        genres = []
        params[:genres].each { |genre_id| genres << Genre.find(genre_id) } if params[:genres].present?
        band.genres = genres
        band.skip_genre_validation = false
      else
        params[:validate_genres]
        band.skip_genre_validation = true
      end

      unless band.new_record?
        OnlinePresence.delete_all(["player_id = ?", band.id])
        PerformanceSample.delete_all(["player_id = ?", band.id])
      end

      online_presences = params[:online_presences]
      if online_presences.present?
        online_presences.each do |op|
          new_presence = OnlinePresence.create(band, op, false)
          band.online_presences << new_presence
        end
      end

      performance_samples = params[:performance_samples]
      if performance_samples.present?
        performance_samples.each do |ps|
          band.performance_samples << PerformanceSample.create(band, ps, false)
        end
      end

      band
    end

    # helper method for creating / updating a Band
    def self.save(user, params)
      band = build_band(user, params)

      if band.save
        # add the creator as the admin
        BandMusician.create(:band_id => band.id, :user_id => user.id, :admin => true) if params[:id].blank?
      end

      band
    end

    def escape_filename(path)
      dir = File.dirname(path)
      file = File.basename(path)
      "#{dir}/#{ERB::Util.url_encode(file)}"
    end

    def update_photo(original_fpfile, cropped_fpfile, cropped_large_fpfile, crop_selection, aws_bucket)
      self.updating_photo = true

      cropped_s3_path = cropped_fpfile["key"]
      cropped_large_s3_path = cropped_large_fpfile["key"]

      self.update_attributes(
                                    :original_fpfile_photo => original_fpfile,
                                    :cropped_fpfile_photo => cropped_fpfile,
                                    :cropped_large_fpfile_photo => cropped_large_fpfile,
                                    :cropped_s3_path_photo => cropped_s3_path,
                                    :cropped_large_s3_path_photo => cropped_large_s3_path,
                                    :crop_selection_photo => crop_selection,
                                    :photo_url => S3Util.url(aws_bucket, escape_filename(cropped_s3_path), :secure => true),
                                    :large_photo_url => S3Util.url(aws_bucket, escape_filename(cropped_large_s3_path), :secure => true))
    end

    def delete_photo(aws_bucket)

      Band.transaction do

        unless self.cropped_s3_path_photo.nil?
          S3Util.delete(aws_bucket, File.dirname(self.cropped_s3_path_photo) + '/cropped.jpg')
          S3Util.delete(aws_bucket, self.cropped_s3_path_photo)
          S3Util.delete(aws_bucket, self.cropped_large_s3_path_photo)
        end

        return self.update_attributes(
                                      :original_fpfile_photo => nil,
                                      :cropped_fpfile_photo => nil,
                                      :cropped_large_fpfile_photo => nil,
                                      :cropped_s3_path_photo => nil,
                                      :cropped_large_s3_path_photo => nil,
                                      :crop_selection_photo => nil,
                                      :photo_url => nil,
                                      :large_photo_url => nil)
      end
    end

    def check_lat_lng
      if (city_changed? || state_changed? || country_changed?)
        update_lat_lng
      end
      true
    end

    def update_lat_lng
      if self.city
        query = { :city => self.city }
        query[:region] = self.state unless self.state.blank?
        query[:countrycode] = self.country unless self.country.blank?
        #if geo = GeoIpLocations.where(query).limit(1).first
        #  geo.latitude = nil if geo.latitude = 0
        #  geo.longitude = nil if geo.longitude = 0
        #  if geo.latitude && geo.longitude && (self.lat != geo.latitude || self.lng != geo.longitude)
        #    self.lat, self.lng = geo.latitude, geo.longitude
        #    return true
        #  end
        #end
      end
      self.lat, self.lng = nil, nil
      false
    end

    def check_website_url
      if website_changed? && self.website.present?
        self.website.strip!
        self.website = "http://#{self.website}" unless self.website =~ /^http/
      end
      true
    end

    def to_s
      name
    end

    def in_real_session?(session)
      b_members = self.users.sort_by(&:id).map(&:id)
      s_members = session.users.sort_by(&:id).map(&:id)
      (b_members - s_members).blank?
    end

    def self.after_maxmind_import(use_copied = true)
      table_suffix = use_copied ? '_copied' : ''
      Band.connection.execute("UPDATE bands SET lat = geo.latitude, lng = geo.longitude FROM geoiplocations#{table_suffix} as geo WHERE bands.city = geo.city AND bands.state = geo.region AND bands.country = geo.countrycode")
    end

    def self.is_member?(band_id, user_id)
      BandMusician.where(band_id: band_id, user_id: user_id)
        .limit(1)
        .present?
    end

    private

    def require_at_least_one_genre
      if self.genres.size < Limits::MIN_GENRES_PER_BAND
        errors.add(:genres, ValidationMessages::BAND_GENRE_MINIMUM_NOT_MET)
      end
    end

    def limit_max_genres
      if self.genres.size > Limits::MAX_GENRES_PER_BAND
        errors.add(:genres, ValidationMessages::BAND_GENRE_LIMIT_EXCEEDED)
      end
    end

    def stringify_photo_info
      # fpfile comes in as a hash, which is a easy-to-use and validate form.  However, we store it as a VARCHAR,
      # so we need t oconvert it to JSON before storing it (otherwise it gets serialized as a ruby object)
      # later, when serving this data out to the REST API, we currently just leave it as a string and make a JSON capable
      # client parse  it, because it's very rare when it's needed at all
      self.original_fpfile_photo = original_fpfile_photo.to_json if !original_fpfile_photo.nil?
      self.cropped_fpfile_photo = cropped_fpfile_photo.to_json if !cropped_fpfile_photo.nil?
      self.crop_selection_photo = crop_selection_photo.to_json if !crop_selection_photo.nil?
    end
  end
end
