<?php
declare(strict_types=1);

namespace Tests\Unit;

use App\Core\Exceptions\ShipmentException;
use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\ApaczkaApiClient;
use App\Modules\Settings\ApaczkaIntegrationRepository;
use App\Modules\Settings\CompanySettingsRepository;
use App\Modules\Shipments\ApaczkaShipmentService;
use App\Modules\Shipments\ShipmentPackageRepository;
use PHPUnit\Framework\TestCase;
use ReflectionMethod;

final class ApaczkaShipmentServiceTest extends TestCase
{
    private ApaczkaShipmentService $service;

    protected function setUp(): void
    {
        $this->service = new ApaczkaShipmentService(
            $this->createMock(ApaczkaIntegrationRepository::class),
            $this->createMock(ApaczkaApiClient::class),
            $this->createMock(ShipmentPackageRepository::class),
            $this->createMock(CompanySettingsRepository::class),
            $this->createMock(OrdersRepository::class)
        );
    }

    private function invokeTruncate(string $street): string
    {
        $method = new ReflectionMethod(ApaczkaShipmentService::class, 'truncateStreetForPoint');
        $method->setAccessible(true);

        return (string) $method->invoke($this->service, $street);
    }

    private function invokeAssert(string $street): void
    {
        $method = new ReflectionMethod(ApaczkaShipmentService::class, 'assertStreetWithinCourierLimit');
        $method->setAccessible(true);
        $method->invoke($this->service, $street);
    }

    /**
     * @param array<string, mixed> $formData
     * @return array<int, int>
     */
    private function invokeBuildOptions(array $formData): array
    {
        $method = new ReflectionMethod(ApaczkaShipmentService::class, 'buildOptionsPayload');
        $method->setAccessible(true);
        /** @var array<int, int> $result */
        $result = $method->invoke($this->service, $formData);

        return $result;
    }

    public function testTruncateStreetForPointShortensLongValue(): void
    {
        $input = 'Punkt odbioru POP-XYZ123 przy al. Jerozolimskich 55A/12';
        $result = $this->invokeTruncate($input);

        $this->assertLessThanOrEqual(30, mb_strlen($result, 'UTF-8'));
        $this->assertStringStartsWith('Punkt odbioru', $result);
    }

    public function testTruncateStreetForPointPreservesShortValue(): void
    {
        $this->assertSame('Polna 5', $this->invokeTruncate('Polna 5'));
    }

    public function testTruncateStreetForPointBoundary30(): void
    {
        $input = str_repeat('A', 30);

        $result = $this->invokeTruncate($input);

        $this->assertSame(30, mb_strlen($result, 'UTF-8'));
        $this->assertSame($input, $result);
    }

    public function testTruncateStreetForPointUtf8Safe(): void
    {
        $input = 'Ulica Żółtych Słoneczników Małych 12345';
        $this->assertGreaterThan(30, mb_strlen($input, 'UTF-8'));

        $result = $this->invokeTruncate($input);

        $this->assertSame(30, mb_strlen($result, 'UTF-8'));
        $this->assertTrue(mb_check_encoding($result, 'UTF-8'));
    }

    public function testTruncateStreetForPointRtrimTrailingSpace(): void
    {
        $input = str_repeat('A', 29) . ' XYZ';

        $result = $this->invokeTruncate($input);

        $this->assertSame(29, mb_strlen($result, 'UTF-8'));
        $this->assertSame(str_repeat('A', 29), $result);
    }

    public function testAssertStreetWithinCourierLimitAcceptsShort(): void
    {
        $this->invokeAssert('Polna 5');
        $this->addToAssertionCount(1);
    }

    public function testAssertStreetWithinCourierLimitAcceptsBoundary30(): void
    {
        $this->invokeAssert(str_repeat('A', 30));
        $this->addToAssertionCount(1);
    }

    public function testAssertStreetWithinCourierLimitRejectsLong(): void
    {
        $this->expectException(ShipmentException::class);
        $this->expectExceptionMessageMatches('/przekracza 30 znakow/');

        $this->invokeAssert('Ul. Generała Pilota Józefa Kowalskiego 6/1');
    }

    public function testBuildOptionsPayloadWeekendEnabled(): void
    {
        $this->assertSame([19 => 1], $this->invokeBuildOptions(['weekend_delivery' => '1']));
    }

    public function testBuildOptionsPayloadWeekendDisabled(): void
    {
        $this->assertSame([], $this->invokeBuildOptions(['weekend_delivery' => '0']));
    }

    public function testBuildOptionsPayloadEmptyForm(): void
    {
        $this->assertSame([], $this->invokeBuildOptions([]));
    }
}
