<?php

declare(strict_types=1);

use App\Framework\Http\Url\Rfc3986Url;
use App\Framework\Http\Url\UrlSpec;

describe('Rfc3986Url', function () {
    describe('parsing', function () {
        it('parses basic HTTP URLs', function () {
            $url = Rfc3986Url::parse('https://example.com/path');

            expect($url->getScheme())->toBe('https');
            expect($url->getHost())->toBe('example.com');
            expect($url->getPath())->toBe('/path');
        });

        it('parses URLs with all components', function () {
            $url = Rfc3986Url::parse('https://user:pass@example.com:8080/path?query=1#fragment');

            expect($url->getScheme())->toBe('https');
            expect($url->getUserInfo())->toBe('user:pass');
            expect($url->getHost())->toBe('example.com');
            expect($url->getPort())->toBe(8080);
            expect($url->getPath())->toBe('/path');
            expect($url->getQuery())->toBe('query=1');
            expect($url->getFragment())->toBe('fragment');
        });

        it('handles default ports correctly', function () {
            $url = Rfc3986Url::parse('https://example.com/path');

            expect($url->getPort())->toBeNull();
        });

        it('parses relative URLs with base', function () {
            $base = Rfc3986Url::parse('https://example.com/base/path');
            $url = Rfc3986Url::parse('../other', $base);

            expect($url->getPath())->toContain('other');
        });
    });

    describe('spec compliance', function () {
        it('reports RFC3986 spec', function () {
            $url = Rfc3986Url::parse('https://example.com');

            expect($url->getSpec())->toBe(UrlSpec::RFC3986);
            expect($url->getSpec()->isRfc3986())->toBeTrue();
            expect($url->getSpec()->isWhatwg())->toBeFalse();
        });
    });

    describe('immutable withers', function () {
        it('returns new instance with different scheme', function () {
            $original = Rfc3986Url::parse('https://example.com');
            $modified = $original->withScheme('http');

            expect($original->getScheme())->toBe('https');
            expect($modified->getScheme())->toBe('http');
            expect(spl_object_id($original))->notToBe(spl_object_id($modified));
        });

        it('returns new instance with different host', function () {
            $original = Rfc3986Url::parse('https://example.com');
            $modified = $original->withHost('api.example.com');

            expect($original->getHost())->toBe('example.com');
            expect($modified->getHost())->toBe('api.example.com');
        });

        it('returns new instance with different port', function () {
            $original = Rfc3986Url::parse('https://example.com');
            $modified = $original->withPort(8080);

            expect($original->getPort())->toBeNull();
            expect($modified->getPort())->toBe(8080);
        });

        it('returns new instance with different path', function () {
            $original = Rfc3986Url::parse('https://example.com/old');
            $modified = $original->withPath('/new');

            expect($original->getPath())->toBe('/old');
            expect($modified->getPath())->toBe('/new');
        });

        it('returns new instance with different query', function () {
            $original = Rfc3986Url::parse('https://example.com?old=1');
            $modified = $original->withQuery('new=2');

            expect($original->getQuery())->toBe('old=1');
            expect($modified->getQuery())->toBe('new=2');
        });

        it('returns new instance with different fragment', function () {
            $original = Rfc3986Url::parse('https://example.com#old');
            $modified = $original->withFragment('new');

            expect($original->getFragment())->toBe('old');
            expect($modified->getFragment())->toBe('new');
        });

        it('returns new instance with user info', function () {
            $original = Rfc3986Url::parse('https://example.com');
            $modified = $original->withUserInfo('user', 'pass');

            expect($original->getUserInfo())->toBe('');
            expect($modified->getUserInfo())->toBe('user:pass');
        });

        it('handles user info without password', function () {
            $url = Rfc3986Url::parse('https://example.com');
            $modified = $url->withUserInfo('user');

            expect($modified->getUserInfo())->toBe('user');
        });
    });

    describe('serialization', function () {
        it('converts to string', function () {
            $url = Rfc3986Url::parse('https://example.com/path?query=1#fragment');

            expect($url->toString())->toBe('https://example.com/path?query=1#fragment');
            expect((string) $url)->toBe('https://example.com/path?query=1#fragment');
        });

        it('converts to ASCII string for IDNA domains', function () {
            $url = Rfc3986Url::parse('https://例え.jp/path');
            $ascii = $url->toAsciiString();

            expect($ascii)->toContain('xn--');
        });
    });

    describe('URL resolution', function () {
        it('resolves relative URLs', function () {
            $base = Rfc3986Url::parse('https://example.com/base/path');
            $resolved = $base->resolve('relative');

            expect($resolved->getHost())->toBe('example.com');
            expect($resolved->getPath())->toContain('relative');
        });

        it('resolves absolute URLs', function () {
            $base = Rfc3986Url::parse('https://example.com/base');
            $resolved = $base->resolve('https://other.com/path');

            expect($resolved->getHost())->toBe('other.com');
            expect($resolved->getPath())->toBe('/path');
        });
    });

    describe('URL comparison', function () {
        it('compares URLs for equality', function () {
            $url1 = Rfc3986Url::parse('https://example.com/path');
            $url2 = Rfc3986Url::parse('https://example.com/path');

            expect($url1->equals($url2))->toBeTrue();
        });

        it('compares URLs ignoring fragments by default', function () {
            $url1 = Rfc3986Url::parse('https://example.com/path#frag1');
            $url2 = Rfc3986Url::parse('https://example.com/path#frag2');

            expect($url1->equals($url2))->toBeTrue();
            expect($url1->equals($url2, includeFragment: true))->toBeFalse();
        });

        it('detects different URLs', function () {
            $url1 = Rfc3986Url::parse('https://example.com/path1');
            $url2 = Rfc3986Url::parse('https://example.com/path2');

            expect($url1->equals($url2))->toBeFalse();
        });
    });

    describe('edge cases', function () {
        it('handles empty path', function () {
            $url = Rfc3986Url::parse('https://example.com');

            expect($url->getPath())->toBe('');
        });

        it('handles empty query', function () {
            $url = Rfc3986Url::parse('https://example.com/path');

            expect($url->getQuery())->toBe('');
        });

        it('handles empty fragment', function () {
            $url = Rfc3986Url::parse('https://example.com/path');

            expect($url->getFragment())->toBe('');
        });

        it('removes port with null', function () {
            $url = Rfc3986Url::parse('https://example.com:8080/path');
            $modified = $url->withPort(null);

            expect($modified->getPort())->toBeNull();
        });

        it('removes query with empty string', function () {
            $url = Rfc3986Url::parse('https://example.com?query=1');
            $modified = $url->withQuery('');

            expect($modified->getQuery())->toBe('');
        });

        it('removes fragment with empty string', function () {
            $url = Rfc3986Url::parse('https://example.com#fragment');
            $modified = $url->withFragment('');

            expect($modified->getFragment())->toBe('');
        });
    });

    describe('API client use cases', function () {
        it('preserves exact URL structure for API calls', function () {
            $url = Rfc3986Url::parse('https://api.example.com/v1/users?filter=active&sort=name');

            expect($url->getQuery())->toBe('filter=active&sort=name');
            expect($url->toString())->toContain('filter=active&sort=name');
        });

        it('supports URL signature generation', function () {
            $url = Rfc3986Url::parse('https://api.example.com/resource');
            $withAuth = $url->withQuery('signature=abc123&timestamp=1234567890');

            $canonical = $withAuth->toString();

            expect($canonical)->toContain('signature=abc123');
            expect($canonical)->toContain('timestamp=1234567890');
        });
    });
});
