describe GridService do
  it { should be_timestamped_document }
  it { should be_kind_of(EventStream) }
  it { should have_fields(:image_name, :name, :user, :entrypoint, :state,
                          :net, :log_driver, :pid, :stop_signal).of_type(String) }
  it { should have_fields(:container_count, :memory,
                          :memory_swap, :cpu_shares,
                          :revision, :stack_revision, :shm_size).of_type(Integer) }
  it { should have_fields(:affinity, :cmd, :ports, :env, :volumes_from,
                          :cap_add, :cap_drop).of_type(Array) }
  it { should have_fields(:labels, :log_opts).of_type(Hash) }
  it { should have_fields(:deploy_requested_at, :deployed_at).of_type(DateTime) }
  it { should have_fields(:privileged).of_type(Mongoid::Boolean) }

  it { should belong_to(:grid) }
  it { should belong_to(:stack) }
  it { should embed_many(:grid_service_links) }
  it { should embed_many(:secrets) }
  it { should embed_many(:hooks) }
  it { should embed_many(:service_volumes) }
  it { should embed_one(:deploy_opts) }
  it { should have_many(:containers) }
  it { should have_many(:container_logs) }
  it { should have_many(:container_stats) }
  it { should have_many(:audit_logs) }
  it { should have_many(:grid_service_instances) }
  it { should have_many(:grid_service_deploys) }
  it { should have_many(:event_logs) }

  it { should validate_presence_of(:name) }
  it { should validate_presence_of(:image_name) }
  it { should validate_presence_of(:grid_id) }
  it { should validate_presence_of(:stack_id) }

  it { should have_index_for(grid_id: 1) }
  it { should have_index_for(grid_service_ids: 1) }

  let(:grid) do
    Grid.create(name: 'test-grid')
  end
  let :stack do
    Stack.create(grid: grid, name: 'stack')
  end
  let :stack2 do
    Stack.create(grid: grid, name: 'stack2')
  end

  let(:grid_service) do
    GridService.create!(grid: grid, name: 'redis', image_name: 'redis:2.8')
  end
  let(:stack_service) do
    GridService.create!(grid: grid, stack: stack, name: 'redis', image_name: 'redis:2.8')
  end

  describe '#qualified_name' do
    it 'returns full path for stack_service service' do
      expect(stack_service.qualified_name).to eq('stack/redis')
    end

    it 'returns path without stack for stackless service' do
      expect(grid_service.qualified_name).to eq('redis')
    end
  end

  describe '#stateful?' do
    it 'returns true if stateful' do
      subject.stateful = true
      expect(subject.stateful?).to be_truthy
    end

    it 'returns false if not stateful' do
      subject.stateful = false
      expect(subject.stateful?).to be_falsey
    end
  end

  describe '#stateless?' do
    it 'returns true if stateless' do
      subject.stateful = false
      expect(subject.stateless?).to be_truthy
    end

    it 'returns false if not stateless' do
      subject.stateful = true
      expect(subject.stateless?).to be_falsey
    end
  end

  describe '#daemon?' do
    it 'returns true if strategy is daemon' do
      subject.strategy = 'daemon'
      expect(subject.daemon?).to be_truthy
    end

    it 'returns false if strategy is not daemon' do
      expect(subject.daemon?).to be_falsey
    end
  end

  describe '#running?' do
    it 'returns true if service is running' do
      subject.state = 'running'
      expect(subject.running?).to eq(true)
    end

    it 'returns false if service is not running' do
      subject.state = 'stopped'
      expect(subject.running?).to eq(false)
    end
  end

  describe '#stopped?' do
    it 'returns true if service is stopped' do
      subject.state = 'stopped'
      expect(subject.stopped?).to eq(true)
    end

    it 'returns false if service is not stopped' do
      subject.state = 'running'
      expect(subject.stopped?).to eq(false)
    end
  end

  describe '#set_state' do
    it 'sets value of state column' do
      grid_service.set_state('running')
      expect(grid_service.state).to eq('running')
    end

    it 'does not modify updated_at field' do
      five_hours_ago = Time.now.utc - 5.hours
      grid_service.timeless.update_attribute(:updated_at, five_hours_ago)
      grid_service.clear_timeless_option
      grid_service.set_state('running')
      expect(grid_service.updated_at).to eq(five_hours_ago)
    end

    it 'publishes update event' do
      expect(grid_service).to receive(:publish_update_event).once
      grid_service.set_state('running')
    end
  end

  describe '#container_by_name' do
    it 'returns related container by name' do
      container = grid_service.containers.create!(name: 'redis-1')
      expect(grid_service.container_by_name(container.name)).to eq(container)
    end

    it 'returns nil if container is not found' do
      expect(grid_service.container_by_name('not_found')).to be_nil
    end
  end

  describe '#linked_from_services' do
    it 'returns Mongoid::Criteria' do
      expect(grid_service.linked_from_services).to be_instance_of(Mongoid::Criteria)
    end

    it 'returns services that service has been linked from' do
      a = GridService.create!(
        grid: grid, name: 'aaa', image_name: 'aaa:latest',
        grid_service_links: [
          GridServiceLink.new(linked_grid_service_id: grid_service.id, alias: 'foo')
        ]
      )
      b = GridService.create!(
        grid: grid, name: 'bbb', image_name: 'bbb:latest',
        grid_service_links: [
          GridServiceLink.new(linked_grid_service_id: a.id, alias: 'a-foo')
        ]
      )
      expect(grid_service.linked_from_services.count).to eq(1)
      expect(grid_service.linked_from_services.first.id).to eq(a.id)
      expect(a.linked_from_services.count).to eq(1)
      expect(a.linked_from_services.first.id).to eq(b.id)
    end
  end

  describe '#load_balancer?' do
    it 'returns true if latest official kontena/lb image' do
      subject.image_name = 'kontena/lb:latest'
      expect(subject.load_balancer?).to eq(true)
    end

    it 'returns true if official kontena/lb image' do
      subject.image_name = 'kontena/lb:edge'
      expect(subject.load_balancer?).to eq(true)
    end

    it 'returns true if custom image with KONTENA_SERVICE_ROLE=lb env variable' do
      subject.image_name = 'custom/lb:latest'
      subject.env << 'KONTENA_SERVICE_ROLE=lb'
      expect(subject.load_balancer?).to eq(true)
    end

    it 'returns false if not official kontena/lb image' do
      subject.image_name = 'acme/lb:latest'
      expect(subject.load_balancer?).to eq(false)
    end

    it 'returns false by default' do
      expect(subject.load_balancer?).to eq(false)
    end
  end

  describe '#health_status' do
    it 'returns health status' do
      healthy_container = grid_service.containers.create!(name: 'redis-1')
      healthy_container.update_attribute(:health_status, 'healthy')
      unhealthy_container = grid_service.containers.create!(name: 'redis-2')
      unhealthy_container.update_attribute(:health_status, 'unhealthy')

      expect(grid_service.health_status).to eq({healthy: 1, unhealthy: 1, total: 2})
    end

    it 'returns nil if container is not found' do
      expect(grid_service.container_by_name('not_found')).to be_nil
    end
  end

  describe '#name_with_stack' do
    it 'returns stackless service name without stack' do
      expect(grid_service.name_with_stack).to eq 'redis'
    end
    it 'returns stack service name with stack' do
      expect(stack_service.name_with_stack).to eq 'stack.redis'
    end
  end

  describe '#stack_exposed?' do
    it 'returns true if service is exposed via stack' do
      stack = Stack.create!(name: 'redis')
      stack.stack_revisions.create(expose: 'redis')
      service = GridService.create!(grid: grid, name: 'redis', image_name: 'redis:2.8', stack: stack)
      expect(service.stack_exposed?).to be_truthy
    end

    it 'returns false if service is not exposed via stack' do
      expect(grid_service.stack_exposed?).to be_falsey
    end
  end

  describe '#affinity' do
    it 'returns empty array by default' do
      expect(subject.affinity).to eq([])
    end

    it 'returns service affinity if set' do
      subject.grid = grid
      subject.affinity = ['label==foo=bar']
      expect(subject.affinity).to eq(['label==foo=bar'])
    end

    it 'returns default affinity from grid if service affinity is not set' do
      grid.default_affinity = ['label!=type=ssd']
      subject.grid = grid
      expect(subject.affinity).to eq(['label!=type=ssd'])
    end
  end

  describe '#env_hash' do
    it 'should build a valid hash' do
      expect(subject).to receive(:env).and_return(
        [
          'FOO=bar',
          'FOO=BAR=bar',
          'BAR=',
          'DOG'
        ]
      )
      expect(subject.env_hash).to eq(
        {
          'FOO' => 'BAR=bar',
          'BAR' => '',
          'DOG' => nil
        }
      )
    end
  end

  describe '#deploying?' do
    it 'returns false when no deployments' do
      expect(subject.deploying?).to be_falsey
    end

    it 'returns true when queued deployments' do
      GridServiceDeploy.create!(grid_service: subject, started_at: nil, finished_at: nil)
      expect(subject.deploying?).to be_truthy
    end

    it 'returns true when un-finished deployments' do
      GridServiceDeploy.create!(grid_service: subject, started_at: 10.minutes.ago, finished_at: nil)
      expect(subject.deploying?).to be_truthy
    end

    it 'returns false when un-finished stale deployments' do
      GridServiceDeploy.create!(grid_service: subject, started_at: 32.minutes.ago, finished_at: nil)
      expect(subject.deploying?).to be_falsey
    end

    it 'returns false when finished deployments' do
      GridServiceDeploy.create!(grid_service: subject, started_at: 32.minutes.ago, finished_at: 26.minutes.ago)
      expect(subject.deploying?).to be_falsey
    end

    it 'returns false when finished deployments with no started_at' do
      GridServiceDeploy.create!(grid_service: subject, started_at: nil, finished_at: 26.minutes.ago)
      expect(subject.deploying?).to be_falsey
    end
  end
end
