require "rails_helper"

RSpec.describe ProjectMembership, type: :model do
  describe "associations" do
    it { is_expected.to belong_to(:project) }
    it { is_expected.to belong_to(:user) }
  end

  describe "validations" do
    describe "#user_id" do
      subject do
        account = build(:account)
        build(:project_membership, 
                      user: build(:user, primary_account: account), 
                      project: build(:project, account: account)) 
      end
      it { is_expected.to validate_uniqueness_of(:user_id).scoped_to(:project_id).with_message("already belongs to this Project") }
    end

    it "does not allow account managers" do
      account = build(:account)
      account_manager = create(:user, :account_manager, accounts: [account])
      project = build(:project, account: account)

      project_membership = ProjectMembership.new(user: account_manager, project: project)

      expect(project_membership).to be_invalid
      expect(project_membership.errors[:user]).not_to be_empty
      expect(project_membership.errors.full_messages.first).to eq "User is already an account manager"
    end
  end

  describe "#save_and_update_account_membership" do
    let(:project) { create(:project) }

    context "when no user exists for the given email" do
      let(:membership) { ProjectMembership.new(project: project, user_email: "new.user@test.com") }

      it "returns true" do
        expect(membership.save_and_update_account_membership).to be_truthy
      end

      it "creates the user" do
        expect {
          membership.save_and_update_account_membership
        }.to change(User, :count).by(1)

        user = User.last

        expect(user.email).to eq "new.user@test.com"
      end

      it "creates account membership" do
        expect {
          membership.save_and_update_account_membership
        }.to change(AccountAuth, :count).by(1)

        account_auth = AccountAuth.last
        user = User.last

        expect(account_auth.account).to eq project.account
        expect(account_auth.user).to eq user
      end

      it "creates project membership" do
        expect {
          membership.save_and_update_account_membership
        }.to change(ProjectMembership, :count).by(1)

        user = User.last

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

    context "when user exists but has no account access" do
      let!(:user) { create(:user, email: "existing.user@test.com") }
      let(:membership) { ProjectMembership.new(project: project, user_email: "existing.user@test.com") }

      it "returns true" do
        expect(membership.save_and_update_account_membership).to be_truthy
      end

      it "creates account membership" do
        expect {
          membership.save_and_update_account_membership
        }.to change(AccountAuth, :count).by(1)

        account_auth = AccountAuth.last

        expect(account_auth.account).to eq project.account
        expect(account_auth.user).to eq user
      end

      it "creates project membership" do
        expect {
          membership.save_and_update_account_membership
        }.to change(ProjectMembership, :count).by(1)

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

    context "when user exists and has account access" do
      let!(:user) { create(:user, email: "existing.user@test.com", accounts: [project.account]) }
      let(:membership) { ProjectMembership.new(project: project, user_email: "existing.user@test.com") }

      it "returns true" do
        expect(membership.save_and_update_account_membership).to be_truthy
      end

      it "creates project membership" do
        expect {
          membership.save_and_update_account_membership
        }.to change(ProjectMembership, :count).by(1)

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

    shared_examples "creates no records and returns false" do
      it "returns false" do
        expect(membership.save_and_update_account_membership).to be_falsey
      end

      it "does not create new records" do
        expect { membership.save_and_update_account_membership }.
          to change(ProjectMembership, :count).by(0).
          and change(AccountAuth, :count).by(0).
          and change(User, :count).by(0)
      end
    end

    context "when user cannot be saved" do
      let(:membership) { ProjectMembership.new(project: project, user_email: "new.user@test.com") }

      before do
        allow_any_instance_of(Oath::Services::SignUp).to receive(:perform).and_return(build(:user))
        allow_any_instance_of(User).to receive(:save).and_return(false)
      end

      include_examples "creates no records and returns false"
    end

    context "when account auth cannot be saved" do
      let(:membership) { ProjectMembership.new(project: project, user_email: "new.user@test.com") }

      before do
        allow_any_instance_of(AccountAuth).to receive(:save).and_return(false)
      end

      include_examples "creates no records and returns false"
    end

    context "when project membership cannot be saved" do
      let(:membership) { ProjectMembership.new(project: project, user_email: "new.user@test.com") }

      before do
        allow_any_instance_of(ProjectMembership).to receive(:save).and_return(false)
      end

      include_examples "creates no records and returns false"
    end
  end
end
