require 'spec_helper'
require 'memory_profiler'
require 'tempfile'
require 'timecop'

class TestBot < Ebooks::Bot
  attr_accessor :twitter

  def configure
  end

  def on_direct_message(dm)
    reply dm, "echo: #{dm.text}"
  end

  def on_mention(tweet)
    reply tweet, "echo: #{meta(tweet).mentionless}"
  end

  def on_timeline(tweet)
    reply tweet, "fine tweet good sir"
  end
end

module Ebooks::Test
  # Generates a random twitter id
  def twitter_id
    (rand*10**18).to_i
  end

  # Creates a mock direct message
  # @param username User sending the DM
  # @param text DM content
  def mock_dm(username, text)
    Twitter::DirectMessage.new(id: twitter_id,
                               sender: { id: twitter_id, screen_name: username},
                               text: text)
  end

  # Creates a mock tweet
  # @param username User sending the tweet
  # @param text Tweet content
  def mock_tweet(username, text, extra={})
    mentions = text.split.find_all { |x| x.start_with?('@') }
    tweet = Twitter::Tweet.new({
      id: twitter_id,
      in_reply_to_status_id: 'mock-link',
      user: { id: twitter_id, screen_name: username },
      text: text,
      created_at: Time.now.to_s,
      entities: {
        user_mentions: mentions.map { |m|
          { screen_name: m.split('@')[1],
            indices: [text.index(m), text.index(m)+m.length] }
        }
      }
    }.merge!(extra))
    tweet
  end

  def twitter_spy(bot)
    twitter = spy("twitter")
    allow(twitter).to receive(:update).and_return(mock_tweet(bot.username, "test tweet"))
    twitter
  end

  def simulate(bot, &b)
    bot.twitter = twitter_spy(bot)
    b.call
  end

  def expect_direct_message(bot, content)
    expect(bot.twitter).to have_received(:create_direct_message).with(anything(), content, {})
    bot.twitter = twitter_spy(bot)
  end

  def expect_tweet(bot, content)
    expect(bot.twitter).to have_received(:update).with(content, anything())
    bot.twitter = twitter_spy(bot)
  end
end


describe Ebooks::Bot do
  include Ebooks::Test
  let(:bot) { TestBot.new('test_ebooks') }

  before { Timecop.freeze }
  after { Timecop.return }

  it "responds to dms" do
    simulate(bot) do
      bot.receive_event(mock_dm("m1sp", "this is a dm"))
      expect_direct_message(bot, "echo: this is a dm")
    end
  end

  it "responds to mentions" do
    simulate(bot) do
      bot.receive_event(mock_tweet("m1sp", "@test_ebooks this is a mention"))
      expect_tweet(bot, "@m1sp echo: this is a mention")
    end
  end

  it "responds to timeline tweets" do
    simulate(bot) do
      bot.receive_event(mock_tweet("m1sp", "some excellent tweet"))
      expect_tweet(bot, "@m1sp fine tweet good sir")
    end
  end

  it "links tweets to conversations correctly" do
    tweet1 = mock_tweet("m1sp", "tweet 1", id: 1, in_reply_to_status_id: nil)

    tweet2 = mock_tweet("m1sp", "tweet 2", id: 2, in_reply_to_status_id: 1)

    tweet3 = mock_tweet("m1sp", "tweet 3", id: 3, in_reply_to_status_id: nil)

    bot.conversation(tweet1).add(tweet1)
    expect(bot.conversation(tweet2)).to eq(bot.conversation(tweet1))

    bot.conversation(tweet2).add(tweet2)
    expect(bot.conversation(tweet3)).to_not eq(bot.conversation(tweet2))
  end

  it "stops mentioning people after a certain limit" do
    simulate(bot) do
      bot.receive_event(mock_tweet("spammer", "@test_ebooks @m1sp 1"))
      expect_tweet(bot, "@spammer @m1sp echo: 1")

      Timecop.travel(Time.now + 60)
      bot.receive_event(mock_tweet("spammer", "@test_ebooks @m1sp 2"))
      expect_tweet(bot, "@spammer @m1sp echo: 2")

      Timecop.travel(Time.now + 60)
      bot.receive_event(mock_tweet("spammer", "@test_ebooks @m1sp 3"))
      expect_tweet(bot, "@spammer echo: 3")
    end
  end

  it "doesn't stop mentioning them if they reply" do
    simulate(bot) do
      bot.receive_event(mock_tweet("spammer", "@test_ebooks @m1sp 4"))
      expect_tweet(bot, "@spammer @m1sp echo: 4")

      Timecop.travel(Time.now + 60)
      bot.receive_event(mock_tweet("m1sp", "@spammer @test_ebooks 5"))
      expect_tweet(bot, "@m1sp @spammer echo: 5")

      Timecop.travel(Time.now + 60)
      bot.receive_event(mock_tweet("spammer", "@test_ebooks @m1sp 6"))
      expect_tweet(bot, "@spammer @m1sp echo: 6")
    end
  end

  it "doesn't get into infinite bot conversations" do
    simulate(bot) do
      bot.receive_event(mock_tweet("spammer", "@test_ebooks @m1sp 7"))
      expect_tweet(bot, "@spammer @m1sp echo: 7")

      Timecop.travel(Time.now + 10)
      bot.receive_event(mock_tweet("spammer", "@test_ebooks @m1sp 8"))
      expect_tweet(bot, "@spammer @m1sp echo: 8")

      Timecop.travel(Time.now + 10)
      bot.receive_event(mock_tweet("spammer", "@test_ebooks @m1sp 9"))
      expect(bot.twitter).to_not have_received(:update)
    end
  end

  it "blocks blacklisted users on contact" do
    simulate(bot) do
      bot.blacklist = ["spammer"]
      bot.receive_event(mock_tweet("spammer", "@test_ebooks @m1sp 7"))
      expect(bot.twitter).to have_received(:block).with("spammer")
    end
  end
end