# frozen_string_literal: true

require "#{File.dirname( __FILE__ )}/../spec_helper"

describe UsersController, "dashboard" do
  elastic_models( Observation )
  it "should be accessible when signed in" do
    user = User.make!
    sign_in user
    get :dashboard
    expect( response ).to be_successful
  end

  describe "announcements" do
    it "should target a site" do
      site = create :site
      annc = create :announcement
      annc.sites << site
      user = create :user, site: site
      sign_in user
      get :dashboard, params: { inat_site_id: site.id }
      expect( assigns( :announcements ) ).to include annc
    end

    it "should include an anouncement without a site if one with a site exists" do
      annc = create :announcement
      site = create :site
      site_annc = create :announcement
      site_annc.sites << site
      user = create :user, site: site
      sign_in user
      get :dashboard, params: { inat_site_id: site.id }
      expect( assigns( :announcements ) ).to include site_annc
      expect( assigns( :announcements ) ).to include annc
    end

    # The intent is to allow the creation of a siteless announcement that can
    # be *overridden* for a site, e.g. an announcement to all iNat users that
    # iNatMX chooses to translate into Spanish
    describe "there exists another annc targeting a site that excludes non-site anncs" do
      it "should not include an anouncement without a site" do
        annc = create :announcement
        site = create :site
        site_annc = create :announcement, excludes_non_site: true
        site_annc.sites << site
        user = create :user, site: site
        sign_in user
        get :dashboard, params: { inat_site_id: site.id }
        expect( assigns( :announcements ) ).to include site_annc
        expect( assigns( :announcements ) ).not_to include annc
      end
    end

    it "should target locales" do
      a = Announcement.make!
      locale_a = Announcement.make!( locales: ["es"] )
      u = User.make!( locale: "es" )
      sign_in u
      get :dashboard, params: { locale: "es" }
      expect( assigns( :announcements ) ).to include locale_a
      expect( assigns( :announcements ) ).not_to include a
    end

    it "should appear for everyone without targeting" do
      a = Announcement.make!
      sign_in User.make!
      get :dashboard
      expect( assigns( :announcements ) ).to include a
    end

    it "should appear for everyone without targeting even if the user has a site and a locale" do
      a = Announcement.make!
      site = Site.make!
      sign_in User.make!( locale: "es", site: site )
      get :dashboard, params: { inat_site_id: site.id }
      expect( assigns( :announcements ) ).to include a
    end

    describe "rendering" do
      render_views

      describe "for regular users" do
        let( :user ) { create :user }
        before { sign_in user }

        it "should not include dismissed" do
          a = create :announcement, dismissible: true
          a.dismiss_user_ids << user.id
          a.save!
          expect( a ).to be_dismissed_by user
          get :dashboard
          expect( response.body ).not_to include a.body
        end

        it "should not be shown if targeting staff" do
          a = create :announcement, prefers_target_staff: true
          get :dashboard
          expect( response.body ).not_to include a.body
        end

        it "should create an AnnouncementImpression" do
          a = create :announcement
          expect( AnnouncementImpression.where( announcement_id: a.id ).count ).to eq 0
          get :dashboard
          expect( response.body ).to include a.body
          expect( AnnouncementImpression.where( announcement_id: a.id ).count ).to eq 1
        end
      end

      it "should target staff" do
        user = create :user, :as_admin
        sign_in user
        a = create :announcement, prefers_target_staff: true
        get :dashboard
        expect( response.body ).to include a.body
      end

      it "should exclude donorbox monthly supporters" do
        user = create :user,
          donorbox_donor_id: 1,
          donorbox_plan_status: "active",
          donorbox_plan_type: "monthly"
        expect( user ).to be_monthly_donor
        sign_in user
        a = create :announcement, target_logged_in: Announcement::YES, prefers_exclude_monthly_supporters: true
        get :dashboard
        expect( response.body ).not_to include a.body
      end

      it "should exclude fundraiseup monthly supporters" do
        user = create :user,
          virtuous_donor_contact_id: 1,
          fundraiseup_plan_status: "active",
          fundraiseup_plan_frequency: "monthly"
        expect( user ).to be_monthly_donor
        sign_in user
        a = create :announcement, target_logged_in: Announcement::YES, prefers_exclude_monthly_supporters: true
        get :dashboard
        expect( response.body ).not_to include a.body
      end
    end
  end
