require 'spec_helper'

describe Frederick::Models::Model do
  let(:hash_code) { 'f8520312a12ecf00f60d5fb9605e8a78' }
  let(:attr_keys) { [:a, :x, :y] }
  let(:attr_values) { ['b', false, nil] }

  subject do
    model = Frederick::Models::Model.new
    model.instance_variable_set("@#{attr_keys[0]}", attr_values[0])
    model.instance_variable_set("@#{attr_keys[1]}", attr_values[1])
    model.instance_variable_set("@#{attr_keys[2]}", attr_values[2])
    model
  end

  before { stub_const("#{described_class}::ATTRS", attr_keys) }

  it 'requires right files' do
    expect($LOADED_FEATURES).to satisfy do |files|
      files.any? { |file| file.include?('active_support/inflector') }
    end
  end

  describe '#initialize' do
    subject { Frederick::Models::Model.new(x: 'y', a: 'b') }

    it 'calls .send for each key-value specified' do
      expect_any_instance_of(Frederick::Models::Model).to receive(:x=).with('y')
      expect_any_instance_of(Frederick::Models::Model).to receive(:a=).with('b')
      subject
    end
  end

  describe '#to_json' do
    it 'returns a json hash of all instance variables, but without the @' do
      expect(JSON.parse(subject.to_json)['x']).to eq false
    end
  end

  describe '#attr_values' do
    it 'gets hash where attr name is key and attr value is ' do
      expect(subject.attr_values).to eq(a: 'b', x: false)
    end
  end

  describe '#hash' do
    let(:attr_values) { subject.attr_values.to_s }

    before { expect(Digest::MD5).to receive(:digest).with(attr_values).and_call_original }

    it 'returns the stringified hash code of the instance values, code is the same over new instances of ruby' do
      expect(subject.hash).to eq hash_code
    end
  end

  describe '.hash_codes_with_index' do
    let(:model2) do
      model2 = Frederick::Models::Model.new
      model2.instance_variable_set("@#{attr_keys[2]}", attr_values[1])
      model2
    end
    let(:hash_code2) { '888626b8a600efea19963402b1cd3e40' }
    let(:models) { [subject, model2] }
    let(:index) { 0 }
    let(:index2) { 1 }
    let(:hash_codes_with_index) { {hash_code => index, hash_code2 => index2} }

    it 'returns the hash code as keys and the index in the array as the val' do
      expect(described_class.hash_codes_with_index(models)).to eq(hash_codes_with_index)
    end

    context 'models w/ same hash code (aka same state)' do
      let(:index) { 2 }
      let(:index2) { 4 }
      let(:models) { [subject, subject, subject, model2, model2] }

      it 'only creates one key-val for multiple models with same state' do
        expect(described_class.hash_codes_with_index(models)).to eq(hash_codes_with_index)
      end
    end
  end

  describe '#to_hash' do
    it 'returns a json hash of all instance variables, but without the @' do
      expect(subject.to_hash['a']).to eq 'b'
      expect(subject.to_hash['x']).to eq 'y'
    end
  end

  describe '.from_hash' do
    let(:model) {
      Frederick::Models::Model.from_hash({
        x: 'y',
        location: {
          a: 'b'
        },
        locations: [{
          c: 'd'
        }]
      })
    }

    before do
      allow_any_instance_of(Frederick::Models::Model).to receive(:x)
      allow_any_instance_of(Frederick::Models::Model).to receive(:location)
      allow_any_instance_of(Frederick::Models::Model).to receive(:locations)
      expect_any_instance_of(Frederick::Models::Model).to receive(:x=).with('y')
      expect_any_instance_of(Frederick::Models::Model).to receive(:location=).with(a_kind_of Frederick::Models::Location)
      expect_any_instance_of(Frederick::Models::Model).to receive(:locations=).with(a_kind_of Array)
    end

    it 'creates a model from the hash' do
      expect(model).to be_a Frederick::Models::Model
    end
  end

  describe '.from_list' do
    let(:models) do
      Frederick::Models::Model.from_list([
                                             {x: 'y'},
                                             {a: 'b'}
                                         ])
    end

    before do
      expect(Frederick::Models::Model).to receive(:from_hash).twice.and_return(Frederick::Models::Model.new)
    end

    it 'returns a list of models' do
      expect(models).to be_a(Array)
      expect(models[0]).to be_a Frederick::Models::Model
    end
  end
end
