<?php

declare(strict_types=1);

namespace ShlinkioTest\Shlink\Rest\Middleware\ShortUrl;

use Laminas\Diactoros\Response;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\ServerRequest;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Shlinkio\Shlink\Rest\Middleware\ShortUrl\CreateShortUrlContentNegotiationMiddleware;

class CreateShortUrlContentNegotiationMiddlewareTest extends TestCase
{
    use ProphecyTrait;

    private CreateShortUrlContentNegotiationMiddleware $middleware;
    private ObjectProphecy $requestHandler;

    public function setUp(): void
    {
        $this->middleware = new CreateShortUrlContentNegotiationMiddleware();
        $this->requestHandler = $this->prophesize(RequestHandlerInterface::class);
    }

    /** @test */
    public function whenNoJsonResponseIsReturnedNoFurtherOperationsArePerformed(): void
    {
        $expectedResp = new Response();
        $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn($expectedResp);

        $resp = $this->middleware->process(new ServerRequest(), $this->requestHandler->reveal());

        self::assertSame($expectedResp, $resp);
    }

    /**
     * @test
     * @dataProvider provideData
     * @param array $query
     */
    public function properResponseIsReturned(?string $accept, array $query, string $expectedContentType): void
    {
        $request = (new ServerRequest())->withQueryParams($query);
        if ($accept !== null) {
            $request = $request->withHeader('Accept', $accept);
        }

        $handle = $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn(
            new JsonResponse(['shortUrl' => 'http://doma.in/foo']),
        );

        $response = $this->middleware->process($request, $this->requestHandler->reveal());

        self::assertEquals($expectedContentType, $response->getHeaderLine('Content-type'));
        $handle->shouldHaveBeenCalled();
    }

    public function provideData(): iterable
    {
        yield [null, [], 'application/json'];
        yield [null, ['format' => 'json'], 'application/json'];
        yield [null, ['format' => 'invalid'], 'application/json'];
        yield [null, ['format' => 'txt'], 'text/plain'];
        yield ['application/json', [], 'application/json'];
        yield ['application/xml', [], 'application/json'];
        yield ['text/plain', [], 'text/plain'];
        yield ['application/json', ['format' => 'txt'], 'text/plain'];
    }

    /**
     * @test
     * @dataProvider provideTextBodies
     * @param array $json
     */
    public function properBodyIsReturnedInPlainTextResponses(array $json, string $expectedBody): void
    {
        $request = (new ServerRequest())->withQueryParams(['format' => 'txt']);

        $handle = $this->requestHandler->handle(Argument::type(ServerRequestInterface::class))->willReturn(
            new JsonResponse($json),
        );

        $response = $this->middleware->process($request, $this->requestHandler->reveal());

        self::assertEquals($expectedBody, (string) $response->getBody());
        $handle->shouldHaveBeenCalled();
    }

    public function provideTextBodies(): iterable
    {
        yield 'shortUrl key' => [['shortUrl' => 'foobar'], 'foobar'];
        yield 'error key' => [['error' => 'FOO_BAR'], 'FOO_BAR'];
        yield 'no shortUrl or error keys' => [[], ''];
    }
}