require 'rails_helper'
require_relative './helpers'

RSpec.configure do |c|
  c.include TwitchSpec::Helpers
end

module Twitch
  describe User do
    describe "all" do
      before do
        allow(Base).to receive(:get)
          .with("/users", {:params => {:login => "g", :displayname => "g"}})
          .and_return(build_fake_response(true, [fake_user, fake_user]))
      end

      it "dedupes user lists" do
        params = ActiveSupport::HashWithIndifferentAccess.new(text: 'g')
        expect(described_class.all({:where => params}).length).to eq(1)
      end
    end

    describe 'make_params' do
      it 'should match single character logins as a login and displayname' do
        params = ActiveSupport::HashWithIndifferentAccess.new(text: 'g')
        expect(described_class.make_params(params)).to eq(login: 'g', displayname: 'g')
      end

      it 'should match unicode characters as displayname' do
        params = ActiveSupport::HashWithIndifferentAccess.new(text: '表示')
        expect(described_class.make_params(params)).to eq(displayname: '表示')
      end

      it 'should not add display name if nothing is sent' do
        params = ActiveSupport::HashWithIndifferentAccess.new(text: '')
        expect(described_class.make_params(params)).to eq({})
      end
    end

    context '(2FA Facilities)' do
      let(:user) { User.new(id: 12345) }
      let(:twofa_info) do
        {
          user_id: user.id,
          authy_id: "12345",
          last_four_digits: "5555",
          country_code: "1",
          region_code: "US",
          created_at: Time.now.to_i,
          updated_at: Time.now.to_i,
        }
      end
      let(:passport_twofa) do
        twofa_info
          .select {|k, _v| [:authy_id, :created_at, :updated_at].include?(k)}
          .to_json
      end
      let(:twofa_dates) do
        {
          created_at: Time.at(twofa_info[:created_at]).utc,
          updated_at: Time.at(twofa_info[:updated_at]).utc
        }
      end
      let(:data_stub) { true }
      let(:data) do
        lambda {
          stub_request(:get, "#{Settings.passport.endpoint}/users/#{user.id}/2fa/info")
            .to_return(mock_2fa_info_response)
          stub_request(:get, "#{Settings.passport.endpoint}/users/#{user.id}/2fa")
            .to_return(mock_2fa_passport_response)
        }
      end
      let(:timeout) do
        lambda {
          stub_request(:get, "#{Settings.passport.endpoint}/users/#{user.id}/2fa/info")
            .to_timeout
          stub_request(:get, "#{Settings.passport.endpoint}/users/#{user.id}/2fa")
            .to_timeout
        }
      end
      before(:each) do
        if data_stub
          data.call
        else
          timeout.call
        end
      end

      context "twofa_info" do

        shared_examples "authy nil checks" do
          it('returns nil authy_id') { expect(user.authy_id).to eq(nil) }
          it('returns nil authy_last_four_digits') { expect(user.authy_last_four_digits).to eq(nil) }
          it('returns nil authy_country_code') { expect(user.authy_country_code).to eq(nil) }
          it('returns nil authy_region_code') { expect(user.authy_region_code).to eq(nil) }
          it('returns nil authy_created_at') { expect(user.authy_created_at).to eq(nil) }
          it('returns nil authy_updated_at') { expect(user.authy_updated_at).to eq(nil) }
        end

        shared_examples "passport nil checks" do
          it('returns nil authy_last_four_digits') { expect(user.authy_last_four_digits).to eq(nil) }
          it('returns nil authy_country_code') { expect(user.authy_country_code).to eq(nil) }
          it('returns nil authy_region_code') { expect(user.authy_region_code).to eq(nil) }
        end

        context "without 2fa active" do

          let(:mock_2fa_info_response) do
            {
              status: 404,
              body: {
                error: "user does not have 2FA enabled"
              }.to_json
            }
          end
          let(:mock_2fa_passport_response) do
            {
              status: 404,
              body: {
                error: "user does not have 2FA enabled"
              }.to_json
            }
          end

          it('contains a passport service error') { expect(user.twofa_info.errors.full_messages.first).to eq(Passport::Base::ERR_MFA_NOT_ENABLED) }
          include_examples "authy nil checks"
        end

        context "with an authy error" do

          let(:mock_2fa_info_response) do
            {
              status: 424,
              body: {
                error: "error message"
              }.to_json
            }
          end

          context "with passport 2fa disabled" do
            let(:mock_2fa_passport_response) do
              {
                status: 404,
                body: {
                  error: "user does not have 2FA enabled"
                }.to_json
              }
            end

            it('contains a passport service error') do
              expect(user.twofa_info.errors.full_messages.reject {|e| e.respond_to?(:map)})
                .to eq([Passport::Base::ERR_MFA_NOT_ENABLED, Passport::Base::ERR_FROM_AUTHY])
            end
            it('contains the correct passport service error') { expect(user.twofa_info.errors.full_messages.last['error']).to eq("error message") }
            include_examples "authy nil checks"
          end

          context "with passport 2fa enabled" do
            let(:mock_2fa_passport_response) do
              {
                status: 200,
                body: passport_twofa
              }
            end

            it('contains a passport service error') { expect(user.twofa_info.errors.full_messages.first).to eq(Passport::Base::ERR_FROM_AUTHY) }
            it('contains the correct passport service error') { expect(user.twofa_info.errors.full_messages.last['error']).to eq("error message") }
            include_examples "passport nil checks"
            it("returns the user's authy_id") { expect(user.authy_id).to eq(twofa_info[:authy_id]) }
            it("returns the user's authy_created_at") { expect(user.authy_created_at).to eq(twofa_dates[:created_at]) }
            it("returns the user's authy_updated_at") { expect(user.authy_updated_at).to eq(twofa_dates[:updated_at]) }

          end
        end

        context "with a passport error" do

          let(:mock_2fa_info_response) do
            {
              status: 500,
              body: {
                error: "error message"
              }.to_json
            }
          end
          let(:mock_2fa_passport_response) do
            {
              status: 500,
              body: {
                error: "error message"
              }.to_json
            }
          end

          it('contains a passport service error') { expect(user.twofa_info.errors.full_messages.first).to eq(Passport::Base::ERR_FROM_PASSPORT) }
          it('contains the correct passport service error') { expect(user.twofa_info.errors.full_messages.last['error']).to eq("error message") }
          include_examples "authy nil checks"
        end

        context "with an unknown error" do

          let(:mock_2fa_info_response) do
            {
              status: 503,
              body: {
                error: "error message"
              }.to_json
            }
          end

          let(:mock_2fa_passport_response) do
            {
              status: 503,
              body: {
                error: "error message"
              }.to_json
            }
          end

          it('contains an unknown passport error') { expect(user.twofa_info.errors.full_messages.first).to eq(Passport::Base::ERR_UNKNOWN) }
          it('contains an exception as the informing argument') { expect(user.twofa_info.errors.full_messages.last).to be_kind_of(String) }
          include_examples "authy nil checks"
        end

        context "with a timeout" do
          let(:data_stub) { false }

          it('contains a timeout error') { expect(user.twofa_info.errors.full_messages.first).to eq(Passport::Base::ERR_TIMEOUT) }
          it('contains only a timeout error') { expect(user.twofa_info.errors.full_messages.last).to eq(Passport::Base::ERR_TIMEOUT) }
          include_examples "authy nil checks"
        end

        context "with 2fa active" do
          let(:mock_2fa_info_response) do
            {
              status: 200,
              body: twofa_info.to_json
            }
          end

          let(:mock_2fa_passport_response) do
            {
              status: 200,
              body: passport_twofa
            }
          end

          it("returns the user's full twofa_info object") { expect(user.twofa_info).to have_attributes(twofa_info) }
          it("returns the user's authy_id") { expect(user.authy_id).to eq(twofa_info[:authy_id]) }
          it("returns the user's authy_last_four_digits") { expect(user.authy_last_four_digits).to eq(twofa_info[:last_four_digits]) }
          it("returns the user's authy_country_code") { expect(user.authy_country_code).to eq(twofa_info[:country_code]) }
          it("returns the user's authy_region_code") { expect(user.authy_region_code).to eq(twofa_info[:region_code]) }
          it("returns the user's authy_created_at") { expect(user.authy_created_at).to eq(twofa_dates[:created_at]) }
          it("returns the user's authy_updated_at") { expect(user.authy_updated_at).to eq(twofa_dates[:updated_at]) }
        end
      end

      context 'two_factor_enabled?' do

        context "without 2fa active" do
          let(:mock_2fa_info_response) do
            {
              status: 404,
              body: nil
            }
          end

          context "with passport 2fa disabled" do
            let(:mock_2fa_passport_response) do
              {
                status: 404,
                body: {
                  error: "user does not have 2FA enabled"
                }.to_json
              }
            end

            it 'returns false' do
              expect(user.two_factor_enabled?).to be false
            end
          end

          context "with passport 2fa enabled" do
            let(:mock_2fa_passport_response) do
              {
                status: 200,
                body: passport_twofa
              }
            end

            it 'returns true' do
              expect(user.two_factor_enabled?).to be true
            end
          end

        end

        context "with 2fa active" do
          let(:mock_2fa_info_response) do
            {
              status: 200,
              body: twofa_info.to_json
            }
          end

          let(:mock_2fa_passport_response) do
            {
              status: 200,
              body: passport_twofa
            }
          end

          it 'returns true' do
            expect(user.two_factor_enabled?).to be true
          end
        end
      end
    end

    describe 'validated_non_partner?' do
      let(:user) { User.new(id: 12345) }

      context "is not validated non partner" do
        it 'returns false' do
          expect(Ripley::ValidatedNonPartner).to receive(:find).with(user.id.to_s).and_return(false)
          expect(user.validated_non_partner?).to be false
        end
      end

      context "with 2fa active" do
        it 'returns true' do
          expect(Ripley::ValidatedNonPartner).to receive(:find).with(user.id.to_s).and_return(true)
          expect(user.validated_non_partner?).to be true
        end
      end
    end

    describe 'vods_count' do
      let(:user) { User.new(id: 12345) }

      before(:each) do
        allow(user).to receive(:user_vods).and_return(double("user_vods"))
      end

      context "with a non-zero total count" do
        it "returns the non-zero count" do
          allow(user.user_vods).to receive(:total_count).and_return(27)
          expect(user.vods_count).to eq 27
        end
      end
      context "with a zero total count" do
        it "returns the zero count" do
          allow(user.user_vods).to receive(:total_count).and_return(0)
          expect(user.vods_count).to eq 0
        end
      end
      context "with a failed total count" do
        it "returns 'Unknown'" do
          allow(user.user_vods).to receive(:total_count).and_return(nil)
          expect(user.vods_count).to eq "Unknown"
        end
      end
    end

    describe 'undelete' do
      let(:user) { User.new(id: 12345, login: 'test_user') }
      let(:ldap) {'admin_ldap'}

      before(:each) do
        allow(user).to receive(:fetch_id).and_return(user.id)
      end

      context "successfully" do

        let(:audit) { SupportTools::AegisGateway.new(user_id: user.id, audit_id: "D3F36E97-3074-4E11-8CD6-138859ABF279")}
        let(:audit_params) do
          {
            action: :appeal_successful,
            login: user.login,
            user_id: user.id,
            ldap: ldap
          }
        end

        before(:each) do
          allow(SupportTools::AegisGateway)
            .to receive(:create_force_username_reset_audit)
            .and_return(audit)

          expect(SupportTools::AegisGateway)
            .to receive(:create_force_username_reset_audit)
            .with(audit_params)
            .and_return(audit)
        end

        it "files and audit and returns the response" do

          request =
            stub_request(:patch, "#{Settings.user.endpoint}/users/#{user.id}/undelete?admin=#{ldap}")
            .to_return({status: 204})

          response = user.undelete(ldap)

          expect(response.success?).to eq(true)
          expect(response.status).to eq(204)

          expect(request).to have_been_requested

        end
      end

      context "failure" do
        it "doesn't file the audit and returns the response" do

          request =
            stub_request(:patch, "#{Settings.user.endpoint}/users/#{user.id}/undelete?admin=#{ldap}")
            .to_return({status: 500})

          response = user.undelete(ldap)

          expect(response.success?).to eq(false)
          expect(response.status).to eq(500)

          expect(request).to have_been_requested
        end

        it "fails without an Admin" do
          expect {user.undelete}.to raise_error(ArgumentError)
        end
      end
    end

    describe 'rename' do
      let(:user) { User.new(id: 12345, login: 'test_user') }
      let(:ldap) {'admin_ldap'}
      let(:audits) do
        {
          appeal_audit_id: "D3C1570A-5565-4167-BCF1-7960660889FB",
          username_change_audit_id: "C55B1622-95B3-4963-8227-A82E22C4D11D"
        }
      end

      before(:each) do
        allow(user).to receive(:fetch_id).and_return(user.id)
        allow(user).to receive(:safety_rename_audit).and_return(audits)
        allow(user).to receive(:audit_rename).and_return(
          Faraday::Response.new.tap do |response|
            allow(response).to receive(:success?).and_return(true)
            allow(response).to receive(:status).and_return(204)
          end
        )
      end

      let(:options) do
        ActiveSupport::HashWithIndifferentAccess.new(
          admin_override: true,
          new_login: 'test_user_2',
          rename_reason: 'test run',
          ldap_login: ldap
        )
      end

      context "without username reset" do

        let(:params) do
          {
            new_login: options[:new_login],
            skip_login_cooldown: true,
            skip_user_notification: true,
            override_login_block: true,
            override_login_length: true,
            skip_name_validation: true,
          }
        end

        it "renames the user" do

          request =
            stub_request(:patch, "#{Settings.user.endpoint}/users/#{user.id}")
            .to_return({status: 204})

          result = user.rename(options)

          expect(result).to eq(true)

          expect(request).to have_been_requested
          expect(user).to have_received(:safety_rename_audit)
          expect(user).to have_received(:audit_rename)

        end

      end

      context "via username reset" do

        before(:each) do
          allow(user).to receive(:flagged_for_rename?).and_return(true)
          allow(user).to receive(:username_flagged_on).and_return(Time.now.utc)
        end

        let(:params) do
          {
            id: user.id,
            admin: options[:ldap_login],
            login: options[:new_login],
            skip_user_notification: true,
            override_login_block: true,
            override_login_length: true,
            skip_name_validation: true,
          }
        end

        it "resets the user" do
          request =
            stub_request(:patch, "#{Settings.user.endpoint}/username_reset")
            .to_return({status: 204})

          result = user.rename(options)

          expect(result).to eq(true)

          expect(request).to have_been_requested
          expect(user).to have_received(:safety_rename_audit)
          expect(user).to have_received(:audit_rename)
        end

      end
    end

    describe 'audit_rename' do
      let(:user) { User.new(id: 12345, login: 'test_user') }
      let(:ldap) {'admin_ldap'}
      let(:options) do
        {
          admin_override: true,
          new_login: 'test_user_2',
          old_login: 'test_user',
          rename_reason: 'test run',
          ldap_login: ldap,
          appeal_audit_id: "D3C1570A-5565-4167-BCF1-7960660889FB",
          username_change_audit_id: "C55B1622-95B3-4963-8227-A82E22C4D11D"
        }
      end
      let(:request) {}

      before(:each) do
        allow(user).to receive(:fetch_id).and_return(user.id)
      end

      context 'creates a well formed audit' do

        it 'includes the correct info' do
          skip 'test incomplete'

          request =
            stub_request(:post, "#{Settings.history.endpoint}/v1/authed/audits")
            .with(body: ::WebMock.hash_including(options))
            .to_return({status: 204})

          expect(user).to receive(:audit_rename)
            .with(options)

          response = user.audit_rename(options)

          expect(response).to eq(true)

          expect(request).to have_been_requested

        end

        it 'includes rename flags when they exist' do
          skip 'test incomplete'
          raise
        end

        it 'includes audit flags when they exist' do
          skip 'test incomplete'
          raise
        end

        it 'does not include flags if they do not exist' do
          skip 'test incomplete'
          raise
        end
      end

      it 'submits an audit to history' do
        skip 'test incomplete'

        expect(request).to have_been_requested
        raise
      end

    end

    describe 'safety_rename_audit' do
      let(:user) { User.new(id: 12345) }
      let(:ldap) {'admin_ldap'}

      let(:appeal_audit_params) do
        {
          action: :appeal_successful,
          login: 'test_user',
          user_id: user.id,
          ldap: ldap,
        }
      end

      let(:username_change_audit_params) do
        {
          action: :username_changed,
          login: 'test_user_2',
          user_id: user.id,
          ldap: ldap,
        }
      end

      let(:options) do
        {
          old_login: appeal_audit_params[:login],
          new_login: username_change_audit_params[:login],
          user_id: user.id,
          admin_ldap: ldap
        }
      end

      let(:results) do
        {
          appeal_audit_id: "D3C1570A-5565-4167-BCF1-7960660889FB",
          username_change_audit_id: "C55B1622-95B3-4963-8227-A82E22C4D11D"
        }
      end

      let(:appeal_audit) { SupportTools::AegisGateway.new(user_id: user.id, audit_id: "D3C1570A-5565-4167-BCF1-7960660889FB")}
      let(:username_change_audit) { SupportTools::AegisGateway.new(user_id: user.id, audit_id: "C55B1622-95B3-4963-8227-A82E22C4D11D")}

      before(:each) do

        expect(SupportTools::AegisGateway)
          .to receive(:create_force_username_reset_audit)
          .with(appeal_audit_params)
          .and_return(appeal_audit)

        expect(SupportTools::AegisGateway)
          .to receive(:create_force_username_reset_audit)
          .with(username_change_audit_params)
          .and_return(username_change_audit)
      end

      it 'submits and returns the audit IDs' do
        returned = user.safety_rename_audit(**options)

        expect(returned).to eq(results)
      end

    end

  end
end
