<?php

namespace Tests\Unit\Modules\Lemmy;

use App\Modules\Lemmy\LemmyRequest;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;

class LemmyRequestTest extends TestCase
{
    public function test_constructor_with_simple_domain(): void
    {
        $request = new LemmyRequest('lemmy.world');

        $this->assertEquals('https', $this->getPrivateProperty($request, 'scheme'));
        $this->assertEquals('lemmy.world', $this->getPrivateProperty($request, 'instance'));
        $this->assertNull($this->getPrivateProperty($request, 'token'));
    }

    public function test_constructor_with_https_url(): void
    {
        $request = new LemmyRequest('https://lemmy.world');

        $this->assertEquals('https', $this->getPrivateProperty($request, 'scheme'));
        $this->assertEquals('lemmy.world', $this->getPrivateProperty($request, 'instance'));
    }

    public function test_constructor_with_http_url(): void
    {
        $request = new LemmyRequest('http://lemmy.world');

        $this->assertEquals('http', $this->getPrivateProperty($request, 'scheme'));
        $this->assertEquals('lemmy.world', $this->getPrivateProperty($request, 'instance'));
    }

    public function test_constructor_with_trailing_slash(): void
    {
        $request = new LemmyRequest('lemmy.world/');

        $this->assertEquals('lemmy.world', $this->getPrivateProperty($request, 'instance'));
    }

    public function test_constructor_with_full_url_and_trailing_slash(): void
    {
        $request = new LemmyRequest('https://lemmy.world/');

        $this->assertEquals('https', $this->getPrivateProperty($request, 'scheme'));
        $this->assertEquals('lemmy.world', $this->getPrivateProperty($request, 'instance'));
    }

    public function test_constructor_with_token(): void
    {
        $request = new LemmyRequest('lemmy.world', 'test-token');

        $this->assertEquals('test-token', $this->getPrivateProperty($request, 'token'));
    }

    public function test_constructor_preserves_case_in_scheme_detection(): void
    {
        $request = new LemmyRequest('HTTPS://lemmy.world');

        $this->assertEquals('https', $this->getPrivateProperty($request, 'scheme'));
    }

    public function test_with_scheme_sets_https(): void
    {
        $request = new LemmyRequest('lemmy.world');
        $result = $request->withScheme('https');

        $this->assertSame($request, $result);
        $this->assertEquals('https', $this->getPrivateProperty($request, 'scheme'));
    }

    public function test_with_scheme_sets_http(): void
    {
        $request = new LemmyRequest('lemmy.world');
        $result = $request->withScheme('http');

        $this->assertSame($request, $result);
        $this->assertEquals('http', $this->getPrivateProperty($request, 'scheme'));
    }

    public function test_with_scheme_normalizes_case(): void
    {
        $request = new LemmyRequest('lemmy.world');
        $request->withScheme('HTTPS');

        $this->assertEquals('https', $this->getPrivateProperty($request, 'scheme'));
    }

    public function test_with_scheme_ignores_invalid_schemes(): void
    {
        $request = new LemmyRequest('lemmy.world');
        $originalScheme = $this->getPrivateProperty($request, 'scheme');

        $request->withScheme('ftp');

        $this->assertEquals($originalScheme, $this->getPrivateProperty($request, 'scheme'));
    }

    public function test_with_token_sets_token(): void
    {
        $request = new LemmyRequest('lemmy.world');
        $result = $request->withToken('new-token');

        $this->assertSame($request, $result);
        $this->assertEquals('new-token', $this->getPrivateProperty($request, 'token'));
    }

    public function test_get_without_token(): void
    {
        Http::fake(['*' => Http::response(['success' => true])]);

        $request = new LemmyRequest('lemmy.world');
        $response = $request->get('site');

        $this->assertInstanceOf(Response::class, $response);

        Http::assertSent(function ($httpRequest) {
            return $httpRequest->url() === 'https://lemmy.world/api/v3/site'
                && ! $httpRequest->hasHeader('Authorization');
        });
    }

    public function test_get_with_token(): void
    {
        Http::fake(['*' => Http::response(['success' => true])]);

        $request = new LemmyRequest('lemmy.world', 'test-token');
        $response = $request->get('site');

        $this->assertInstanceOf(Response::class, $response);

        Http::assertSent(function ($httpRequest) {
            return $httpRequest->url() === 'https://lemmy.world/api/v3/site'
                && $httpRequest->header('Authorization')[0] === 'Bearer test-token';
        });
    }

    public function test_get_with_parameters(): void
    {
        Http::fake(['*' => Http::response(['success' => true])]);

        $request = new LemmyRequest('lemmy.world');
        $params = ['limit' => 10, 'page' => 1];
        $response = $request->get('posts', $params);

        $this->assertInstanceOf(Response::class, $response);

        Http::assertSent(function ($httpRequest) {
            $url = $httpRequest->url();

            return str_contains($url, 'https://lemmy.world/api/v3/posts')
                && str_contains($url, 'limit=10')
                && str_contains($url, 'page=1');
        });
    }

    public function test_get_with_http_scheme(): void
    {
        Http::fake(['*' => Http::response(['success' => true])]);

        $request = new LemmyRequest('lemmy.world');
        $request->withScheme('http');
        $response = $request->get('site');

        $this->assertInstanceOf(Response::class, $response);

        Http::assertSent(function ($httpRequest) {
            return $httpRequest->url() === 'http://lemmy.world/api/v3/site';
        });
    }

