<?php
declare(strict_types=1);

namespace Tests\Unit;

use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\AllegroApiClient;
use App\Modules\Settings\AllegroTokenManager;
use App\Modules\Settings\CompanySettingsRepository;
use App\Modules\Shipments\AllegroShipmentService;
use App\Modules\Shipments\ShipmentPackageRepository;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use RuntimeException;

final class AllegroShipmentServiceTest extends TestCase
{
    private AllegroTokenManager&MockObject $tokenManager;
    private AllegroApiClient&MockObject $apiClient;
    private ShipmentPackageRepository&MockObject $packages;
    private CompanySettingsRepository&MockObject $companySettings;
    private OrdersRepository&MockObject $ordersRepository;

    protected function setUp(): void
    {
        $this->tokenManager = $this->createMock(AllegroTokenManager::class);
        $this->apiClient = $this->createMock(AllegroApiClient::class);
        $this->packages = $this->createMock(ShipmentPackageRepository::class);
        $this->companySettings = $this->createMock(CompanySettingsRepository::class);
        $this->ordersRepository = $this->createMock(OrdersRepository::class);
    }

    public function testCheckCreationStatusPushesWaybillForAllegroOrder(): void
    {
        $this->tokenManager
            ->method('resolveToken')
            ->willReturn(['token-1', 'sandbox']);

        $this->packages
            ->method('findById')
            ->with(11)
            ->willReturn([
                'id' => 11,
                'order_id' => 200,
                'command_id' => 'cmd-11',
                'carrier_id' => 'INPOST',
            ]);

        $this->packages
            ->expects($this->once())
            ->method('update')
            ->with(
                11,
                $this->callback(static function (array $payload): bool {
                    return ($payload['tracking_number'] ?? null) === 'TRK-001'
                        && ($payload['carrier_id'] ?? null) === 'INPOST';
                })
            );

        $this->packages
            ->expects($this->once())
            ->method('resolveCarrierName')
            ->with('allegro_wza', 'INPOST')
            ->willReturn('InPost');

        $this->ordersRepository
            ->method('findDetails')
            ->with(200)
            ->willReturn([
                'order' => [
                    'source' => 'allegro',
                    'source_order_id' => 'CHECKOUT-200',
                ],
            ]);

        $this->apiClient
            ->method('getShipmentCreationStatus')
            ->with('sandbox', 'token-1', 'cmd-11')
            ->willReturn([
                'status' => 'SUCCESS',
                'shipmentId' => 'SHIP-11',
            ]);

        $this->apiClient
            ->method('getShipmentDetails')
            ->with('sandbox', 'token-1', 'SHIP-11')
            ->willReturn([
                'waybill' => 'TRK-001',
            ]);

        $this->apiClient
            ->expects($this->once())
            ->method('addShipmentToOrder')
            ->with(
                'sandbox',
                'token-1',
                'CHECKOUT-200',
                'TRK-001',
                'INPOST',
                'InPost'
            )
            ->willReturn([]);

        $service = $this->createService();
        $result = $service->checkCreationStatus(11);

        $this->assertSame('created', $result['status']);
        $this->assertSame('TRK-001', $result['tracking_number']);
    }

    public function testCheckCreationStatusSkipsPushForNonAllegroOrder(): void
    {
        $this->tokenManager
            ->method('resolveToken')
            ->willReturn(['token-1', 'sandbox']);

        $this->packages
            ->method('findById')
            ->with(12)
            ->willReturn([
                'id' => 12,
                'order_id' => 201,
                'command_id' => 'cmd-12',
                'carrier_id' => 'DPD',
            ]);

        $this->packages
            ->expects($this->once())
            ->method('update');

        $this->ordersRepository
            ->method('findDetails')
            ->with(201)
            ->willReturn([
                'order' => [
                    'source' => 'shoppro',
                    'source_order_id' => 'SP-ORDER-1',
                ],
            ]);

        $this->apiClient
            ->method('getShipmentCreationStatus')
            ->willReturn([
                'status' => 'SUCCESS',
                'shipmentId' => 'SHIP-12',
            ]);

        $this->apiClient
            ->method('getShipmentDetails')
            ->willReturn([
                'waybill' => 'TRK-002',
            ]);

        $this->apiClient
            ->expects($this->never())
            ->method('addShipmentToOrder');

        $service = $this->createService();
        $result = $service->checkCreationStatus(12);

        $this->assertSame('created', $result['status']);
        $this->assertSame('TRK-002', $result['tracking_number']);
    }

