<?php declare(strict_types=1);

use Lewisdale\Webmentions\Endpoint;
use Lewisdale\Webmentions\Exceptions\InvalidTargetException;
use Lewisdale\Webmentions\Exceptions\InvalidUrlException;
use Lewisdale\Webmentions\Exceptions\SourceNotFoundException;
use Lewisdale\Webmentions\Exceptions\TargetNotMentionedException;
use Lewisdale\Webmentions\Gateways\WebmentionGatewayInterface;
use Lewisdale\Webmentions\Models\MentionType;
use Lewisdale\Webmentions\Models\Webmention;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\TestWith;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;

class EndpointTest extends TestCase {
    private function objectContains(string $key, mixed $value) {
        return $this->callback(fn(object $obj) => $obj->$key === $value);
    }

    #[TestWith(["https://my.url.com", true])]
    #[TestWith(["my.url.com", false])]
    public function testValidatesUrls(string $url, bool $expected)
    {
        $endpoint = new Endpoint($this->createMock(HttpClientInterface::class), $this->createMock(WebmentionGatewayInterface::class));
        $this->assertEquals($expected, $endpoint->validateUrl($url), "Expected $url");
    }

    #[TestWith(["https://my-valid-source.url", "htt://my-invalid-target.com"])]
    #[TestWith(["my-invalid-source", "https://my-valid-target.com"])]
    #[TestWith(["http:///an-invalid-source", "an-invalid-target"])]
    public function testThrowsInvalidUrlExceptionIfTheUrlIsInvalid(string $source, string $target)
    {
        $this->expectException(InvalidUrlException::class);
        $endpoint = new Endpoint($this->createMock(HttpClientInterface::class), $this->createMock(WebmentionGatewayInterface::class));
        $endpoint->receiveWebmention($source, $target);
    }

    public function testThrowsInvalidTargetExceptionIfTheTargetUrlIsNotOnCurrentSite()
    {
        $this->expectException(InvalidTargetException::class);
        $endpoint = new Endpoint($this->createMock(HttpClientInterface::class), $this->createMock(WebmentionGatewayInterface::class));
        $endpoint->receiveWebmention("https://a-valid-source.url", "https://not-my-site.com");
    }

    #[TestWith([404])]
    #[TestWith([401])]
    #[TestWith([500])]
    #[TestWith([501])]
    #[TestWith([502])]
    #[TestWith([503])]
    #[TestWith([500])]
    public function testItShouldThrowASourceNotFoundExceptionIfTheSourceUrlIsUnreachable(int $statusCode)
    {
        $source = "https://my-valid-source-url.com";
        $target = "https://lewisdale.dev/post/a-post-page";

        $mockClient = $this->createMock(HttpClientInterface::class);
        $mockResponse = $this->createMock(ResponseInterface::class);

        $mockClient->expects($this->once())
            ->method('request')
            ->with($this->identicalTo('GET'), $this->identicalTo($source))
            ->will($this->returnValue($mockResponse));

        $mockResponse->method('getStatusCode')
            ->will($this->returnValue($statusCode));

        $this->expectException(SourceNotFoundException::class);

        $endpoint = new Endpoint($mockClient, $this->createMock(WebmentionGatewayInterface::class));
        $endpoint->receiveWebmention($source, $target);
    }

    public function testItShouldThrowATargetNotMentionedErrorIfTheSourceDoesNotMentionTheTarget()
    {
        $source = "https://my-valid-source-url.com";
        $target = "https://lewisdale.dev/post/a-post-page";

        $content = <<<XML
        <html>
            <head>
            </head>
            <body>
                <h1>Some content</h1>
                <p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
                <p>But it does not mention the target.</p>
            </body>
        </html>
        XML;

        $mockClient = $this->createMock(HttpClientInterface::class);
        $mockResponse = $this->createMock(ResponseInterface::class);

        $mockClient->expects($this->once())
            ->method('request')
            ->with($this->identicalTo('GET'), $this->identicalTo($source))
            ->will($this->returnValue($mockResponse));

        $mockResponse->method('getStatusCode')
            ->will($this->returnValue(200));

        $mockResponse->method('getContent')
            ->willReturn($content);

        $this->expectException(TargetNotMentionedException::class);

        $endpoint = new Endpoint($mockClient, $this->createMock(WebmentionGatewayInterface::class));
        $endpoint->receiveWebmention($source, $target);
    }