    public function test_post_without_token(): void
    {
        Http::fake(['*' => Http::response(['success' => true])]);

        $request = new LemmyRequest('lemmy.world');
        $response = $request->post('login');

        $this->assertInstanceOf(Response::class, $response);

        Http::assertSent(function ($httpRequest) {
            return $httpRequest->url() === 'https://lemmy.world/api/v3/login'
                && $httpRequest->method() === 'POST'
                && ! $httpRequest->hasHeader('Authorization');
        });
    }

    public function test_post_with_token(): void
    {
        Http::fake(['*' => Http::response(['success' => true])]);

        $request = new LemmyRequest('lemmy.world', 'test-token');
        $response = $request->post('login');

        $this->assertInstanceOf(Response::class, $response);

        Http::assertSent(function ($httpRequest) {
            return $httpRequest->url() === 'https://lemmy.world/api/v3/login'
                && $httpRequest->method() === 'POST'
                && $httpRequest->header('Authorization')[0] === 'Bearer test-token';
        });
    }

    public function test_post_with_data(): void
    {
        Http::fake(['*' => Http::response(['success' => true])]);

        $request = new LemmyRequest('lemmy.world');
        $data = ['username' => 'test', 'password' => 'pass'];
        $response = $request->post('login', $data);

        $this->assertInstanceOf(Response::class, $response);

        Http::assertSent(function ($httpRequest) use ($data) {
            return $httpRequest->url() === 'https://lemmy.world/api/v3/login'
                && $httpRequest->method() === 'POST'
                && $httpRequest->data() === $data;
        });
    }

    public function test_post_with_http_scheme(): void
    {
        Http::fake(['*' => Http::response(['success' => true])]);

        $request = new LemmyRequest('lemmy.world');
        $request->withScheme('http');
        $response = $request->post('login');

        $this->assertInstanceOf(Response::class, $response);

        Http::assertSent(function ($httpRequest) {
            return $httpRequest->url() === 'http://lemmy.world/api/v3/login';
        });
    }

    public function test_requests_use_30_second_timeout(): void
    {
        Http::fake(['*' => Http::response(['success' => true])]);

        $request = new LemmyRequest('lemmy.world');
        $request->get('site');

        Http::assertSent(function ($httpRequest) {
            return $httpRequest->url() === 'https://lemmy.world/api/v3/site';
        });
    }

    public function test_chaining_methods(): void
    {
        Http::fake(['*' => Http::response(['success' => true])]);

        $request = new LemmyRequest('lemmy.world');
        $response = $request->withScheme('http')->withToken('chained-token')->get('site');

        $this->assertInstanceOf(Response::class, $response);

        Http::assertSent(function ($httpRequest) {
            return $httpRequest->url() === 'http://lemmy.world/api/v3/site'
                && $httpRequest->header('Authorization')[0] === 'Bearer chained-token';
        });
    }

    public function test_post_multipart_targets_a_root_relative_path(): void
    {
        Http::fake(['*' => Http::response(['files' => []])]);

        $request = new LemmyRequest('lemmy.world', 'test-token');
        $response = $request->postMultipart('pictrs/image', 'images[]', 'binary-data', 'thumb.jpg');

        $this->assertInstanceOf(Response::class, $response);

        Http::assertSent(function ($httpRequest) {
            return $httpRequest->url() === 'https://lemmy.world/pictrs/image'
                && $httpRequest->header('Authorization')[0] === 'Bearer test-token';
        });
    }

    public function test_post_multipart_does_not_use_the_api_prefix(): void
    {
        Http::fake(['*' => Http::response(['files' => []])]);

        (new LemmyRequest('lemmy.world'))->postMultipart('/pictrs/image', 'images[]', 'data', 'thumb.jpg');

        Http::assertSent(fn ($httpRequest) => ! str_contains($httpRequest->url(), '/api/v3/'));
    }

    public function test_post_multipart_sends_the_file_as_multipart_data(): void
    {
        Http::fake(['*' => Http::response(['files' => []])]);

        (new LemmyRequest('lemmy.world'))->postMultipart('pictrs/image', 'images[]', 'binary-data', 'thumb.jpg');

        Http::assertSent(function ($httpRequest) {
            return str_contains($httpRequest->header('Content-Type')[0], 'multipart/form-data')
                && str_contains($httpRequest->body(), 'binary-data')
                && str_contains($httpRequest->body(), 'thumb.jpg');
        });
    }

    public function test_post_multipart_omits_authorization_without_a_token(): void
    {
        Http::fake(['*' => Http::response(['files' => []])]);

        (new LemmyRequest('lemmy.world'))->postMultipart('pictrs/image', 'images[]', 'data', 'thumb.jpg');

        Http::assertSent(fn ($httpRequest) => ! $httpRequest->hasHeader('Authorization'));
    }

    public function test_post_multipart_respects_the_scheme(): void
    {
        Http::fake(['*' => Http::response(['files' => []])]);

        (new LemmyRequest('http://lemmy.world'))->postMultipart('pictrs/image', 'images[]', 'data', 'thumb.jpg');

        Http::assertSent(fn ($httpRequest) => $httpRequest->url() === 'http://lemmy.world/pictrs/image');
    }

    private function getPrivateProperty(object $object, string $property): mixed
    {
        $reflection = new \ReflectionClass($object);
        $reflectionProperty = $reflection->getProperty($property);
        $reflectionProperty->setAccessible(true);

        return $reflectionProperty->getValue($object);
    }
}
