require 'rails_helper'

describe Api::V1::StancesController do
  let(:user) { create :user }
  let(:another_user) { create :user }
  let(:poll) { create :poll, discussion: discussion }
  let(:proposal) { create :poll_proposal, discussion: discussion }
  let(:poll_option) { create :poll_option, poll: poll }
  let(:old_stance) { create :stance, poll: poll, participant: user, poll_options: [poll_option] }
  let(:discussion) { create :discussion, group: group }
  let(:group) { create :group }
  let(:stance_params) {{
    poll_id: poll.id,
    stance_choices_attributes: [{poll_option_id: poll_option.id}],
    reason: "here is my stance"
  }}

  describe 'stance actions' do
    let(:actor) { create :user }
    let(:voter) { create :user }
    let(:group) { build(:group).tap(&:save) }
    let(:poll) { build(:poll, group: group).tap(&:save) }
    let(:stance) { poll.stances.create(participant_id: voter.id, latest: true) }

    before do
      group.add_admin! actor
    end
    describe 'revoke' do
      context 'with permission' do
        before { sign_in actor }
        it 'updates revoked at on stance' do
          expect(stance.reload.revoked_at).to eq nil
          post :revoke, params: {participant_id: stance.participant_id, poll_id: stance.poll_id }
          expect(response.status).to eq 200
          expect(stance.reload.revoked_at).not_to eq nil
        end
      end

      context 'without permission' do
        before { sign_in another_user }
        it 'returns access denied' do
          expect(stance.reload.revoked_at).to eq nil
          post :revoke, params: {participant_id: stance.participant_id, poll_id: stance.poll_id }
          expect(response.status).to eq 403
          expect(stance.reload.revoked_at).to eq nil
        end
      end
    end

    describe 'make_admin' do
      context 'with permission' do
        before { sign_in actor }
        it 'makes user an admin of poll' do
          expect(stance.reload.admin).to eq false
          post :make_admin, params: {participant_id: stance.participant_id, poll_id: stance.poll_id }
          expect(response.status).to eq 200
          expect(stance.reload.admin).to eq true
        end
      end

      context 'without permission' do
        before { sign_in another_user }
        it 'returns access denied' do
          expect(stance.reload.admin).to eq false
          post :make_admin, params: {participant_id: stance.participant_id, poll_id: stance.poll_id }
          expect(response.status).to eq 403
          expect(stance.reload.admin).to eq false
        end
      end
    end

    describe 'remove_admin' do
      before { stance.update(admin: true) }

      context 'with permission' do
        before { sign_in actor }
        it 'removes user admin status' do
          expect(stance.reload.admin).to eq true
          post :remove_admin, params: {participant_id: stance.participant_id, poll_id: stance.poll_id }
          expect(response.status).to eq 200
          expect(stance.reload.admin).to eq false
        end
      end

      context 'without permission' do
        before { sign_in another_user }
        it 'returns access denied' do
          expect(stance.reload.admin).to eq true
          post :make_admin, params: {participant_id: stance.participant_id, poll_id: stance.poll_id }
          expect(response.status).to eq 403
          expect(stance.reload.admin).to eq true
        end
      end
    end
  end

  describe 'index' do
    before { group.add_member! user }

    let(:recent_stance) { create :stance, poll: poll, cast_at: 1.day.ago, choice: [low_priority_option.name] }
    let(:old_stance) { create :stance, poll: poll, cast_at: 5.days.ago, choice: [low_priority_option.name] }
    let(:undecided_stance) { create :stance, poll: poll, cast_at: nil}
    let(:high_priority_stance) { create :stance, poll: poll, cast_at: 1.day.ago, choice: [high_priority_option.name] }
    let(:low_priority_stance) { create :stance, poll: poll, cast_at: 1.day.ago, choice: [low_priority_option.name] }
    let(:high_priority_option) { create(:poll_option, poll: poll, priority: 0) }
    let(:low_priority_option) { create(:poll_option, poll: poll, priority: 10) }

    it 'can order by cast_at' do
      sign_in user
      recent_stance; old_stance
      get :index, params: { poll_id: poll.id, order: 'cast_at DESC NULLS LAST'}
      expect(response.status).to eq 200
      json = JSON.parse(response.body)

      expect(json['stances'][0]['id']).to eq recent_stance.id
      expect(json['stances'][1]['id']).to eq old_stance.id
    end

    it 'anonymous does not reveal participant for other peoples votes' do
      my_stance    = create(:stance, participant: user, poll: poll)
      other_stance = create(:stance, poll: poll)
      poll.update(anonymous: true)
      sign_in user
      get :index, params: { poll_id: poll.id }

      json = JSON.parse(response.body)
      stance_ids = json['stances'].map { |s| s['id'] }
      user_ids   = json['stances'].map { |u| u['participant_id'] }

      expect(stance_ids).to include my_stance.id
      expect(json['polls'][0]['my_stance_id']).to eq my_stance.id
      expect(stance_ids).to include other_stance.id
      expect(user_ids).to_not include my_stance.participant_id
      expect(user_ids).to_not include other_stance.participant_id
    end

    it 'anonymous inclues meta for admin users' do
      my_stance    = create(:stance, participant: user, poll: poll)
      other_stance = create(:stance, poll: poll, admin: true)
      poll.update(anonymous: true)
      sign_in user
      get :users, params: { poll_id: poll.id }

      json = JSON.parse(response.body)
      expect(json['meta']['stance_admin_ids']).to include other_stance[:participant_id]
      expect(json['meta']['stance_admin_ids'].length).to eq 1
    end

    it 'does not allow unauthorized users to get stances' do
      get :index, params: { poll_id: poll.id }
      expect(response.status).to eq 403
    end
  end

  describe 'uncast' do
    let(:poll)       { create :poll }
    let(:voter)       { create :user, name: "voter", email: 'voter@example.com'}
    let(:stance_params) {{
      poll_id: poll.id,
      stance_choices_attributes: [{poll_option_id: poll.poll_options.first.id}],
      reason: "the season is the reason"
    }}

    describe "happy case" do
      before do
        sign_in voter
        stances = PollService.invite(poll: poll, actor: poll.author, params: {recipient_emails: [voter.email]})
        stance = stances.first
        StanceService.update(stance: stance, actor: stance.participant, params: stance_params)
      end

      it 'sets cast_at to nil' do
        stance = poll.stances.latest.find_by(participant_id: voter.id)
        put :uncast, params: {id: stance.id}
        expect(response.status).to eq 200
        stance = poll.stances.latest.find_by(participant_id: voter.id)
        expect(stance.cast_at).to eq nil
      end
    end

    describe "another user's vote" do
      before do
        sign_in poll.author
        stances = PollService.invite(poll: poll, actor: poll.author, params: {recipient_emails: [voter.email]})
        stance = stances.first
        StanceService.update(stance: stance, actor: stance.participant, params: stance_params)
      end

      it 'sets cast_at to nil' do
        stance = poll.stances.where(participant_id: voter.id).last
        put :uncast, params: {id: stance.id}
        expect(response.status).to eq 404
        expect(stance.reload.cast_at).to_not eq nil
      end
    end

    describe "poll has closed" do
      before do
        sign_in voter
        stances = PollService.invite(poll: poll, actor: poll.author, params: {recipient_emails: [voter.email]})
        stance = stances.first
        StanceService.update(stance: stance, actor: stance.participant, params: stance_params)
      end

      it 'sets cast_at to nil' do
        poll.update(closed_at: Time.now)
        stance = poll.stances.where(participant_id: voter.id).last
        put :uncast, params: {id: stance.id}
        expect(response.status).to eq 403
      end
    end
  end

  describe 'create' do

    let(:another_user) { create :user, email: 'another_user@example.com', email_verified: false }
    let(:membership) { create :membership, user: build(:user, name: "unverified", email: 'user@example.com', email_verified: false), group: poll.guest_group }
    let(:user)       { create :user, name: "unverified", email: 'user@example.com', email_verified: false }
    let(:poll)       { create :poll }
    let(:poll_option) { create :poll_option, poll: poll }

    it 'returns 403 for logged out users' do
      post :create, params: { stance: stance_params }
      expect(response.status).to eq 403
    end

    describe "non member votes" do
      it "denies access" do
        sign_in create :user
        post :create, params: {stance: stance_params}
        expect(response.status).to eq 403
      end
    end

    describe "verified user votes" do
      it "creates stance and updates name and email" do
        user.update(email_verified: true)
        stance = poll.add_guest! user, poll.author
        sign_in user
        expect { post :update, params: {id: stance.id, stance: stance_params } }.to_not change {ActionMailer::Base.deliveries.count}
        expect(user.stances.latest.count).to eq 1
        expect(response.status).to eq 200
        expect(user.reload.email_verified).to eq true
      end
    end

    it 'includes the participant when current_user' do
      poll.update(anonymous: true)
      user.update(email_verified: true)
      sign_in user
      stance = poll.add_guest! user, poll.author
      post :update, params: { id: stance.id, stance: stance_params }
      json = JSON.parse(response.body)
      expect(response.status).to eq 200
      expect(json['polls'][0]['my_stance_id']).to be json['stances'][0]['id']
      expect(json['stances'][0]['participant_id']).to be nil
      expect(json['events'][0]['actor_id']).to be nil
    end

    describe 'poll.specified_voters_only false' do
      let(:user) { create(:user) }
      before do
        group = create(:group)
        poll.update(group: group, specified_voters_only: false, discussion: create(:discussion, group: group))
        sign_in user
      end

      it 'member of formal group can vote' do
        poll.group.add_member! user
        post :create, params: { stance: stance_params }
        expect(response.status).to eq 200
      end

      it 'guest of discussion can vote' do
        poll.discussion.add_guest! user, poll.author
        post :create, params: { stance: stance_params }
        expect(response.status).to eq 200
      end

      it 'guest of poll can vote' do
        stance = poll.add_guest! user, poll.author
        post :update, params: { id: stance.id, stance: stance_params }
        expect(response.status).to eq 200
      end
    end

    describe 'poll.specified_voters_only true' do
      let(:user) { create(:user) }
      before do
        poll.update(group: group, specified_voters_only: true, discussion: create(:discussion, group: group))
        sign_in user
      end

      it 'member of formal group cannot vote' do
        poll.group.add_member! user
        post :create, params: { stance: stance_params }
        expect(response.status).to eq 403
      end

      it 'guest of discussion cannot vote' do
        poll.discussion.add_guest! user, poll.author
        post :create, params: { stance: stance_params }
        expect(response.status).to eq 403
      end

      it 'guest of poll can vote' do
        stance = poll.add_guest! user, poll.author
        post :update, params: { id: stance.id, stance: stance_params }
        expect(response.status).to eq 200
      end
    end

    describe 'poll.require_all_choices = true' do
      let(:poll) { create :poll_meeting, discussion: discussion }

      before do
        discussion.group.add_member! user
        user.update(email_verified: true)
        sign_in user
      end

      it 'prevents fewer choices than poll options' do
        stance_params = {
          poll_id: poll.id,
          stance_choices_attributes: [],
          reason: "here is my stance"
        }
        post :create, params: { stance: stance_params}
        expect { post :create, params: { stance: stance_params} }.to_not change { Stance.count }
        expect(response.status).to eq 422
      end

      it 'allows the right number of choices' do
        stance_params = {
          poll_id: poll.id,
          stance_choices_attributes: [{poll_option_id: poll.poll_options.first.id , score: 1}],
          reason: "here is my stance"
        }
        expect { post :create, params: { stance: stance_params} }.to change { Stance.count }.by(1)
        expect(response.status).to eq 200
      end
    end
  end

  describe "create as logged in member without token" do
    before { group.add_member! user }

    it 'creates a new stance' do
      sign_in user
      expect { post :create, params: { stance: stance_params } }.to change { Stance.count }.by(1)

      stance = Stance.last
      expect(stance.poll).to eq poll
      expect(stance.poll_options.first).to eq poll_option
      expect(stance.reason).to eq stance_params[:reason]
      expect(stance.latest).to eq true

      expect(response.status).to eq 200
      json = JSON.parse(response.body)
      expect(json['stances'].length).to eq 1
      expect(json['stances'][0]['id']).to eq stance.id
    end

    it 'updates existing stances' do
      sign_in user
      old_stance
      expect { post :update, params: {id: old_stance.id, stance: stance_params } }.to change { Stance.count }.by(0)
      expect(response.status).to eq 200
      expect(old_stance.reload.latest).to eq true
    end

    it 'does not allow non members to create stances' do
      sign_in another_user
      expect { post :create, params: { stance: stance_params } }.to_not change { Stance.count }
      expect(response.status).to eq 403
    end

    it 'validates minimum stance choices' do
      sign_in user
      stance_params[:poll_id] = proposal.id
      stance_params[:stance_choices_attributes] = []
      expect { post :create, params: { stance: stance_params } }.to_not change { Stance.count }
      expect(response.status).to eq 422
    end
  end
end
