require 'spec_helper'
require 'synchromesh/integration/test_components'
require 'rspec-steps'

describe "column types on client", js: true do

  before(:all) do
    # TimeXternal - lets us easily get back time values
    # that will look like client json strings.
    class Timex

      def initialize(time = nil)
        if time.is_a? Time
          @time = time
        elsif time.is_a? Timex
          @time = time.time.dup
        elsif time
          @time = Time.new(time.localtime)
        else
          @time = Time.new
        end
      end

      def method_missing(m, *args, &b)
        val = time.send(m, *args, &b)
        val = Timex.new(val) if val.is_a? Time
        val
      end

      def as_json
        strftime("%Y-%m-%dT%H:%M:%S%z")
      end

      attr_reader :time

      def time_only
        utc_time = Timex.new(self).utc
        start_time = Time.parse('2000-01-01T00:00:00.000-00:00').utc
        Timex.new (start_time+(utc_time-utc_time.beginning_of_day.to_i).to_i).localtime
      end

      class << self
        def at(x)
          Timex.new(Time.at(x))
        end
        def sqlmin
          Timex.new(Time.parse('2001-01-01T00:00:00.000-00:00').localtime)
        end
        def parse(s)
          Timex.new(Time.parse(s))
        end
      end
    end

    require 'pusher'
    require 'pusher-fake'
    Pusher.app_id = "MY_TEST_ID"
    Pusher.key =    "MY_TEST_KEY"
    Pusher.secret = "MY_TEST_SECRET"
    require "pusher-fake/support/base"

    HyperMesh.configuration do |config|
      config.transport = :pusher
      config.channel_prefix = "synchromesh"
      config.opts = {app_id: Pusher.app_id, key: Pusher.key, secret: Pusher.secret}.merge(PusherFake.configuration.web_options)
    end

    class TypeTest < ActiveRecord::Base
      def self.build_tables
        connection.create_table :type_tests, force: true do |t|
          t.binary :binary
          t.boolean :boolean
          t.date :date
          t.datetime :datetime
          t.decimal :decimal, precision: 5, scale: 2
          t.float :float
          t.integer :integer
          t.bigint :bigint
          t.string :string
          t.text :text
          t.time :time
          t.timestamp :timestamp
        end
      end
    end

    class DefaultTest < ActiveRecord::Base
      def self.build_tables
        connection.create_table :default_tests, force: true do |t|
          t.string :string, default: "I'm a string!"
          t.date :date, default: Date.today
          t.datetime :datetime, default: Time.now
        end
      end
    end

    isomorphic do
      class TypeTest < ActiveRecord::Base
      end
      class DefaultTest < ActiveRecord::Base
      end
    end

    TypeTest.build_tables rescue nil
    DefaultTest.build_tables rescue nil

    ActiveRecord::Base.instance_variable_set(:@public_columns_hash, nil)

  end

  before(:each) do
    stub_const 'ApplicationPolicy', Class.new
    ApplicationPolicy.class_eval do
      always_allow_connection
      regulate_all_broadcasts { |policy| policy.send_all }
      allow_change(to: :all, on: [:create, :update, :destroy]) { true }
    end

    size_window(:small, :portrait)
  end

  it 'transfers the columns hash to the client' do
    expect_evaluate_ruby do
      TypeTest.columns_hash
    end.to eq(TypeTest.columns_hash.as_json)
  end

  it 'creates a dummy value of the appropriate type' do
    t = Time.now
    TypeTest.create(
      boolean: true,
      date: t,
      datetime: t,
      decimal: 12.2,
      float: 13.2,
      integer: 14,
      bigint: 15,
      string: "hello",
      text: "goodby",
      time: t,
      timestamp: t
    )
    expect_evaluate_ruby do
      TypeTest.columns_hash.collect do |attr, _info|
        TypeTest.find(1).send(attr).class
      end
    end.to eq([
      'Number', 'NilClass', 'Boolean', 'Date', 'Time', 'Number', 'Number', 'Number',
      'Number', 'String', 'String', 'Time', 'Time'
    ])
  end

  it 'loads and converts the value' do
    t = Timex.parse('1/2/2003')
    r = TypeTest.create(
      boolean: true,
      date: t.time,
      datetime: t.time,
      decimal: 12.2,
      float: 13.2,
      integer: 14,
      bigint: 15,
      string: "hello",
      text: "goodby",
      time: t.time,
      timestamp: t.time
    )
    r.reload
    expect_promise do
      ReactiveRecord.load do
        TypeTest.columns_hash.collect do |attr, _info|
          [TypeTest.find(1).send(attr).class, TypeTest.find(1).send(attr)]
        end.flatten
      end
    end.to eq([
      'Number', 1,
      'NilClass', nil,
      'Boolean', true,
      'Date', t.to_date.as_json,
      'Time', t.as_json,
      'Number', 12.2,
      'Number', 13.2,
      'Number', 14,
      'Number', 15,
      'String', 'hello',
      'String', 'goodby',
      'Time', t.time_only.as_json, # date is indeterminate for active record time
      'Time', t.as_json
    ])
  end

  it 'while loading the dummy value delegates the correct type with operators etc' do
    t = Time.parse('1/2/2003')
    TypeTest.create(
      boolean: true,
      date: t,
      datetime: t,
      decimal: 12.2,
      float: 13.2,
      integer: 14,
      bigint: 15,
      string: "hello",
      text: "goodby",
      time: t,
      timestamp: t
    )
    expect_evaluate_ruby do
      t = TypeTest.find(1)
      [
        !t.boolean, t.date+1, t.datetime+2.days, t.decimal + 5, t.float + 6, t.integer + 7,
        t.bigint + 8, t.string.length, t.text.length, t.time+3.days, t.timestamp+4.days
      ]
    end.to eq([
      true, "2001-01-02", (Timex.sqlmin+2.days).as_json, 5, 6, 7,
      8, 0, 0, (Timex.sqlmin+3.days).as_json, (Timex.sqlmin+4.days).as_json
    ])
  end

  it 'converts a time string to a time on writing' do
    expect_evaluate_ruby do
      test = TypeTest.new
      test.datetime = "1/1/2001"
      (test.datetime + 1.minute)
    end.to eq((Timex.parse("1/1/2001") + 1.minute).as_json)
  end

  it 'converts a integer to a time on writing' do
    expect_evaluate_ruby do
      test = TypeTest.new
      test.datetime = 12
      test.datetime + 60.seconds
    end.to eq((Timex.at(12)+60.seconds).as_json)
  end

  it 'converts a float to a time on writing' do
    expect_evaluate_ruby do
      test = TypeTest.new
      test.datetime = 12.2
      test.datetime + 60.9
    end.to eq((Timex.at(12.2)+60.9).as_json)
  end

  it 'converts a 0, "false" false and nil to a boolean false, everything else is true' do
    expect_evaluate_ruby do
      [0, 'false', false, nil, 'hi', 17, [], true].collect do |val|
        TypeTest.new(boolean: val).boolean
      end
    end.to eq([false, false, false, false, true, true, true, true])
  end

  it 'converts other dates just like time' do
    expect_evaluate_ruby do
      [:datetime, :time, :timestamp].collect do |attr|
        TypeTest.new(attr => 12).send(attr)
      end.uniq
    end.to eq([Timex.at(12).as_json])
  end

  it 'converts floats, decimals, integers and bigints' do
    expect_evaluate_ruby do
      [:float, :decimal, :integer, :bigint].collect do |attr|
        TypeTest.new(attr => 12.5).send(attr) +
        TypeTest.new(attr => "12.2").send(attr) +
        TypeTest.new(attr => -12.2).send(attr)
      end.uniq
    end.to eq([12.5, 12])
  end

  it 'converts strings and text' do
    expect_evaluate_ruby do
      [:string, :text].collect do |attr|
        [TypeTest.new(attr => 12).send(attr), TypeTest.new(attr => '12').send(attr)]
      end.flatten.uniq
    end.to eq(['12'])
  end

  it 'uses the default value if specified for the dummy value' do
    r = DefaultTest.create(string: "no no no")
    expect_evaluate_ruby do
      t = DefaultTest.find(1)
      [t.string, t.date, t.datetime]
    end.to eq(["I'm a string!", r.date.as_json, Timex.new(r.datetime.localtime).as_json])
  end

  it 'uses the default value if specified when initializing a new record' do
    expect_evaluate_ruby do
      DefaultTest.new.string
    end.to eq("I'm a string!")
  end

  it 'uses the default value when initializing a new record & can be saved' do
    starting_fetch_time = evaluate_ruby("ReactiveRecord::Base.last_fetch_at")
    expect_promise do
      record = DefaultTest.new
      record.string = record.string.reverse
      record.save
    end.to be_truthy
    expect(DefaultTest.find(1).string).to eq("!gnirts a m'I")
    expect_evaluate_ruby("ReactiveRecord::Base.last_fetch_at").to eq(starting_fetch_time)
  end

end