end

describe UsersController, "update" do
  let( :user ) { User.make! }
  before { sign_in user }

  it "changes updated_at when changing preferred_project_addition_by" do
    expect do
      put :update, params: { id: user.id, user: { preferred_project_addition_by: "none" } }
      user.reload
    end.to change( user, :updated_at )
  end
end

describe UsersController, "delete" do
  let( :user ) { User.make! }
  elastic_models( Observation )

  it "destroys in a delayed job" do
    sign_in user
    delete :destroy, params: { id: user.id, confirmation: user.login, confirmation_code: user.login }
    expect( Delayed::Job.where( "handler LIKE '%sane_destroy%'" ).count ).to eq 1
    expect( Delayed::Job.where( "unique_hash = '{:\"User::sane_destroy\"=>#{user.id}}'" ).
      count ).to eq 1
  end

  it "redirects with the account_deleted=true parameter" do
    sign_in user
    delete :destroy, params: { id: user.id, confirmation: user.login, confirmation_code: user.login }
    expect( response.redirect_url ).to match /account_deleted=true/
  end

  it "should be possible for the user" do
    sign_in user
    without_delay { delete :destroy, params: { id: user.id, confirmation: user.login, confirmation_code: user.login } }
    expect( response ).to be_redirect
    expect( User.find_by_id( user.id ) ).to be_blank
  end

  it "should be impossible for everyone else" do
    nogoodnik = User.make!
    sign_in nogoodnik
    delete :destroy, params: { id: user.id }
    expect( User.find_by_id( user.id ) ).not_to be_blank
  end
end

describe UsersController, "set_spammer" do
  elastic_models( Observation )

  describe "non-curators" do
    it "cannot access it" do
      post :set_spammer
      expect( response ).to be_redirect
      expect( flash[:alert] ).to eq "You need to sign in or sign up before continuing."
    end
  end

  describe "curators" do
    before( :each ) do
      @curator = make_curator
      UserPrivilege.make!( privilege: UserPrivilege::INTERACTION, user: @curator )
      sign_in( @curator )
      request.env["HTTP_REFERER"] = "/"
    end

    it "can access it" do
      post :set_spammer
      expect( response ).not_to be_redirect
      expect( flash[:alert] ).to be_blank
    end

    it "can set spammer to true" do
      @user = User.make!( spammer: nil )
      post :set_spammer, params: { id: @user.id, spammer: "true" }
      @user.reload
      expect( @user.spammer ).to be true
    end

    describe "when setting to non-spammer" do
      it "removes spam flags" do
        @user = User.make!
        obs = Observation.make!( user: @user )
        @user.update( spammer: true )
        Flag.make!( flaggable: obs, flag: Flag::SPAM )
        expect( @user.spammer ).to be true
        expect( @user.flags_on_spam_content.count ).to eq 1
        post :set_spammer, params: { id: @user.id, spammer: "false" }
        @user.reload
        expect( @user.spammer ).to be false
        expect( @user.flags_on_spam_content.count ).to eq 0
      end

      it "resolves spam flags" do
        o = Observation.make!
        f = Flag.make!( flaggable: o, flag: Flag::SPAM )
        expect( f ).not_to be_resolved
        post :set_spammer, params: { id: o.user_id, spammer: "false" }
        f.reload
        expect( f ).to be_resolved
      end

      it "marks the current user as the resolver" do
        o = Observation.make!
        f = Flag.make!( flaggable: o, flag: Flag::SPAM )
        post :set_spammer, params: { id: o.user_id, spammer: "false" }
        f.reload
        expect( f.resolver ).to eq @curator
      end
    end

    it "does not resolve spam flags when setting to spammer" do
      o = Observation.make!
      f = Flag.make!( flaggable: o, flag: Flag::SPAM )
      expect( f ).not_to be_resolved
      post :set_spammer, params: { id: o.user_id, spammer: "true" }
      f.reload
      expect( f ).not_to be_resolved
    end

    it "sets the user_id of the flag to the current_user" do
      u = User.make!
      post :set_spammer, params: { id: u.id, spammer: "true" }
      u.reload
      expect( u.flags.last.user ).to eq @curator
    end

    it "resolves the spam flag on the user when setting to non-spammer" do
      u = User.make!( spammer: true )
      post :set_spammer, params: { id: u.id, spammer: "true" }
      u.reload
      flag = u.flags.detect {| f | f.flag == Flag::SPAM }
      expect( flag ).not_to be_blank
      post :set_spammer, params: { id: u.id, spammer: "false" }
      flag.reload
      expect( flag ).to be_resolved
    end
  end
