<?php
namespace Jdcloud\Test;

use Jdcloud\JdcloudClientInterface;
use Jdcloud\Exception\JdcloudException;
use Jdcloud\MockHandler;
use Jdcloud\Result;
use Jdcloud\Sdk;
use Jdcloud\Api\Service;

/**
 * @internal
 */
trait UsesServiceTrait
{
    private $_mock_handler;

    /**
     * Creates an instance of the Jdcloud SDK for a test
     *
     * @param array $args
     *
     * @return Sdk
     */
    private function getTestSdk(array $args = [])
    {
        return new Sdk($args + [
            'region'      => 'cn-north-1',
            'version'     => 'latest',
            'retries'     => 0
        ]);
    }

    /**
     * Creates an instance of a service client for a test
     *
     * @param string $service
     * @param array  $args
     *
     * @return JdcloudClientInterface
     */
    private function getTestClient($service, array $args = [])
    {
        // Disable network access. If the INTEGRATION envvar is set, then this
        // disabling is not done.
        if (!isset($_SERVER['INTEGRATION'])
            && !isset($args['handler'])
            && !isset($args['http_handler'])
        ) {
            $this->_mock_handler = $args['handler'] = new MockHandler([]);
        }

        return $this->getTestSdk($args)->createClient($service);
    }

    /**
     * Queues up mock Result objects for a client
     *
     * @param JdcloudClientInterface $client
     * @param Result[]|array[]   $results
     * @param callable $onFulfilled Callback to invoke when the return value is fulfilled.
     * @param callable $onRejected  Callback to invoke when the return value is rejected.
     *
     * @return JdcloudClientInterface
     */
    private function addMockResults(
        JdcloudClientInterface $client,
        array $results,
        callable $onFulfilled = null,
        callable $onRejected = null
    ) {
        foreach ($results as &$res) {
            if (is_array($res)) {
                $res = new Result($res);
            }
        }

        $this->_mock_handler = new MockHandler($results, $onFulfilled, $onRejected);
        $client->getHandlerList()->setHandler($this->_mock_handler);

        return $client;
    }

    private function mockQueueEmpty()
    {
        return 0 === count($this->_mock_handler);
    }

    /**
     * Creates a mock CommandException with a given error code
     *
     * @param string $code
     * @param string $type
     * @param string|null $message
     *
     * @return JdcloudException
     */
    private function createMockJdcloudException(
        $code = null,
        $type = null,
        $message = null
    ) {
        $code = $code ?: 'ERROR';
        $type = $type ?: 'Jdcloud\Exception\JdcloudException';

        $client = $this->getMockBuilder('Jdcloud\JdcloudClientInterface')
            ->setMethods(['getApi'])
            ->getMockForAbstractClass();

        $client->expects($this->any())
            ->method('getApi')
            ->will($this->returnValue(
                new Service(
                    [
                        'metadata' => [
                            'endpointPrefix' => 'foo',
                            'apiVersion' => 'version'
                        ]
                    ],
                    function () { return []; }
                )));

        return new $type(
            $message ?: 'Test error',
            $this->getMockBuilder('Jdcloud\CommandInterface')->getMock(),
            [
                'message' => $message ?: 'Test error',
                'code'    => $code
            ]
        );
    }

    /**
     * Verifies an operation alias returns the expected types
     *
     * @param JdcloudClientInterface $client
     * @param string $operation
     * @param array $params
     */
    private function verifyOperationAlias(
        $client,
        $operation,
        $params
    ) {
        $this->addMockResults($client, [new Result()]);
        $output = $client->{$operation}($params);
        if (substr($operation, -5) === 'Async') {
            $this->assertFalse($this->mockQueueEmpty());
            $this->assertInstanceOf('GuzzleHttp\\Promise\\PromiseInterface', $output);
            $output = $output->wait();
            $this->assertTrue($this->mockQueueEmpty());
        }
        $this->assertInstanceOf(Result::class, $output);
        $this->assertTrue($this->mockQueueEmpty());
    }
}
