require 'rails_helper'

describe Twitch::TeamsController do
  include_context 'mocked permissions'

  let(:user) { Twitch::User.new(id: 123, login: 'kappa') }

  let(:team) do
    Twitch::Team.new(
      id: '1',
      name: 'staff',
      display_name: 'Staff',
      description_markdown: '# Markdown',
      user_id: user.id
    )
  end

  describe '#index' do
    let(:permission) { 'users' }

    before do
      allow(Twitch::Team).to receive(:page).with(1).and_return([team])
      allow(Twitch::User).to receive(:all).with(where: { id: [user.id] }).and_return([])

      get :index
    end

    it_behaves_like 'it returns a success response'
  end

  describe '#show' do
    let(:permission) { 'users' }

    context 'when the team is not found' do
      before do
        allow(Twitch::Team).to receive(:find_by_name).with(team.name).and_return(nil)

        get :show, params: { name: team.name }
      end

      it_behaves_like 'it redirects the user and displays an error'
    end

    context 'when the team is found' do
      before do
        allow(Twitch::Team).to receive(:find_by_name).with(team.name).and_return(team)
        allow(Twitch::User).to receive(:find).with(user.id).and_return(nil)

        get :show, params: { name: team.name }
      end

      it_behaves_like 'it returns a success response'
    end
  end

  describe '#new' do
    let(:permission) { 'manage_twitch_teams' }

    context 'without authorization' do
      include_context 'an unauthorized user'

      before { get :new }

      it_behaves_like 'it redirects the user and displays an error'
    end

    context 'with authorization' do
      include_context 'an authorized user'

      before { get :new }

      it_behaves_like 'it returns a success response'
    end
  end

  describe '#create' do
    let(:permission) { 'manage_twitch_teams' }

    context 'without authorization' do
      include_context 'an unauthorized user'

      before { post :create }

      it_behaves_like 'it redirects the user and displays an error'
    end

    context 'with authorization' do
      include_context 'an authorized user'

      let(:params) do
        {
          twitch_team: {
            name: 'new_team',
            display_name: 'New Team!',
            description_markdown: 'We are **better** than Twitch Staff'
          },
          user_login: user.login
        }
      end

      context 'when the to-be team owner is not found by login' do
        before do
          allow(Twitch::User).to receive(:find_by_login).with(user.login).and_return(nil)

          post :create, params: params
        end

        it 'displays an error' do
          expect(response.code).to eq('200')
          expect(flash[:error]).to include('not found')
        end
      end

      context 'when the to-be team owner is suspended' do
        before do
          user.terms_of_service_violation = true
          allow(Twitch::User).to receive(:find_by_login).with(user.login).and_return(user)

          post :create, params: params
        end

        it 'displays an error' do
          expect(response.code).to eq('200')
          expect(flash[:error]).to include('not allowed')
        end
      end

      context 'when saving the team fails' do
        before do
          allow(Twitch::User).to receive(:find_by_login).with(user.login).and_return(user)

          allow_any_instance_of(Faraday::Connection).to receive(:run_request).and_return(
            double(
              success?: false,
              status: 500,
              body: {
                'message' => 'error!'
              }
            )
          )

          post :create, params: params
        end

        it 'displays an error' do
          expect(response.code).to eq('200')
          expect(flash[:error]).to include('error!')
        end
      end

      context 'when saving the team succeeds' do
        include_context 'it creates any tracking call'

        before do
          allow(Twitch::User).to receive(:find_by_login).with(user.login).and_return(user)

          expect_any_instance_of(Faraday::Connection).to receive(:run_request).and_return(
            double(success?: true)
          )

          post :create, params: params
        end

        it_behaves_like 'it redirects the user and displays a success message'
      end
    end
  end

  describe '#edit' do
    let(:permission) { 'manage_twitch_teams' }

    context 'without authorization' do
      include_context 'an unauthorized user'

      before { get :edit, params: { name: team.name } }

      it_behaves_like 'it redirects the user and displays an error'
    end

    context 'with authorization' do
      include_context 'an authorized user'

      context 'when the team is not found' do
        before do
          allow(Twitch::Team).to receive(:find_by_name).with(team.name).and_return(nil)

          get :edit, params: { name: team.name }
        end

        it_behaves_like 'it redirects the user and displays an error'
      end

      context 'when the team is found' do
        before do
          allow(Twitch::Team).to receive(:find_by_name).with(team.name).and_return(team)
          allow(Twitch::User).to receive(:find).with(user.id).and_return(user)

          get :edit, params: { name: team.name }
        end

        it_behaves_like 'it returns a success response'
      end
    end
  end

  describe '#update' do
    let(:permission) { 'manage_twitch_teams' }

    context 'without authorization' do
      include_context 'an unauthorized user'

      before { patch :update, params: { name: team.name } }

      it_behaves_like 'it redirects the user and displays an error'
    end

    context 'with authorization' do
      include_context 'an authorized user'

      let(:params) do
        {
          name: team.name,
          twitch_team: {
            display_name: team.display_name,
            description_markdown: team.description_markdown
          }
        }
      end

      context 'when the team is not found' do
        before do
          allow(Twitch::Team).to receive(:find_by_name).with(team.name).and_return(nil)

          patch :update, params: params
        end

        it_behaves_like 'it redirects the user and displays an error'
      end

      context 'when the team is found' do
        before do
          allow(Twitch::Team).to receive(:find_by_name).with(team.name).and_return(team)
          allow(Twitch::User).to receive(:find).with(user.id).and_return(user)
        end

        context 'when the team ownership is to be reassigned' do
          let(:new_owner) { Twitch::User.new(id: 999, login: 'twitchoffice') }

          before do
            params[:user_login] = new_owner.login
          end

          context 'when the to-be team owner is not found by login' do
            before do
              allow(Twitch::User).to receive(:find_by_login).with(new_owner.login).and_return(nil)

              patch :update, params: params
            end

            it 'displays an error' do
              expect(response.code).to eq('200')
              expect(flash[:error]).to include('not found')
            end
          end

          context 'when the to-be team owner is suspended' do
            before do
              new_owner.terms_of_service_violation = true
              allow(Twitch::User).to receive(:find_by_login).with(new_owner.login).and_return(new_owner)

              patch :update, params: params
            end

            it 'displays an error' do
              expect(response.code).to eq('200')
              expect(flash[:error]).to include('not allowed')
            end
          end

          context 'when the to-be team owner is in good-standing' do
            before do
              allow(Twitch::User).to receive(:find_by_login).with(new_owner.login).and_return(new_owner)
            end

            context 'when updating the team fails' do
              before do
                allow_any_instance_of(Faraday::Connection).to receive(:run_request).and_return(
                  double(
                    success?: false,
                    status: 500,
                    body: {
                      'message' => '💩'
                    }
                  )
                )

                patch :update, params: params
              end

              it 'displays an error' do
                expect(response.code).to eq('200')
                expect(flash[:error]).to include('💩')
              end
            end

            context 'when updating the team succeeds' do
              before do
                # Use a proc because chaining a block is ugly.
                argument_matcher = proc do |_, method, _, body|
                  expect(method).to eq(:patch)
                  expect(body).to include("\"user_id\":\"#{new_owner.id}\"")
                end

                allow_any_instance_of(Faraday::Connection).to receive(:run_request) { argument_matcher }.and_return(
                  double(success?: true)
                )

                patch :update, params: params
              end

              it_behaves_like 'it redirects the user and displays a success message'
            end
          end
        end

        context 'when the team ownership is to remain the same' do
          before do
            params[:user_login] = user.login
          end

          context 'when updating the team succeeds' do
            before do
              allow_any_instance_of(Faraday::Connection).to receive(:run_request).and_return(
                double(success?: true)
              )

              patch :update, params: params
            end

            it_behaves_like 'it redirects the user and displays a success message'
          end
        end
      end
    end
  end

  describe '#delete' do
    let(:permission) { 'manage_twitch_teams' }

    context 'without authorization' do
      include_context 'an unauthorized user'

      before { get :delete, params: { name: team.name } }

      it_behaves_like 'it redirects the user and displays an error'
    end

    context 'with authorization' do
      include_context 'an authorized user'

      context 'when the team is not found' do
        before do
          allow(Twitch::Team).to receive(:find_by_name).with(team.name).and_return(nil)

          get :delete, params: { name: team.name }
        end

        it_behaves_like 'it redirects the user and displays an error'
      end

      context 'when the team is found' do
        before do
          allow(Twitch::Team).to receive(:find_by_name).with(team.name).and_return(team)

          get :delete, params: { name: team.name }
        end

        it_behaves_like 'it returns a success response'
      end
    end
  end

  describe '#destroy' do
    let(:permission) { 'manage_twitch_teams' }

    context 'without authorization' do
      include_context 'an unauthorized user'

      before { delete :destroy, params: { name: team.name } }

      it_behaves_like 'it redirects the user and displays an error'
    end

    context 'with authorization' do
      include_context 'an authorized user'

      context 'when the team is not found' do
        before do
          allow(Twitch::Team).to receive(:find_by_name).with(team.name).and_return(nil)

          delete :destroy, params: { name: team.name }
        end

        it_behaves_like 'it redirects the user and displays an error'
      end

      context 'when the team is found' do
        before do
          allow(Twitch::Team).to receive(:find_by_name).with(team.name).and_return(team)
        end

        context 'when destroying the team fails' do
          before do
            allow_any_instance_of(Faraday::Connection).to receive(:run_request).and_return(
              double(
                success?: false,
                status: 500
              )
            )
          end

          it 'raises an error' do
            expect { delete :destroy, params: { name: team.name } }.to raise_error(RuntimeError)
          end
        end

        context 'when destroying the team succeeds' do
          before do
            allow_any_instance_of(Faraday::Connection)
              .to receive(:run_request) { |_, method| expect(method).to eq(:delete) }
              .and_return(
                double(success?: true)
              )

            delete :destroy, params: { name: team.name }
          end

          it 'redirects the user and displays a notice' do
            expect(response.code).to eq('302')
            expect(flash[:notice]).to be_present
          end
        end
      end
    end
  end

  describe '#search' do
    context 'without a search query' do
      before { get :search }

      it 'redirects the user' do
        expect(response.code).to eq('302')
      end
    end

    context 'with a search query' do
      include_context 'it creates any tracking call'

      before do
        allow(Twitch::User).to receive(:all).with(where: { id: [user.id] }).and_return([])

        expect(Twitch::Team).to receive(:search).with(name: 'taf', page: 1).and_return([team])

        get :search, params: { name: 'taf' }
      end

      it_behaves_like 'it returns a success response'
    end
  end
end
