<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the MIT license.
 */

namespace ZfrPusherTest\Service;

use Guzzle\Common\Collection;
use Guzzle\Http\Exception\BadResponseException;
use Guzzle\Http\Message\Response;
use Guzzle\Service\ClientInterface;
use PHPUnit_Framework_TestCase;
use PHPUnit_Framework_MockObject_MockObject;
use ReflectionMethod;
use ReflectionProperty;
use Symfony\Component\EventDispatcher\EventDispatcher;
use ZfrPusher\Service\PusherService;

class PusherServiceTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var PusherService
     */
    protected $service;

    /**
     * @var ClientInterface|PHPUnit_Framework_MockObject_MockObject
     */
    protected $client;

    public function setUp()
    {
        $methodsToMock = array('trigger', 'getChannelsInfo', 'getChannelInfo', 'getPresenceUsers',
                               'addSubscriber', 'removeSubscriber');

        $this->client  = $this->getMock('ZfrPusher\Client\PusherClient', $methodsToMock, array(), '', false);
        $this->service = new PusherService($this->client);

        $this->client->expects($this->any())
                     ->method('getEventDispatcher')
                     ->will($this->returnValue(new EventDispatcher()));
    }

    /**
     * @covers PusherService::trigger
     */
    public function testAsyncPluginCanBeAdded()
    {
        $property = new ReflectionProperty('ZfrPusher\Service\PusherService', 'asyncPlugin');
        $property->setAccessible(true);

        $this->client->expects($this->once())
                     ->method('addSubscriber')
                     ->with($property->getValue($this->service));

        $this->service->trigger('my-channel', 'my-event', array(), '', true);
    }

    /**
     * @covers PusherService::trigger
     */
    public function testTriggerDoesNotAddAsyncPluginByDefault()
    {
        $this->client->expects($this->never())
                     ->method('addSubscriber');

        $this->service->trigger('my-channel', 'my-event', array(), '');
    }

    /**
     * @covers PusherService::triggerAsync
     */
    public function testTriggerAsyncAddAsyncPlugin()
    {
        $property = new ReflectionProperty('ZfrPusher\Service\PusherService', 'asyncPlugin');
        $property->setAccessible(true);

        $this->client->expects($this->once())
                     ->method('addSubscriber')
                     ->with($property->getValue($this->service));

        $this->service->triggerAsync('my-channel', 'my-event', array(), '');
    }

    /**
     * @covers PusherService::trigger
     */
    public function testTriggerWithSocketIdEmpty()
    {
        $expectedParameters = array(
            'event'     => 'my-event',
            'channel'   => 'my-channel',
            'data'      => array()
        );

        $this->client->expects($this->once())
                     ->method('trigger')
                     ->with($expectedParameters);

        $this->service->trigger('my-channel', 'my-event', array(), '');
    }

    /**
     * @covers PusherService::trigger
     */
    public function testTriggerWithSocketId()
    {
        $expectedParameters = array(
            'event'      => 'my-event',
            'channel'    => 'my-channel',
            'data'       => array(),
            'socket_id'  => '123.123'
        );

        $this->client->expects($this->once())
                     ->method('trigger')
                     ->with($expectedParameters);

        $this->service->trigger('my-channel', 'my-event', array(), '123.123');
    }

    /**
     * @covers PusherService::trigger
     */
    public function testTriggerUseChannelIfString()
    {
        $expectedParameters = array(
            'event'     => 'my-event',
            'channel'   => 'my-channel',
            'data'      => array()
        );

        $this->client->expects($this->once())
                     ->method('trigger')
                     ->with($expectedParameters);

        $this->service->trigger('my-channel', 'my-event', array());
    }

    /**
     * @covers PusherService::trigger
     */
    public function testTriggerUseChannelsIfArray()
    {
        $expectedParameters = array(
            'event'     => 'my-event',
            'channels'  => array('my-channel-1', 'my-channel-2'),
            'data'      => array()
        );

        $this->client->expects($this->once())
                     ->method('trigger')
                     ->with($expectedParameters);

        $this->service->trigger(array('my-channel-1', 'my-channel-2'), 'my-event', array());
    }

    /**
     * @covers PusherService::getChannelsInfo
     */
    public function testFormatParametersWhenGetChannelsInfo()
    {
        $expectedParameters = array(
            'prefix' => 'presence-',
            'info'   => array('user_count')
        );

        $this->client->expects($this->once())
                     ->method('getChannelsInfo')
                     ->with($expectedParameters)
                     ->will($this->returnValue(new Collection()));

        $this->service->getChannelsInfo('presence-', array('user_count'));
    }

    /**
     * @covers PusherService::getChannelInfo
     */
    public function testFormatParametersWhenGetChannelInfo()
    {
        $expectedParameters = array(
            'channel' => 'private-foobar',
            'info'    => array('user_count')
        );

        $this->client->expects($this->once())
                     ->method('getChannelInfo')
                     ->with($expectedParameters)
                     ->will($this->returnValue(new Collection()));

        $this->service->getChannelInfo('private-foobar', array('user_count'));
    }

    /**
     * @covers PusherService::getPresenceUsers
     */
    public function testFormatParametersWhenGetPresenceUsers()
    {
        $expectedParameters = array(
            'channel' => 'presence-foobar'
        );

        $this->client->expects($this->once())
                     ->method('getPresenceUsers')
                     ->with($expectedParameters)
                     ->will($this->returnValue(new Collection()));

        $this->service->getPresenceUsers('presence-foobar');
    }

    public function exceptionDataProvider()
    {
        return array(
            array(200, null),
            array(400, 'ZfrPusher\Service\Exception\RuntimeException'),
            array(401, 'ZfrPusher\Service\Exception\UnauthorizedException'),
            array(403, 'ZfrPusher\Service\Exception\ForbiddenException'),
            array(404, 'ZfrPusher\Service\Exception\UnknownResourceException'),
            array(500, 'ZfrPusher\Service\Exception\RuntimeException'),
        );
    }

    /**
     * @covers PusherService::handleException
     * @dataProvider exceptionDataProvider
     */
    public function testExceptionsAreThrownOnErrors($statusCode, $expectedException)
    {
        $method = new ReflectionMethod('ZfrPusher\Service\PusherService', 'handleException');
        $method->setAccessible(true);

        $exception = new BadResponseException();
        $exception->setResponse(new Response($statusCode));

        $this->setExpectedException($expectedException);

        $method->invoke($this->service, $exception);
    }
}
