<?php

namespace Drupal\Tests\lingotek\Unit\Remote;

use Drupal\Core\Config\Config;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\lingotek\Remote\LingotekHttp;
use Drupal\Tests\UnitTestCase;
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;

/**
 * @coversDefaultClass \Drupal\lingotek\Remote\LingotekHttp
 * @group lingotek
 * @preserveGlobalState disabled
 * @requires PHPUnit > 8
 * We depend on MockBuilder::addMethods() which was introduced in PHPUnit 8.
 */
class LingotekHttpUnitTest extends UnitTestCase {


  /**
   * @var \Drupal\lingotek\Remote\LingotekHttp
   */
  protected $lingotekHttp;

  /**
   * The HTTP client to interact with the Lingotek service.
   *
   * @var \GuzzleHttp\Client|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $httpClient;

  /**
   * @var \Drupal\Core\Config\Config|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $config;

  /**
   * @var \Drupal\Core\Config\Config|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $accountConfig;

  /**
   * @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $configFactory;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->httpClient = $this
      ->getMockBuilder(Client::class)
      ->onlyMethods(['get', 'post', 'patch', 'delete'])
      ->getMockForAbstractClass();

    $this->config = $this->createMock(Config::class);
    $this->accountConfig = $this->createMock(Config::class);

    $this->accountConfig->expects($this->any())
      ->method('get')
      ->will($this->returnValueMap([['host', 'http://example.com'], ['access_token', 'the_token']]));

    $this->configFactory = $this->createMock(ConfigFactoryInterface::class);
    $this->configFactory->expects($this->any())
      ->method('get')
      ->willReturnCallback(function ($argument) {
        switch ($argument) {
          case 'lingotek.settings':
            return $this->config;

          case 'lingotek.account':
            return $this->accountConfig;

          default:
            return NULL;
        }
      });
    $this->lingotekHttp = new LingotekHttp($this->httpClient, $this->configFactory);
  }

  /**
   * @covers ::get
   */
  public function testGet() {
    $response = $this->createMock(ResponseInterface::class);
    $this->httpClient->expects($this->once())
      ->method('get')
      ->with(
        $this->equalTo('http://example.com/test'),
        $this->equalTo([
            'headers' => [
                'Accept' => '*/*',
                'Authorization' => 'bearer the_token',
            ],
            'query' => [
                'one' => 'one_value',
            ],
        ])
    )->willReturn($response);
    $args = ['one' => 'one_value'];
    $this->lingotekHttp->get('/test', $args);
  }

  /**
   * @covers ::post
   */
  public function testPost() {
    $response = $this->createMock(ResponseInterface::class);

    $httpClientCalls = [
      [
          'url' => 'http://example.com/test',
          'options' => [
              'headers' => [
                  'Accept' => '*/*',
                  'Authorization' => 'bearer the_token',
              ],
              'multipart' => [
                  [
                      'name' => 'one',
                      'contents' => 'one_value',
                  ],
                  [
                      'name' => 'fruits',
                      'contents' => 'pear',
                  ],
                  [
                      'name' => 'fruits',
                      'contents' => 'lemon',
                  ],
              ],
          ],
      ],
      [
          'url' => 'http://example.com/test',
          'options' => [
              'headers' => [
                  'Accept' => '*/*',
                  'Authorization' => 'bearer the_token',
              ],
              'form_params' => [
                  'one' => 'one_value',
                  'fruits' => ['pear', 'lemon'],
              ],
          ],
      ],
    ];
    $callCount = 0;
    $this->httpClient->expects($this->exactly(count($httpClientCalls)))
      ->method('post')
      ->willReturnCallback(function ($url, $options) use ($response, $httpClientCalls, &$callCount) {
        $expectedCall = $httpClientCalls[$callCount];
        $callCount++;
        if ($expectedCall['url'] === $url && $expectedCall['options'] === $options) {
          return $response;
        }
        return NULL;
      });

    $args = ['one' => 'one_value', 'fruits' => ['pear', 'lemon']];
    $this->lingotekHttp->post('/test', $args, TRUE);
    $this->lingotekHttp->post('/test', $args, FALSE);
  }

  /**
   * @covers ::patch
   */
  public function testPatch() {
    $response = $this->createMock(ResponseInterface::class);

    $httpClientExpectations = [
      [
          'method' => 'patch',
          'arguments' => [
              'http://example.com/test',
              [
                  'headers' => [
                      'Accept' => '*/*',
                      'Authorization' => 'bearer the_token',
                      'X-HTTP-Method-Override' => 'PATCH',
                  ],
                  'multipart' => [
                      [
                          'name' => 'one',
                          'contents' => 'one_value',
                      ],
                      [
                          'name' => 'fruits',
                          'contents' => 'pear',
                      ],
                      [
                          'name' => 'fruits',
                          'contents' => 'lemon',
                      ],
                  ],
              ],
          ],
      ],
      [
          'method' => 'patch',
          'arguments' => [
              'http://example.com/test',
              [
                  'headers' => [
                      'Accept' => '*/*',
                      'Authorization' => 'bearer the_token',
                      'X-HTTP-Method-Override' => 'PATCH',
                  ],
                  'form_params' => [
                      'one' => 'one_value',
                      'fruits' => ['pear', 'lemon'],
                  ],
              ],
          ],
      ],
    ];
    $callCount = 0;
    $this->httpClient->expects($this->exactly(count($httpClientExpectations)))
      ->method('patch')
      ->willReturnCallback(function () use ($httpClientExpectations, &$callCount, $response) {
        $call = $httpClientExpectations[$callCount];
        $callCount++;
        if ($callCount <= count($httpClientExpectations)) {
          return $response;
        }
        return NULL;
      });

    $args = ['one' => 'one_value', 'fruits' => ['pear', 'lemon']];
    $this->lingotekHttp->patch('/test', $args, TRUE);
    $this->lingotekHttp->patch('/test', $args, FALSE);
  }

  /**
   * @covers ::delete
   */
  public function testDelete() {
    $response = $this->createMock(ResponseInterface::class);

    $expectedUrl = 'http://example.com/test';
    $expectedOptions = [
    'headers' => [
        'Accept' => '*/*',
        'Authorization' => 'bearer the_token',
        'X-HTTP-Method-Override' => 'DELETE',
      ],
    'query' => [
        'one' => 'one_value',
      ],
    ];

    $this->httpClient->expects($this->exactly(1))
      ->method('delete')
      ->willReturnCallback(function ($url, $options) use ($response, $expectedUrl, $expectedOptions) {
        if ($url === $expectedUrl && $options === $expectedOptions) {
          return $response;
        }
        return NULL;
      });

    $args = ['one' => 'one_value'];
    $this->lingotekHttp->delete('/test', $args);
  }

  /**
   * @covers ::getCurrentToken
   */
  public function testGetCurrentToken() {
    $value = $this->lingotekHttp->getCurrentToken();
    $this->assertEquals('the_token', $value);
  }

}