end

describe UsersController, "spam" do
  let( :spammer ) { User.make!( spammer: true ) }

  it "should render 403 when the user is a spammer" do
    get :show, params: { id: spammer.id }
    expect( response.response_code ).to eq 403
  end
end

describe UsersController, "update_session" do
  it "should set session attributes" do
    session[:prefers_observations_search_subview] = "list"
    get :update_session, params: { prefers_observations_search_subview: "grid" }
    expect( session[:prefers_observations_search_subview] ).to eq "grid"
  end

  it "should save preferences for logged in users" do
    user = User.make!( prefers_observations_search_subview: "list" )
    expect( user.prefers_observations_search_subview ).to eq "list"
    sign_in( user )
    get :update_session, params: { prefers_observations_search_subview: "grid" }
    user.reload
    expect( user.prefers_observations_search_subview ).to eq "grid"
  end
end

describe UsersController, "merge" do
  let( :normal_user ) { User.make! }
  let( :curator_user ) { make_curator }
  let( :admin_user ) { make_admin }
  let( :keeper_user ) { User.make!( login: "keeper", name: "keeper" ) }
  let( :reject_user ) { User.make!( login: "reject", name: "reject" ) }
  it "should not work for normal users" do
    sign_in normal_user
    after_delayed_job_finishes do
      expect do
        put :merge, params: { id: keeper_user.id, reject_user_id: reject_user.id }
      end.to throw_symbol( :abort )
    end
    expect( User.find_by_id( reject_user.id ) ).not_to be_blank
  end
  it "should not work for curators" do
    sign_in curator_user
    after_delayed_job_finishes do
      expect do
        put :merge, params: { id: keeper_user.id, reject_user_id: reject_user.id }
      end.to throw_symbol( :abort )
    end
    expect( User.find_by_id( reject_user.id ) ).not_to be_blank
  end
  it "should work for site admins" do
    sign_in admin_user
    after_delayed_job_finishes do
      put :merge, params: { id: keeper_user.id, reject_user_id: reject_user.id }
    end
    expect( User.find_by_id( reject_user.id ) ).to be_blank
  end
  describe "reindexing" do
    elastic_models( Project )
    it "should happen for projects the reject created" do
      proj = Project.make!( :collection, user: reject_user )
      es_proj = Project.elastic_search( where: { id: proj.id } ).results.results[0]
      proj_admin_user = User.find_by_id( es_proj.admins.first.user_id )
      expect( proj_admin_user ).to eq reject_user
      sign_in admin_user
      after_delayed_job_finishes do
        put :merge, params: { id: keeper_user.id, reject_user_id: reject_user.id }
      end
      expect( User.find_by_id( reject_user.id ) ).to be_blank
      es_proj = Project.elastic_search( where: { id: proj.id } ).results.results[0]
      proj_admin_user = User.find_by_id( es_proj.admins.first.user_id )
      expect( proj_admin_user ).to eq keeper_user
    end
  end
end

describe UsersController, "add_role" do
  let( :normal_user ) { User.make! }
  before { Role.make!( name: Role::CURATOR ) }
  it "should not work for a curator" do
    curator_user = make_curator
    sign_in curator_user
    put :add_role, params: { id: normal_user.id, role: Role::CURATOR }
    normal_user.reload
    expect( normal_user ).not_to be_is_curator
  end
  it "should work for a site_admin" do
    Site.make! if Site.default.blank?
    site = Site.make!
    sa = SiteAdmin.make!( site: site )
    normal_user.update!( site: site )
    sign_in sa.user
    put :add_role, params: { id: normal_user.id, role: Role::CURATOR }
    normal_user.reload
    expect( normal_user ).to be_is_curator
  end
  it "should set curator_sponsor to current user" do
    admin_user = make_admin
    sign_in admin_user
    put :add_role, params: { id: normal_user.id, role: Role::CURATOR }
    normal_user.reload
    expect( normal_user ).to be_is_curator
    expect( normal_user.curator_sponsor ).to eq admin_user
  end
