# frozen_string_literal: true

require_relative "abstract_unit"
require "active_support/core_ext/date_time"
require "active_support/core_ext/numeric/time"
require "active_support/core_ext/string/conversions"
require_relative "time_zone_test_helpers"

class TimeTravelTest < ActiveSupport::TestCase
  include TimeZoneTestHelpers

  class TimeSubclass < ::Time; end
  class DateSubclass < ::Date; end
  class DateTimeSubclass < ::DateTime; end

  def test_time_helper_travel
    Time.stub(:now, Time.now) do
      expected_time = Time.now + 1.day
      travel 1.day

      assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
      assert_equal expected_time.to_date, Date.today
      assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
    ensure
      travel_back
    end
  end

  def test_time_helper_travel_with_block
    Time.stub(:now, Time.now) do
      expected_time = Time.now + 1.day

      travel 1.day do
        assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
        assert_equal expected_time.to_date, Date.today
        assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
      end

      assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db)
      assert_not_equal expected_time.to_date, Date.today
      assert_not_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
    end
  end

  def test_time_helper_travel_to
    Time.stub(:now, Time.now) do
      expected_time = Time.new(2004, 11, 24, 1, 4, 44)
      travel_to expected_time

      assert_equal expected_time, Time.now
      assert_equal Date.new(2004, 11, 24), Date.today
      assert_equal expected_time.to_datetime, DateTime.now
    ensure
      travel_back
    end
  end

  def test_time_helper_travel_to_with_block
    Time.stub(:now, Time.now) do
      expected_time = Time.new(2004, 11, 24, 1, 4, 44)

      travel_to expected_time do
        assert_equal expected_time, Time.now
        assert_equal Date.new(2004, 11, 24), Date.today
        assert_equal expected_time.to_datetime, DateTime.now
      end

      assert_not_equal expected_time, Time.now
      assert_not_equal Date.new(2004, 11, 24), Date.today
      assert_not_equal expected_time.to_datetime, DateTime.now
    end
  end

  def test_time_helper_travel_to_with_time_zone
    with_env_tz "US/Eastern" do
      with_tz_default ActiveSupport::TimeZone["UTC"] do
        Time.stub(:now, Time.now) do
          expected_time = 5.minutes.ago

          travel_to 5.minutes.ago do
            assert_equal expected_time.to_s(:db), Time.zone.now.to_s(:db)
          end
        end
      end
    end
  end

  def test_time_helper_travel_to_with_string_for_time_zone
    with_env_tz "US/Eastern" do
      with_tz_default ActiveSupport::TimeZone["UTC"] do
        Time.stub(:now, Time.now) do
          expected_time = Time.new(2004, 11, 24, 1, 4, 44)

          travel_to "2004-11-24 01:04:44" do
            assert_equal expected_time.to_s(:db), Time.zone.now.to_s(:db)
          end
        end
      end
    end
  end

  def test_time_helper_travel_back
    Time.stub(:now, Time.now) do
      expected_time = Time.new(2004, 11, 24, 1, 4, 44)

      travel_to expected_time
      assert_equal expected_time, Time.now
      assert_equal Date.new(2004, 11, 24), Date.today
      assert_equal expected_time.to_datetime, DateTime.now
      travel_back

      assert_not_equal expected_time, Time.now
      assert_not_equal Date.new(2004, 11, 24), Date.today
      assert_not_equal expected_time.to_datetime, DateTime.now
    ensure
      travel_back
    end
  end

  def test_time_helper_travel_back_with_block
    Time.stub(:now, Time.now) do
      expected_time = Time.new(2004, 11, 24, 1, 4, 44)

      travel_to expected_time
      assert_equal expected_time, Time.now
      assert_equal Date.new(2004, 11, 24), Date.today
      assert_equal expected_time.to_datetime, DateTime.now

      travel_back do
        assert_not_equal expected_time, Time.now
        assert_not_equal Date.new(2004, 11, 24), Date.today
        assert_not_equal expected_time.to_datetime, DateTime.now
      end

      assert_equal expected_time, Time.now
      assert_equal Date.new(2004, 11, 24), Date.today
      assert_equal expected_time.to_datetime, DateTime.now
    ensure
      travel_back
    end
  end

  def test_time_helper_travel_to_with_nested_calls_with_blocks
    Time.stub(:now, Time.now) do
      outer_expected_time = Time.new(2004, 11, 24, 1, 4, 44)
      inner_expected_time = Time.new(2004, 10, 24, 1, 4, 44)
      travel_to outer_expected_time do
        e = assert_raises(RuntimeError) do
          travel_to(inner_expected_time) do
            # noop
          end
        end
        assert_match(/Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing\./, e.message)
      end
    end
  end

  def test_time_helper_travel_to_with_nested_calls
    Time.stub(:now, Time.now) do
      outer_expected_time = Time.new(2004, 11, 24, 1, 4, 44)
      inner_expected_time = Time.new(2004, 10, 24, 1, 4, 44)
      travel_to outer_expected_time do
        assert_nothing_raised do
          travel_to(inner_expected_time)

          assert_equal inner_expected_time, Time.now
        end
      end
    end
  end

  def test_time_helper_travel_to_with_subsequent_calls
    Time.stub(:now, Time.now) do
      initial_expected_time = Time.new(2004, 11, 24, 1, 4, 44)
      subsequent_expected_time = Time.new(2004, 10, 24, 1, 4, 44)
      assert_nothing_raised do
        travel_to initial_expected_time
        travel_to subsequent_expected_time

        assert_equal subsequent_expected_time, Time.now

        travel_back
      end
    ensure
      travel_back
    end
  end

  def test_time_helper_travel_with_subsequent_block
    Time.stub(:now, Time.now) do
      outer_expected_time = Time.new(2004, 11, 24, 1, 4, 44)
      inner_expected_time = Time.new(2004, 10, 24, 1, 4, 44)
      travel_to outer_expected_time

      assert_equal outer_expected_time, Time.now

      assert_nothing_raised do
        travel_to(inner_expected_time) do
          assert_equal inner_expected_time, Time.now
        end
      end

      assert_equal outer_expected_time, Time.now
    ensure
      travel_back
    end
  end

  def test_travel_to_will_reset_the_usec_to_avoid_mysql_rounding
    Time.stub(:now, Time.now) do
      travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do
        assert_equal 50, Time.now.sec
        assert_equal 0, Time.now.usec
        assert_equal 50, DateTime.now.sec
        assert_equal 0, DateTime.now.usec
      end
    end
  end

  def test_time_helper_travel_with_time_subclass
    assert_equal TimeSubclass, TimeSubclass.now.class
    assert_equal DateSubclass, DateSubclass.today.class
    assert_equal DateTimeSubclass, DateTimeSubclass.now.class

    travel 1.day do
      assert_equal TimeSubclass, TimeSubclass.now.class
      assert_equal DateSubclass, DateSubclass.today.class
      assert_equal DateTimeSubclass, DateTimeSubclass.now.class
      assert_equal Time.now.to_s, TimeSubclass.now.to_s
      assert_equal Date.today.to_s, DateSubclass.today.to_s
      assert_equal DateTime.now.to_s, DateTimeSubclass.now.to_s
    end
  end

  def test_time_helper_freeze_time
    expected_time = Time.now
    freeze_time
    sleep(1)

    assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
  ensure
    travel_back
  end

  def test_time_helper_freeze_time_with_block
    expected_time = Time.now

    freeze_time do
      sleep(1)

      assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
    end

    assert_operator expected_time.to_s(:db), :<, Time.now.to_s(:db)
  end

  def test_time_helper_unfreeze_time
    assert_equal method(:travel_back), method(:unfreeze_time)
  end
end
