RSpec.describe Play, type: :model do
  before(:each) do
    @game = setup_game
    @play = @game.controlling_player.plays.new(game_id: @game.id)
  end

  subject { @play }

  it { should respond_to(:game_id) }
  it { should respond_to(:game) }
  it { should respond_to(:player_id) }
  it { should respond_to(:player) }
  it { should respond_to(:player_cards) }
  it { should respond_to(:hand) }
  it { should respond_to(:hand_type) }
  it { should respond_to(:to_s) }

  it '#game_id returns a game id' do
    expect(@play.game).to eq Game.find(@play.game_id)
  end

  it '#player_id return a player id' do
    expect(@play.player).to eq Player.find(@play.player_id)

  end
  it '#player_cards returns a PlayerCard collection' do
    expect(@play.player_cards.class).to eq(PlayerCard::ActiveRecord_Associations_CollectionProxy)
  end

  it '#hand returns a PlayerCard collection' do
    expect(@play.hand.class).to eq(PlayerCard::ActiveRecord_Associations_CollectionProxy)
  end

  it '#hand_type returns a string' do
    @play.player_cards << create_hand(1, 'single')
    expect(@play.hand_type).to eq 'single'
  end

  it '#to_s returns a string of the hand' do
    @play.player_cards << create_hand(1, 'single')
    expect(@play.to_s).to eq '3 Spade'
  end

  describe 'validations' do
    describe 'first_play' do
      it 'valid if it contains the lowest card' do
        # expect(@game.plays.count).to eq(1)
        # Active player is the lowest card holder
        expect(@game.controlling_player).to eq(@game.lowest_card.player)
        # Play the lowest card
        @cards_to_play = @game.controlling_player.player_cards.order(:value).first
        @play.player_cards << @cards_to_play
        #@play.save
        expect(@play.player_cards.include? @game.lowest_card).to eq true
        expect(subject.valid?).to eq(true)
      end
    end
    describe 'wins?' do
      context 'it does not beat the hand_to_beat' do
        it 'single' do
          # TODO: Refactor "lowest card first" to FactoryGirl.create :game, hand_to_beat: @cards
          # First hand must contain lowest card
          @cards_to_play = @play.player.player_cards.order(:value).first
          @play.player_cards = [@cards_to_play]
          @play.save
          @game.reload

          # Play a high single card
          @play = @game.controlling_player.plays.new(game_id: @game.id)
          @cards_to_play = @play.player.player_cards.order(:value).last
          @play.player_cards = [@cards_to_play]
          @play.save
          @game.reload

          # Try to play a losing card
          @losing_play = @game.controlling_player.plays.new(game_id: @game.id)
          @losing_play.player_cards << @losing_play.player.player_cards.order(:value).first
          expect(@losing_play.beats? @play).to eq(false)
          expect(@losing_play.hand_valid?).to eq(false)
          expect(@losing_play.save).to eq(false)
          expect(@losing_play.errors[:player_cards].include?('invalid hand')).to eq(true)
          expect(@game.play_to_beat.play).to eq(@play)
          expect(@game.player_to_beat).to eq(@play.player)
        end

        it 'double' do
          # First hand must contain low card
          @cards_to_play = @play.player.player_cards.order(:value).first
          @play.player_cards = [@cards_to_play]
          @play.save
          @game.reload

          @deck = Deck.new
          @cards_to_beat = [
            @spade5 = PlayerCard.new(@deck.cards[8].instance_values),
            @club5  = PlayerCard.new(@deck.cards[9].instance_values)
          ]

          @play_to_beat = Play.new
          @play_to_beat.player = @game.players.where.not(id: @game.controlling_player_id).first
          @play_to_beat.player_cards << @cards_to_beat
          @play_to_beat.game = @game
          #@play_to_beat.save

          @play.player_cards = [
            @spade4 = PlayerCard.new(@deck.cards[4].instance_values),
            @club4  = PlayerCard.new(@deck.cards[5].instance_values)
          ]

          # Shared tests
          [@play_to_beat,@play].each do |play|
            expect(play.errors[:player_cards]).to eq([])
            expect(play.hand_type).to eq('double')
            expect(play.player_cards.length).to eq(2)
          end

          @game.reload

          expect(@play.valid?).to eq(false)
          expect(@play.beats? @play_to_beat).to eq(false)

        end

        it 'mismatch type' do
          @deck = Deck.new
          # Hand to beat is a double
          @cards_to_beat = [
            @spade4 = PlayerCard.new(@deck.cards[4].instance_values),
            @club4  = PlayerCard.new(@deck.cards[5].instance_values)
          ]

          @play_to_beat = Play.new
          @play_to_beat.player = @game.players.where.not(id: @game.controlling_player_id).first
          @play_to_beat.player_cards << @cards_to_beat
          @play_to_beat.game = @game
          #@play_to_beat.save

          # Attempt to play a single
          @play.player_cards = [
            @spade5 = PlayerCard.new(@deck.cards[8].instance_values)
          ]

          @game.reload

          expect(@play.valid?).to eq(false)

        end

      end
      context 'it beats the hand_to_beat' do
        it 'single' do
          # current player plays their lowest card
          @cards_to_play = @play.player.player_cards.order(:value).first
          @play.player_cards << @cards_to_play
          @play.save
          @game.reload

          @play = @game.controlling_player.plays.new(game_id: @game.id)
          # next player plays their lowest card and beats the previous play
          @cards_to_play = @play.player.player_cards.order(:value).first
          @play.player_cards << @cards_to_play
          expect(@play.valid?).to eq(true)
          @play.save
          @game.reload

          expect(@game.play_to_beat.play).to eq(@play)
          expect(@play.errors[:player_cards]).to eq([])
        end

        it 'double' do
          @deck = Deck.new
          # Give 3 of clubs to current player
          @club3 = @game.player_cards.find_by(value: 2)
          @club3.player_id = @game.controlling_player.id
          @club3.save
          @cards_to_beat = [
            @spade3 = @game.controlling_player.player_cards.order(:value).first,
            @club3
          ]

          @play = @game.controlling_player.plays.new(game_id: @game.id)
          @play.player_cards = @cards_to_beat
          expect(@play.save).to be true
          @game.reload
          @game.controlling_player_id = @game.next_player_id
          @game.save
          @game.reload

          @winning_play = Play.new
          @winning_play.player = @game.controlling_player
          @winning_play.game = @game
          # Give needed cards to current player
          @spade4 = @game.player_cards.find_by(value: 5)
          @spade4.player_id = @game.controlling_player.id
          @spade4.save
          @club4 = @game.player_cards.find_by(value: 6)
          @club4.player_id = @game.controlling_player.id
          @club4.save
          @winning_play.player_cards = [
            @spade4,
            @club4
          ]
          expect(@winning_play.beats? @play).to eq(true)
          expect(@winning_play.save).to be true
        end

        it 'becomes the new play_to_beat' do
          expect(@play.game.play_to_beat).to eq(nil)
          # Play the hand
          @cards_to_play = @play.player.inventory.first
          @play.hand << @cards_to_play

          expect(@play.hand_valid?).to eq(true)
          expect(@play.save)
          expect(@play.game.play_to_beat.play).to eq(@play)
        end
      end
    end
  end

  describe 'hand_type' do
    context 'when a single card is played' do
      it 'single' do
        @cards_to_play = @game.controlling_player.player_cards.order(:value).first
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('single')
      end
    end

    context 'when two cards are played' do
      it 'valid pair' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club3  = PlayerCard.new(rank: "3", suit: "Club")
        @cards_to_play = [@spade3, @club3]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('double')
        expect(@play.errors[:player_cards]).to eq([])
      end
      it 'invalid pair' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club4  = PlayerCard.new(rank: "4", suit: "Club")
        @cards_to_play = [@spade3, @club4]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('double')
        expect(@play.errors[:player_cards]).to eq(['Invalid pair. Rank must match.'])
      end
    end

    context 'when three cards are played' do
      it 'valid triple' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club3  = PlayerCard.new(rank: "3", suit: "Club")
        @diamond3  = PlayerCard.new(rank: "3", suit: "Diamond")
        @cards_to_play = [@spade3, @club3, @diamond3]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('triple')
      end
      it 'invalid triple' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club3  = PlayerCard.new(rank: "3", suit: "Club")
        @diamond4  = PlayerCard.new(rank: "4", suit: "Diamond")
        @cards_to_play = [@spade3, @club3, @diamond4]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('triple')
        expect(@play.errors[:player_cards]).to eq(['Invalid triple. Rank must match.'])
      end
      it 'valid run of 3' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club4  = PlayerCard.new(rank: "4", suit: "Club")
        @diamond5  = PlayerCard.new(rank: "5", suit: "Diamond")
        @cards_to_play = [@spade3, @club4, @diamond5]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('triple run')
      end
      it 'valid run of 3 suited' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club4  = PlayerCard.new(rank: "4", suit: "Spade")
        @diamond5  = PlayerCard.new(rank: "5", suit: "Spade")
        @cards_to_play = [@spade3, @club4, @diamond5]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('triple run suited')
      end
    end

    context 'when four cards are played' do
      it 'valid bomb against a 2' do
        # TODO: Refactor "lowest card first" to FactoryGirl.create :game, hand_to_beat: @cards
        # First hand must contain lowest card
        @spade3 = @game.lowest_card 
        first_play = Play.new(game_id: @game.id, player_id: @game.controlling_player_id, player_cards: [@spade3])
        first_play.save
        @game.reload

        # Setup play to beat
        @heart2 = PlayerCard.new(rank: '2', suit: 'Heart', value: 52, player_id: @game.next_player_id)
        @play_to_beat = Play.new(game_id: @game.id, player_id: @game.controlling_player_id, player_cards: [@heart2])
        @play_to_beat.save
        @game.reload

        @spade3 = PlayerCard.new(value: 1, rank: "3", suit: "Spade")
        @club3  = PlayerCard.new(value: 2, rank: "3", suit: "Club")
        @diamond3  = PlayerCard.new(value: 3, rank: "3", suit: "Diamond")
        @heart3  = PlayerCard.new(value: 4, rank: "3", suit: "Heart")
        @cards_to_play = [@spade3, @club3, @diamond3, @heart3]
        @play = @game.controlling_player.plays.new(game_id: @game.id)
        @play.player_cards = @cards_to_play
        expect(@play.hand_type).to eq('bomb')
        expect(@play.valid?).to eq(true)
      end
      it 'valid bomb against a lesser bomb' do
        # Setup the play to beat
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade", value: 1)
        @club3  = PlayerCard.new(rank: "3", suit: "Club", value: 2)
        @diamond3  = PlayerCard.new(rank: "3", suit: "Diamond", value: 3)
        @heart3  = PlayerCard.new(rank: "3", suit: "Heart", value: 4)
        @cards_to_beat = [@spade3, @club3, @diamond3, @heart3]
        @play_to_beat = Play.new(game_id: @game.id, player_id: @game.controlling_player_id, player_cards: @cards_to_beat)
        @play_to_beat.save
        @game.play_to_beat_id = @play_to_beat.id
        @game.reload

        # Setup the play
        @spade4 = PlayerCard.new(rank: "4", suit: "Spade", value: 5)
        @club4  = PlayerCard.new(rank: "4", suit: "Club", value: 6)
        @diamond4  = PlayerCard.new(rank: "4", suit: "Diamond", value: 7)
        @heart4  = PlayerCard.new(rank: "4", suit: "Heart", value: 8)
        @cards_to_play = [@spade4, @club4, @diamond4, @heart4]
        @play = @game.controlling_player.plays.new(game_id: @game.id)
        @play.player_cards = @cards_to_play

        expect(@play.hand_type).to eq('bomb')
        expect(@play.beats? @play_to_beat).to eq(true)
        expect(@play.valid?).to eq(true)
      end

      it 'invalid bomb against a greater bomb' do
        # Play lowest card first
        @spade3 = @game.lowest_card 
        first_play = Play.new(game_id: @game.id, player_id: @game.controlling_player_id, player_cards: [@spade3])
        first_play.save
        @game.reload

        # Create an Open Table, so every hand is valid.
        @game.controlling_player_id = first_play.player_id
        @game.save
        #@game.reload

        #
        # Setup the play to beat
        @spade4 = PlayerCard.new(rank: "4", suit: "Spade", value: 5)
        @club4  = PlayerCard.new(rank: "4", suit: "Club", value: 6)
        @diamond4  = PlayerCard.new(rank: "4", suit: "Diamond", value: 7)
        @heart4  = PlayerCard.new(rank: "4", suit: "Heart", value: 8)
        @cards_to_beat = [@spade4, @club4, @diamond4, @heart4]
        @play_to_beat = Play.new(game_id: @game.id, player_id: @game.controlling_player_id, player_cards: @cards_to_beat)
        expect(@play_to_beat.save).to eq true
        @game.reload

        # Setup the play
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade", value: 1)
        @club3  = PlayerCard.new(rank: "3", suit: "Club", value: 2)
        @diamond3  = PlayerCard.new(rank: "3", suit: "Diamond", value: 3)
        @heart3  = PlayerCard.new(rank: "3", suit: "Heart", value: 4)
        @cards_to_play = [@spade3, @club3, @diamond3, @heart3]
        @play = Play.new(game_id: @game.id, player_id: @game.controlling_player_id, player_cards: @cards_to_beat)

        expect(@play.hand_type).to eq('bomb')
        #TODO: Refactor this test to only test the @play.beats? method
        #TODO: Move other expectations to another test
        #TODO: Test every validation of a play
        #TODO: Refactor @play.valid? method
        expect(@play.beats? @play_to_beat).to eq(false)
        expect(@play.valid?).to eq(false)
      end

      it 'valid run of 4' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club4  = PlayerCard.new(rank: "4", suit: "Club")
        @diamond5  = PlayerCard.new(rank: "5", suit: "Diamond")
        @diamond6  = PlayerCard.new(rank: "6", suit: "Diamond")
        @cards_to_play = [@spade3, @club4, @diamond5, @diamond6]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('run of 4')
      end
      it 'valid run of 4 suited' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club4  = PlayerCard.new(rank: "4", suit: "Spade")
        @diamond5  = PlayerCard.new(rank: "5", suit: "Spade")
        @diamond6  = PlayerCard.new(rank: "6", suit: "Spade")
        @cards_to_play = [@spade3, @club4, @diamond5, @diamond6]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('run of 4 suited')
      end
    end

    context 'when five cards are played' do
      it 'valid run of 5' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club4  = PlayerCard.new(rank: "4", suit: "Club")
        @diamond5  = PlayerCard.new(rank: "5", suit: "Diamond")
        @diamond6  = PlayerCard.new(rank: "6", suit: "Diamond")
        @diamond7  = PlayerCard.new(rank: "7", suit: "Diamond")
        @cards_to_play = [@spade3, @club4, @diamond5, @diamond6, @diamond7]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('run of 5')
      end
      it 'invalid run of 5' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club4  = PlayerCard.new(rank: "4", suit: "Club")
        @diamond5  = PlayerCard.new(rank: "5", suit: "Diamond")
        @diamond6  = PlayerCard.new(rank: "6", suit: "Diamond")
        @diamond8  = PlayerCard.new(rank: "8", suit: "Diamond")
        @cards_to_play = [@spade3, @club4, @diamond5, @diamond6, @diamond8]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('run of 5')
        expect(@play.errors[:player_cards]).to eq(['Invalid run of 5'])
      end

      it 'valid run of 5 suited' do
        @spade3 = PlayerCard.new(rank: "3", suit: "Spade")
        @club4  = PlayerCard.new(rank: "4", suit: "Spade")
        @diamond5  = PlayerCard.new(rank: "5", suit: "Spade")
        @diamond6  = PlayerCard.new(rank: "6", suit: "Spade")
        @diamond7  = PlayerCard.new(rank: "7", suit: "Spade")
        @cards_to_play = [@spade3, @club4, @diamond5, @diamond6, @diamond7]
        @play.player_cards << @cards_to_play
        expect(@play.hand_type).to eq('run of 5 suited')
      end
    end

    (5..13).each do |i|
      context "when #{i} cards are played" do
        it "run of #{i}" do
          @cards_to_play = create_hand(i, 'run')
          @play.player_cards << @cards_to_play
          expect(@play.hand_type).to eq("run of #{i}")
        end
        it "run of #{i} suited" do
          @cards_to_play = create_hand(i, 'run suited')
          @play.player_cards << @cards_to_play
          expect(@play.hand_type).to eq("run of #{i} suited")
        end
        if [6, 8, 10].include? i
          it "double run of #{i}" do
            @cards_to_play = create_hand(i, 'double run')
            @play.player_cards << @cards_to_play
            expect(@play.hand_type).to eq("double run of #{i}")
            expect(@play.errors[:player_cards]).to be_empty
          end
        end
      end
    end
  end

end