end

describe UsersController, "remove_role" do
  let( :curator_user ) { make_curator }
  let( :target_curator_user ) { make_curator }
  it "should not work for a curator" do
    sign_in curator_user
    put :remove_role, params: { id: target_curator_user.id, role: Role::CURATOR }
    target_curator_user.reload
    expect( target_curator_user ).to be_is_curator
  end
  it "should work for a site_admin" do
    Site.make! if Site.default.blank?
    site = Site.make!
    sa = SiteAdmin.make!( site: site )
    target_curator_user.update!( site: site )
    sign_in sa.user
    put :remove_role, params: { id: target_curator_user.id, role: Role::CURATOR }
    target_curator_user.reload
    expect( target_curator_user ).not_to be_is_curator
  end
  it "should nilify curator_sponsor" do
    admin_user = make_admin
    sign_in admin_user
    put :remove_role, params: { id: curator_user.id, role: Role::CURATOR }
    updated_curator_user = User.find_by_id( curator_user.id )
    expect( updated_curator_user ).not_to be_is_curator
    expect( updated_curator_user.curator_sponsor ).to be_blank
  end
end

describe UsersController, "show" do
  it "should with a login" do
    u = User.make!
    get :show, params: { id: u.login }
    expect( assigns( :user ) ).to eq u
    expect( response ).to be_successful
  end

  describe "profile text" do
    render_views

    it "should not allow target=_blank" do
      u = create :user,
        spammer: false,
        description: '<a target="_blank" href="https://www.evil.com">foo</a>'
      get :show, params: { id: u.id }
      elt = Nokogiri::HTML( response.body ).at_css( "a[href='https://www.evil.com']" )
      expect( elt[:target] ).to be_nil
    end
  end
end

describe UsersController, "moderation" do
  let( :subject_user ) do
    user = UserPrivilege.make!( privilege: UserPrivilege::ORGANIZER ).user
    UserPrivilege.make!( privilege: UserPrivilege::INTERACTION, user: user )
    user
  end
  let( :curator ) { make_curator }
  it "should be viewable by curators" do
    sign_in make_curator
    get :moderation, params: { id: subject_user.login }
    expect( response.response_code ).to eq 200
  end
  it "should not be viewable by non-curators" do
    sign_in User.make!
    get :moderation, params: { id: subject_user.login }
    expect( response.response_code ).not_to eq 200
  end
  it "should not be viewable by a curator if it's about the curator" do
    sign_in curator
    get :moderation, params: { id: curator.login }
    expect( response.response_code ).not_to eq 200
  end
  it "should be viewable by an admin if it's about the admin" do
    admin = make_admin
    sign_in admin
    get :moderation, params: { id: admin.login }
    expect( response.response_code ).to eq 200
  end

  describe "rendering" do
    render_views
    before { sign_in curator }
    it "should work with a flag and ModeratorAction" do
      comment = create :comment, user: subject_user
      flag = create :flag, flaggable: comment
      moderator_action = create(
        :moderator_action,
        resource: comment,
        action: ModeratorAction::HIDE,
        user: make_curator
      )
      get :moderation, params: { id: subject_user.login }
      expect( response.response_code ).to eq 200
      expect( assigns( :records ) ).to include flag
      expect( assigns( :records ) ).to include moderator_action
    end
    it "should work with a ModeratorNote" do
      moderator_note = create :moderator_note, subject_user: subject_user
      get :moderation, params: { id: subject_user.login }
      expect( response.response_code ).to eq 200
      expect( assigns( :records ) ).to include moderator_note
    end
    it "displays ModeratorActions even if the moderated resource has since been deleted" do
      observation = Observation.make!
      identification = Identification.make!( observation: observation, user: observation.user )
      action = ModeratorAction.create(
        resource: identification,
        action: ModeratorAction::HIDE,
        user: make_admin,
        reason: Faker::Lorem.sentence
      )
      identification.destroy
      get :moderation, params: { id: observation.user_id }
      expect( response.response_code ).to eq 200
      expect( assigns( :records ) ).to include action
      expect( assigns( :records ).first.resource ).to be_nil
      expect( response.body ).to have_tag(
        "td", text: /Moderator Action.*Identification.*Removed/m
      )
    end
  end
end
