require 'spec_helper'

describe Locomotive::ContentType do

  before(:each) do
    allow_any_instance_of(Locomotive::Site).to receive(:create_default_pages!).and_return(true)
  end

  context 'when validating' do

    it 'haves a valid factory' do
      content_type = build(:content_type)
      content_type.entries_custom_fields.build label: 'anything', type: 'string'
      expect(content_type).to be_valid
    end

    # Validations ##

    %w{site name}.each do |field|
      it "requires the presence of #{field}" do
        content_type = build(:content_type, field.to_sym => nil)
        expect(content_type).to_not be_valid
        expect(content_type.errors[field.to_sym]).to eq(["can't be blank"])
      end
    end

    it 'requires the presence of slug' do
      content_type = build(:content_type, name: nil, slug: nil)
      expect(content_type).to_not be_valid
      expect(content_type.errors[:slug]).to eq(["can't be blank"])
    end

    it 'is not valid if slug is not unique' do
      content_type = build(:content_type)
      content_type.entries_custom_fields.build label: 'anything', type: 'string'
      content_type.save
      content_type = build(:content_type, site: content_type.site, slug: content_type.slug)
      expect(content_type).to_not be_valid
      expect(content_type.errors[:slug]).to eq(["is already taken"])
    end

    it 'is not valid if there is not at least one field' do
      content_type = build(:content_type)
      expect(content_type).to_not be_valid
      expect(content_type.errors[:entries_custom_fields]).to eq({ base: ['At least, one custom field is required'] })
    end

    %w(created_at updated_at).each do |name|
      it "does not allow #{name} as name" do
        content_type = build(:content_type)
        field = content_type.entries_custom_fields.build label: 'anything', type: 'string', name: name
        expect(field.valid?).to eq(false)
        expect(field.errors[:name]).to eq(['is reserved'])
      end
    end

    it 'sets a slug from the name before the validation' do
      content_type = build(:content_type, name: 'my content Type')
      content_type.valid?
      expect(content_type.slug).to match(/slug_of_content_type_/)
    end

    it 'make sure the slug is correctly set before the validation' do
      content_type = build(:content_type, slug: 'my content-type')
      content_type.valid?
      expect(content_type.slug).to eq('my_content_type')
    end

  end

  context '#ordered_entries' do

    before(:each) do
      (@content_type = build_content_type(order_by: 'created_at')).save
      @content_2 = @content_type.entries.create name: 'Sacha'
      @content_1 = @content_type.entries.create name: 'Did'
    end

    it 'orders with the ASC direction by default' do
      expect(@content_type.order_direction).to eq('asc')
    end

    it 'has a getter for manual order' do
      expect(@content_type.order_manually?).to eq(false)
      @content_type.order_by = '_position'
      expect(@content_type.order_manually?).to eq(true)
    end

    it 'returns a list of entries ordered manually' do
      @content_type.order_by = '_position'
      expect(@content_type.ordered_entries.collect(&:name)).to eq(%w(Sacha Did))
    end

    it 'returns a list of entries ordered by a column specified by order_by (ASC)' do
      @content_type.order_by = @content_type.entries_custom_fields.where(name: 'name').first._id
      expect(@content_type.ordered_entries.collect(&:name)).to eq(%w(Did Sacha))
    end

    it 'returns a list of entries ordered by a column specified by order_by (DESC)' do
      @content_type.order_by = @content_type.entries_custom_fields.where(name: 'name').first._id
      @content_type.order_direction = 'desc'
      expect(@content_type.ordered_entries.collect(&:name)).to eq(%w(Sacha Did))
    end

    it 'returns a list of contents ordered through condition {order_by: "name asc"}' do
      @content_type.order_by = @content_type.entries_custom_fields.where(name: 'name').first._id
      @content_type.order_direction = 'desc'
      expect(@content_type.ordered_entries(order_by: 'name asc').collect(&:name)).to eq(%w(Did Sacha))
    end

    it 'returns a list of entries ordered by a Date column when first instance is missing the value' do
      @content_type.order_by = @content_type.entries_custom_fields.where(name: 'active_at').first._id
      @content_2.update_attribute :active_at, Date.parse('01/01/2001')
      content_3 = @content_type.entries.create name: 'Mario', active_at: Date.parse('02/02/2001')

      expect(@content_type.ordered_entries.map(&:active_at)).to eq([nil, Date.parse('01/01/2001'), Date.parse('02/02/2001')])

      @content_type.order_direction = 'desc'
      expect(@content_type.ordered_entries.map(&:active_at)).to eq([Date.parse('02/02/2001'), Date.parse('01/01/2001'), nil])
    end

  end

  describe 'belongs_to/has_many relationship' do

    before(:each) do
      build_belongs_to_has_many_relationship

      @category_1 = @category_content_type.entries.create name: 'Gems'
      @category_2 = @category_content_type.entries.create name: 'Service'

      @content_1 = @content_type.entries.create name: 'Github',        category: @category_2
      @content_2 = @content_type.entries.create name: 'LocomotiveCMS', category: @category_1, description: 'Lorem ipsum',  _position_in_category: 1
      @content_3 = @content_type.entries.create name: 'RubyOnRails',   category: @category_1, description: 'Zzzzzz',       _position_in_category: 2
    end

    context '#ordering in a belongs_to/has_many relationship' do

      it 'orders projects based on the default order of the Project content type' do
        expect(@category_1.projects.relation_metadata.order).to eq(%w(name desc))
        expect(@category_1.projects.sort.map(&:name)).to eq(%w(RubyOnRails LocomotiveCMS).sort)
        expect(@category_1.projects.ordered.all.map(&:name)).to eq(%w(RubyOnRails LocomotiveCMS))
      end

      it 'updates the information about the order of a has_many relationship if the target class changes its order' do
        @content_type.order_by = 'description'; @content_type.order_direction = 'ASC'; @content_type.save!
        @category_1 = safe_find(@category_1.class, @category_1._id)

        expect(@category_1.projects.relation_metadata.order).to eq(%w(description ASC))
        expect(@category_1.projects.map(&:name)).to eq(%w(LocomotiveCMS RubyOnRails))
      end

      it 'uses the order by position if the UI option is enabled' do
        field = @category_content_type.entries_custom_fields.where(name: 'projects').first
        field.ui_enabled = true;

        @category_content_type.save!; @category_1 = safe_find(@category_1.class, @category_1._id)

        expect(@category_1.projects.relation_metadata.order.to_s).to eq('position_in_category')
        expect(@category_1.projects.map(&:name)).to eq(%w(LocomotiveCMS RubyOnRails))
      end

    end

    describe '#list_of_groups' do

      subject { @content_type.send(:list_of_groups) }

      it { expect(subject.size).to eq 2 }
      it { expect(subject[0][:name]).to eq 'Gems' }
      it { expect(subject[1][:name]).to eq 'Service' }

    end

  end

  describe 'custom fields' do

    before(:each) do
      site = build(:site)
      allow(Locomotive::Site).to receive(:find).and_return(site)

      @content_type = build_content_type(site: site)
      # Locomotive::ContentType.logger = Logger.new($stdout)
      # Locomotive::ContentType.db.connection.instance_variable_set(:@logger, Logger.new($stdout))
    end

    context 'validation' do

      %w{label type}.each do |key|
        it "validates presence of #{key}" do
          field = @content_type.entries_custom_fields.build({ label: 'Shortcut', type: 'string' }.merge(key.to_sym => nil))
          expect(field).to_not be_valid
          expect(field.errors[key.to_sym]).to eq(["can't be blank"])
        end
      end

      it 'does not have unique label' do
        field = @content_type.entries_custom_fields.build label: 'Active', type: 'boolean'
        expect(field).to_not be_valid
        expect(field.errors[:label]).to eq(["is already taken"])
      end

      it 'invalidates parent if custom field is not valid' do
        field = @content_type.entries_custom_fields.build
        expect(@content_type).to_not be_valid
        expect(@content_type.entries_custom_fields.last.errors[:label]).to eq(["can't be blank"])
      end

    end

    context 'define core attributes' do

      it 'has an unique name' do
        @content_type.valid?
        expect(@content_type.entries_custom_fields.first.name).to eq('name')
        expect(@content_type.entries_custom_fields.last.name).to eq('active_at')
      end

    end

    context 'build and save' do

      before(:each) do
        @content_type.save
      end

      it 'builds asset' do
        asset = @content_type.entries.build
        expect {
          asset.name
          asset.description
          asset.active
        }.not_to raise_error
      end

      it 'assigns values to newly built asset' do
        asset = build_content_entry(@content_type)
        expect(asset.description).to eq('Lorem ipsum')
        expect(asset.active).to eq(true)
      end

      it 'saves asset' do
        asset = build_content_entry(@content_type)
        asset.save and @content_type.reload
        asset = @content_type.entries.first
        expect(asset.description).to eq('Lorem ipsum')
        expect(asset.active).to eq(true)
      end

      it 'does not modify entries from another content type' do
        asset = build_content_entry(@content_type)
        asset.save and @content_type.reload
        another_content_type = Locomotive::ContentType.new
        expect { another_content_type.entries.build.description }.to raise_error
      end

    end

    context 'modifying fields' do

      before(:each) do
        @content_type.save
        @asset = build_content_entry(@content_type).save
      end

      it 'adds new field' do
        @content_type.entries_custom_fields.build label: 'Author', name: 'author', type: 'string'
        @content_type.save && @content_type.reload
        asset = @content_type.entries.first
        expect { asset.author }.to_not raise_error
      end

      it 'removes a field' do
        @content_type.entries_custom_fields.destroy_all(name: 'active_at')
        @content_type.save && @content_type.reload
        asset = @content_type.entries.first
        expect { asset.active_at }.to raise_error
      end

      it 'removes the field used as the label when setting the original label_field_name value before' do
        @content_type.label_field_name = 'name'
        @content_type.entries_custom_fields.destroy_all(name: @content_type.label_field_name)
        @content_type.save
        expect(@content_type.label_field_name).to eq('description')
      end

      it 'renames field label' do
        @content_type.entries_custom_fields[1].label = 'Simple description'
        @content_type.entries_custom_fields[1].name = nil
        @content_type.save && @content_type.reload
        asset = @content_type.entries.first
        expect(asset.simple_description).to eq('Lorem ipsum')
      end

    end

    context 'managing from hash' do

      it 'adds new field' do
        @content_type.entries_custom_fields.clear
        field = @content_type.entries_custom_fields.build label: 'Title'
        @content_type.entries_custom_fields_attributes = { 0 => { id: field.id.to_s, 'label' => 'A title', 'type' => 'string' }, 1 => { 'label' => 'Tagline', 'type' => 'sring' } }
        expect(@content_type.entries_custom_fields.size).to eq(2)
        expect(@content_type.entries_custom_fields.first.label).to eq('A title')
        expect(@content_type.entries_custom_fields.last.label).to eq('Tagline')
      end

      it 'updates/removes fields' do
        @content_type.save

        field = @content_type.entries_custom_fields.build label: 'Title', type: 'string'
        @content_type.save

        @content_type.update_attributes(entries_custom_fields_attributes: {
          '0' => { '_id' => lookup_field_id(1), 'label' => 'My Description', 'type' => 'text', '_destroy' => '1' },
          '1' => { '_id' => lookup_field_id(2), 'label' => 'Active', 'type' => 'boolean', '_destroy' => '1' },
          '2' => { '_id' => field._id, 'label' => 'My Title !' },
          '3' => { 'label' => 'Published at', 'type' => 'string' }
        })

        @content_type = Locomotive::ContentType.find(@content_type.id)

        expect(@content_type.entries_custom_fields.size).to eq(4)
        expect(@content_type.entries_custom_fields.map(&:name)).to eq(%w(name active_at title published_at))
        expect(@content_type.entries_custom_fields[2].label).to eq('My Title !')
      end

    end

  end

  describe 'finding by id or slug' do

    let(:content_type) { build_content_type(slug: 'my_project') }
    let(:id_or_slug) { 'unknown' }

    subject { Locomotive::ContentType.by_id_or_slug(id_or_slug).first }

    before { content_type.save }

    describe 'unknown id' do

      it { should eq nil }

    end

    describe 'existing id' do

      let(:id_or_slug) { content_type._id.to_s }
      it { expect(subject.name).to eq 'My project' }

    end

    describe 'existing slug' do

      let(:id_or_slug) { 'my_project' }
      it { expect(subject.name).to eq 'My project' }

    end

  end

  it_should_behave_like 'model scoped by a site' do

    let(:model)         { build_content_type(slug: 'my_project') }
    let(:attribute)     { :content_version }

  end

  def build_content_type(options = {}, &block)
    build(:content_type, options).tap do |content_type|
      content_type.entries_custom_fields.build label: 'Name',        type: 'string'
      content_type.entries_custom_fields.build label: 'Description', type: 'text'
      content_type.entries_custom_fields.build label: 'Active',      type: 'boolean'
      content_type.entries_custom_fields.build label: 'Active at',   type: 'date'
      block.call(content_type) if block_given?
    end
  end

  def safe_find(klass, id)
    # Mongoid::IdentityMap.clear
    # Rails.logger.debug "---> reload #{klass}, #{id}!!!!!"
    klass.find(id)
  end

  def build_content_entry(content_type)
    content_type.entries.build(name: 'Asset on steroids', description: 'Lorem ipsum', active: true)
  end

  def build_belongs_to_has_many_relationship
    (@category_content_type = build_content_type(name: 'Categories')).save!
    category_klass = @category_content_type.klass_with_custom_fields(:entries).name

    @content_type = build_content_type.tap do |content_type|
      field = content_type.entries_custom_fields.build label: 'Category', type: 'belongs_to', class_name: category_klass
      content_type.order_by           = 'name'
      content_type.order_direction    = 'desc'
      content_type.group_by_field_id  = field._id
      content_type.save!
    end
    project_klass = @content_type.klass_with_custom_fields(:entries).name

    field = @category_content_type.entries_custom_fields.build label: 'Projects', type: 'has_many', class_name: project_klass, inverse_of: :category, ui_enabled: false
    @category_content_type.save!
  end

  def lookup_field_id(index)
    @content_type.entries_custom_fields.all[index].id.to_s
  end

end