require "rails_helper"

RSpec.describe ProjectMembershipsController, type: :controller do
  render_views

  let(:current_user) { create(:user, :manager) }
  let!(:project) { create(:project, members: current_user, account: current_user.primary_account) }

  before do
    sign_in current_user
  end

  describe "#index" do
    it "responds successfully" do
      get :index, params: { project_id: project }

      expect(response).to be_successful
    end

    it "renders content" do
      project = create(:project, name: "My Project", account: current_user.primary_account, members: [
        current_user,
        create(:user, :manager, email: "jane.doe@test.com", accounts: current_user.accounts),
        create(:user, :associate, email: "john.doe@test.com", accounts: current_user.accounts),
      ])
      
      get :index, params: { project_id: project }

      expect(response.body).to have_content "Team"
      expect(response.body).to have_content "3 Team Members"
      expect(response.body).to have_content current_user.email
      expect(response.body).to have_content "jane.doe@test.com"
      expect(response.body).to have_content "Project Manager"
      expect(response.body).to have_content "john.doe@test.com"
      expect(response.body).to have_content "Associate"
    end

    it "includes account managers in the team roster" do
      project = create(:project, name: "My Project", account: current_user.primary_account, members: current_user)
      account_manager = create(:user, :account_manager, accounts: current_user.accounts) 
      
      get :index, params: { project_id: project }

      expect(response.body).to have_content "Team"
      expect(response.body).to have_content "2 Team Members"
      expect(response.body).to have_content current_user.email
      expect(response.body).to have_content account_manager.email
    end

    context "for an associate" do
      let(:current_user) { create(:user, :associate) }

      it "does not render invite form" do
        get :index, params: { project_id: project }

        expect(response.body).not_to have_content "Invite New Member"
        expect(response.body).not_to have_field "project_membership[user_email]" 
        expect(response.body).not_to have_button "Send Invite" 
      end

      it "does not render remove links for any users" do
        project.users << create(:user, :associate, email: "another.user@test.com", primary_account: current_user.primary_account)

        get :index, params: { project_id: project }

        expect(response.body).to have_content("another.user@test.com")
        expect(response.body).not_to have_link("Remove")
      end
    end

    context "for a manager" do
      let(:current_user) { create(:user, :manager) }

      it "renders invite form" do
        get :index, params: { project_id: project }

        expect(response.body).to have_content "Invite New Member"
        expect(response.body).to have_field "project_membership[user_email]" 
        expect(response.body).to have_button "Send Invite" 
      end

      it "renders a remove link for other users" do
        my_project_membership = project.project_memberships.first
        their_project_membership = create(:project_membership, 
                                          project: project, 
                                          user: create(:user, :associate, 
                                                       email: "another.user@test.com", 
                                                       primary_account: project.account))

        get :index, params: { project_id: project }

        expect(response.body).to have_content("another.user@test.com")
        expect(response.body).to have_link("Remove", href: project_membership_path(their_project_membership))
        expect(response.body).not_to have_link("Remove", href: project_membership_path(my_project_membership))
      end
    end
  end

  describe "#create" do
    let!(:user) { create(:user, email: "user@test.com") }

    it "responds with redirect" do
      post :create, params: { project_id: project, project_membership: { user_email: "user@test.com" } }

      expect(response).to be_redirect
      expect(response).to redirect_to [project, :project_memberships]
      expect(flash.notice).to be_present
    end

    it "creates a new membership for the invited user" do
      expect {
        post :create, params: { project_id: project, project_membership: { user_email: "user@test.com" } }
      }.to change(ProjectMembership, :count).by(1)

      membership = ProjectMembership.last

      expect(membership.project).to eq(project)
      expect(membership.user).to eq(user)
    end

    it "sends an email notification" do
      post :create, params: { project_id: project, project_membership: { user_email: "user@test.com" } }

      assert_enqueued_email_with UserMailer, :project_invitation, args: [user, project, user_is_new: false]
    end

    context "when invitation cannot be saved" do
      before do
        allow_any_instance_of(ProjectMembership).to receive(:save_and_update_account_membership).and_return(false)
      end

      it "responds with redirect" do
        post :create, params: { project_id: project, project_membership: { user_email: "user@test.com" } }

        expect(response).to be_redirect
        expect(response).to redirect_to [project, :project_memberships]
        expect(flash.alert).to be_present
      end

      it "creates a new membership for the invited user" do
        expect {
          post :create, params: { project_id: project, project_membership: { user_email: "user@test.com" } }
        }.not_to change(ProjectMembership, :count)
      end
    end

    context "when invited user does not exist yet" do
      it "creates a new user record" do
        expect {
          post :create, params: { project_id: project, project_membership: { user_email: "new.user@test.com" } }
        }.to change(User, :count)

        project_membership = ProjectMembership.last
        user = User.last

        expect(project_membership.project).to eq(project)
        expect(project_membership.user).to eq(user)
        expect(user.email).to eq("new.user@test.com")
        expect(user.accounts).to include(current_user.primary_account)
        expect(user.associate?(current_user.primary_account)).to be_truthy
      end

      it "sends an email to the new user" do
        post :create, params: { project_id: project, project_membership: { user_email: "new.user@test.com" } }

        assert_enqueued_email_with UserMailer, :project_invitation, args: [User.last, project, user_is_new: true]
      end
    end

    context "when invited user does not have access to the account yet" do
      let!(:existing_user) { create(:user, email: "existing.user@test.com") }

      it "creates a new account membership record" do
        expect {
          post :create, params: { project_id: project, project_membership: { user_email: "existing.user@test.com" } }
        }.to change(AccountAuth, :count)

        account_auth = AccountAuth.last
        project_membership = ProjectMembership.last

        expect(account_auth.user).to eq existing_user
        expect(account_auth.account).to eq project.account
        expect(account_auth.role).to eq "associate"
        expect(project_membership.user).to eq existing_user
        expect(project_membership.project).to eq project
      end

      it "sends an email to the new user" do
        post :create, params: { project_id: project, project_membership: { user_email: "new.user@test.com" } }

        assert_enqueued_email_with UserMailer, :project_invitation, args: [User.last, project, user_is_new: true]
      end
    end

    context "for an associate" do
      let(:current_user) { create(:user, :associate) }

      it "raises exception" do
        expect {
          post :create, params: { project_id: project, project_membership: { user_email: "new.user@test.com" } }
        }.to raise_error(Pundit::NotAuthorizedError)
      end
    end
  end

  describe "#destroy" do
    let(:project_membership) { current_user.project_memberships.first }

    it "responds with redirect" do
      delete :destroy, params: { id: project_membership }

      expect(response).to be_redirect
      expect(response).to redirect_to([project, :project_memberships]) 
      expect(flash.alert).to be_present
    end

    it "destroys the project membership record" do
      expect {
        delete :destroy, params: { id: project_membership }
      }.to change(ProjectMembership, :count).by(-1)
    end
  end
end