    public function testItShouldParseAWebmentionAsALikeIfItHasTheCorrectMicroFormat()
    {
        $source = "https://my-valid-source-url.com";
        $target = "https://lewisdale.dev/post/a-post-page";

        $content = <<<XML
        <html>
            <head>
            </head>
            <body>
                <h1>Some content</h1>
                <p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
                <p>I'm liking <a href="$target" class="u-like-of">this post</a>.</p>
            </body>
        </html>
        XML;

        $mockClient = $this->createMock(HttpClientInterface::class);
        $mockResponse = $this->createMock(ResponseInterface::class);
        $mockGateway = $this->createMock(WebmentionGatewayInterface::class);

        $mockClient->expects($this->once())
            ->method('request')
            ->with($this->identicalTo('GET'), $this->identicalTo($source))
            ->will($this->returnValue($mockResponse));

        $mockResponse->method('getStatusCode')
            ->will($this->returnValue(200));

        $mockResponse->method('getContent')
            ->willReturn($content);

        $mockGateway->expects($this->once())
            ->method('save')
            ->with($this->objectContains('type', MentionType::Like));

        $endpoint = new Endpoint($mockClient, $mockGateway);
     
        $endpoint->receiveWebmention($source, $target);
    }

    public function testItShouldParseAWebmentionAsAMentionIfItHasNoMicroformat()
    {
        $source = "https://my-valid-source-url.com";
        $target = "https://lewisdale.dev/post/a-post-page";

        $content = <<<XML
        <html>
            <head>
            </head>
            <body>
                <h1>Some content</h1>
                <p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
                <p>I'm writing about <a href="$target" class="my-link">this post</a>.</p>
            </body>
        </html>
        XML;

        $mockClient = $this->createMock(HttpClientInterface::class);
        $mockResponse = $this->createMock(ResponseInterface::class);
        $mockGateway = $this->createMock(WebmentionGatewayInterface::class);

        $mockClient->expects($this->once())
            ->method('request')
            ->with($this->identicalTo('GET'), $this->identicalTo($source))
            ->will($this->returnValue($mockResponse));

        $mockResponse->method('getStatusCode')
            ->will($this->returnValue(200));

        $mockResponse->method('getContent')
            ->willReturn($content);

        $mockGateway->expects($this->once())
            ->method('save')
            ->with($this->objectContains('type', MentionType::Mention));

        $endpoint = new Endpoint($mockClient, $mockGateway);
        $endpoint->receiveWebmention($source, $target);
    }

    public function testItShouldParseAWebmentionAsAReplyIfItHasTheCorrectMicroFormat()
    {
        $source = "https://my-valid-source-url.com";
        $target = "https://lewisdale.dev/post/a-post-page";

        $content = <<<XML
        <html>
            <head>
            </head>
            <body>
                <h1>Some content</h1>
                <p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
                <p>I'm writing about <a href="$target" class="u-in-reply-to">this post</a>.</p>
            </body>
        </html>
        XML;

        $mockClient = $this->createMock(HttpClientInterface::class);
        $mockResponse = $this->createMock(ResponseInterface::class);
        $mockGateway = $this->createMock(WebmentionGatewayInterface::class);

        $mockClient->expects($this->once())
            ->method('request')
            ->with($this->identicalTo('GET'), $this->identicalTo($source))
            ->will($this->returnValue($mockResponse));

        $mockResponse->method('getStatusCode')
            ->will($this->returnValue(200));

        $mockResponse->method('getContent')
            ->willReturn($content);

        $mockGateway->expects($this->once())
            ->method('save')
            ->with($this->objectContains('type', MentionType::Reply));

        $endpoint = new Endpoint($mockClient, $mockGateway);
        $endpoint->receiveWebmention($source, $target);
    }

    public function testItShouldParseAWebmentionAsARepost()
    {
        $source = "https://my-valid-source-url.com";
        $target = "https://lewisdale.dev/post/a-post-page";

        $content = <<<XML
        <html>
            <head>
            </head>
            <body>
                <h1>Some content</h1>
                <p>Here's some body content. It <a href="/another/page">contains a url</a>.</p>
                <p>I'm writing about <a href="$target" class="u-repost-of">this post</a>.</p>
            </body>
        </html>
        XML;

        $mockClient = $this->createMock(HttpClientInterface::class);
        $mockResponse = $this->createMock(ResponseInterface::class);
        $mockGateway = $this->createMock(WebmentionGatewayInterface::class);

        $mockClient->expects($this->once())
            ->method('request')
            ->with($this->identicalTo('GET'), $this->identicalTo($source))
            ->will($this->returnValue($mockResponse));

        $mockResponse->method('getStatusCode')
            ->will($this->returnValue(200));

        $mockResponse->method('getContent')
            ->willReturn($content);

        $mockGateway->expects($this->once())
            ->method('save')
            ->with($this->objectContains('type', MentionType::Repost));
                
        $endpoint = new Endpoint($mockClient, $mockGateway);
        $endpoint->receiveWebmention($source, $target);
    }
}
?>