    public function testCheckCreationStatusDoesNotFailWhenPushReturnsError(): void
    {
        $this->tokenManager
            ->method('resolveToken')
            ->willReturn(['token-1', 'sandbox']);

        $this->packages
            ->method('findById')
            ->with(13)
            ->willReturn([
                'id' => 13,
                'order_id' => 202,
                'command_id' => 'cmd-13',
                'carrier_id' => 'DHL',
            ]);

        $this->packages
            ->expects($this->once())
            ->method('update');

        $this->packages
            ->method('resolveCarrierName')
            ->willReturn('DHL');

        $this->ordersRepository
            ->method('findDetails')
            ->with(202)
            ->willReturn([
                'order' => [
                    'source' => 'allegro',
                    'source_order_id' => 'CHECKOUT-202',
                ],
            ]);

        $this->apiClient
            ->method('getShipmentCreationStatus')
            ->willReturn([
                'status' => 'SUCCESS',
                'shipmentId' => 'SHIP-13',
            ]);

        $this->apiClient
            ->method('getShipmentDetails')
            ->willReturn([
                'waybill' => 'TRK-003',
            ]);

        $this->apiClient
            ->expects($this->once())
            ->method('addShipmentToOrder')
            ->willThrowException(new RuntimeException('API Allegro HTTP 422'));

        $service = $this->createService();
        $result = $service->checkCreationStatus(13);

        $this->assertSame('created', $result['status']);
        $this->assertSame('TRK-003', $result['tracking_number']);
    }

    public function testCheckCreationStatusRetriesPushAfter401(): void
    {
        $this->tokenManager
            ->method('resolveToken')
            ->willReturnOnConsecutiveCalls(
                ['token-old', 'sandbox'],
                ['token-new', 'production']
            );

        $this->packages
            ->method('findById')
            ->with(14)
            ->willReturn([
                'id' => 14,
                'order_id' => 203,
                'command_id' => 'cmd-14',
                'carrier_id' => 'INPOST',
            ]);

        $this->packages
            ->expects($this->once())
            ->method('update');

        $this->packages
            ->method('resolveCarrierName')
            ->willReturn('InPost');

        $this->ordersRepository
            ->method('findDetails')
            ->with(203)
            ->willReturn([
                'order' => [
                    'source' => 'allegro',
                    'source_order_id' => 'CHECKOUT-203',
                ],
            ]);

        $this->apiClient
            ->method('getShipmentCreationStatus')
            ->with('sandbox', 'token-old', 'cmd-14')
            ->willReturn([
                'status' => 'SUCCESS',
                'shipmentId' => 'SHIP-14',
            ]);

        $this->apiClient
            ->method('getShipmentDetails')
            ->with('sandbox', 'token-old', 'SHIP-14')
            ->willReturn([
                'waybill' => 'TRK-004',
            ]);

        $this->apiClient
            ->expects($this->exactly(2))
            ->method('addShipmentToOrder')
            ->willReturnCallback(static function (
                string $environment,
                string $token,
                string $checkoutId,
                string $waybill,
                string $carrierId,
                string $carrierName
            ): array {
                static $calls = 0;
                $calls++;

                if ($calls === 1) {
                    TestCase::assertSame('sandbox', $environment);
                    TestCase::assertSame('token-old', $token);
                    TestCase::assertSame('CHECKOUT-203', $checkoutId);
                    TestCase::assertSame('TRK-004', $waybill);
                    TestCase::assertSame('INPOST', $carrierId);
                    TestCase::assertSame('InPost', $carrierName);
                    throw new RuntimeException('ALLEGRO_HTTP_401');
                }

                TestCase::assertSame('production', $environment);
                TestCase::assertSame('token-new', $token);
                return [];
            });

        $service = $this->createService();
        $result = $service->checkCreationStatus(14);

        $this->assertSame('created', $result['status']);
        $this->assertSame('TRK-004', $result['tracking_number']);
    }

    private function createService(): AllegroShipmentService
    {
        return new AllegroShipmentService(
            $this->tokenManager,
            $this->apiClient,
            $this->packages,
            $this->companySettings,
            $this->ordersRepository
        );
    }
}
