<?php

namespace Alsendo\AlsendoWrapper\Tests\Unit\Api\Zaslat;


use Alsendo\AlsendoWrapper\Model\Order\Valuation\OrderValuationResponse;
use Alsendo\AlsendoWrapper\Model\Order\SendOrderResponse;
use Alsendo\AlsendoWrapper\Api\Zaslat\Model\Order\ZaslatOrderResponse;
use Alsendo\AlsendoWrapper\Api\Zaslat\Model\ZaslatContact;
use Alsendo\AlsendoWrapper\ApiClientFactory;
use Alsendo\AlsendoWrapper\ApiClientInterface;
use Alsendo\AlsendoWrapper\Exception\ValidationException;
use Alsendo\AlsendoWrapper\Model\Address;
use Alsendo\AlsendoWrapper\Model\Cod;
use Alsendo\AlsendoWrapper\Model\Contact;
use Alsendo\AlsendoWrapper\Model\Notification;
use Alsendo\AlsendoWrapper\Model\NotificationDetail;
use Alsendo\AlsendoWrapper\Model\Order\OrderRequest;
use Alsendo\AlsendoWrapper\Model\Pickup;
use Alsendo\AlsendoWrapper\Model\Service\ServiceStructure;
use Alsendo\AlsendoWrapper\Model\Shipment;
use DateTimeImmutable;
use Dotenv\Dotenv;
use Faker\Factory;
use PHPUnit\Framework\TestCase;

/**
 * Test class for ApiClient integration with Zaslat's test environment.
 * 
 * Provides a comprehensive suite of tests to validate the full API functionality including:
 * - Order creation and sending
 * - Order valuation and rate calculation
 * - Order retrieval (single and multiple)
 * - Waybill generation
 * - Order cancellation
 * - Manifest/turn-in list generation
 * - Contact list retrieval
 * - Unsupported endpoint handling
 *
 * The test setup initializes an API client using test credentials from environment variables
 * and configures a Czech locale faker for generating realistic address, phone, and person data.
 *
 * All tests verify that API responses conform to expected response types and return appropriate
 * data structures specific to the Zaslat API.
 */
class ApiZaslatTest extends TestCase
{
    private ApiClientInterface $apiClient;

    private $faker;

    /**
     * Test order ID used in tests
     * @var string|null
     */
    private ?string $orderId = null;

    /**
     * Configures the test environment by loading environment variables, creating an API client instance,
     * and initializing a Faker instance with Czech locale providers.
     *
     * This method sets up the necessary dependencies for running tests, including environment configuration,
     * API client credentials, and a Faker instance tailored for Czech data generation.
     *
     * @return void
     * @throws ValidationException
     */
    protected function setUp(): void
    {
        // Load environment variables from .env file
        $dotenv = Dotenv::createImmutable(__DIR__ . '/../../../../', '.env.test.local');
        $dotenv->load();
        $this->apiClient = (new ApiClientFactory())->create(
            ApiClientFactory::ZASLAT_TEST,
            [
                'api_key' => $_ENV['ZASLAT_API_KEY'],
            ]);

        $this->faker = Factory::create();
        $this->faker->addProvider(new \Faker\Provider\cs_CZ\Address($this->faker));
        $this->faker->addProvider(new \Faker\Provider\cs_CZ\PhoneNumber($this->faker));
        $this->faker->addProvider(new \Faker\Provider\cs_CZ\Person($this->faker));
    }

    /**
     * Creates a test order request with predefined values for carrier, address, options, notification,
     * shipment, and pickup details specific to Zaslat API.
     *
     * @return OrderRequest The constructed OrderRequest object with default test values for Zaslat
     */
    private function createTestOrderRequest(): OrderRequest
    {
        $orderRequest = new OrderRequest();
        $orderRequest->carrier = 'GLS';
        $orderRequest->currency = 'CZK';

        $orderRequest->address = new Address(
            new Contact(),
            new Contact(
                'CZ',
                $this->faker->name(),
                $this->faker->streetAddress(),
                '',
                '12000',
                '',
                'Praha',
                1,
                $this->faker->name(),
                $this->faker->email(),
                $this->faker->phoneNumber(),
                ''
            )
        );

        $orderRequest->address->sender->id = 374956;

        $orderRequest->option = ['31' => 1];

        $orderRequest->notification = new Notification(
            new NotificationDetail(1, 1, 1),
            new NotificationDetail(1),
            new NotificationDetail(1, 0, 1),
            new NotificationDetail(1, 0, 1)
        );

        $orderRequest->shipmentValue = 200;

        $orderRequest->cod = new Cod();
        $orderRequest->cod->amount = 0;
        $orderRequest->cod->bankAccount = '';

        $orderRequest->pickup = new Pickup();
        $orderRequest->pickup->type = 'OCCASIONAL';
        $orderRequest->pickup->date = (new DateTimeImmutable('+1 day'))->format('Y-m-d');
        $orderRequest->pickup->hoursFrom = '12:00';
        $orderRequest->pickup->hoursTo = '19:00';

        $shipment = new Shipment();
        $shipment->dimension1 = 10;
        $shipment->dimension2 = 10;
        $shipment->dimension3 = 10;
        $shipment->weight = 10;
        $shipment->isNstd = 0;
        $shipment->shipmentTypeCode = 'PACZKA';
        $orderRequest->shipment = [$shipment];

        $orderRequest->content = 'Test order';

        return $orderRequest;
    }

    /**
     * Tests the sending of an order request and verifies that a valid ZaslatOrderCreatedResponse is returned.
     *
     * This test creates a complete order request with all required fields and sends it to the Zaslat API.
     * It verifies that the API returns the appropriate response object type.
     *
     * @return void
     */
    public function testSendOrder(): void
    {
        $orderRequest = $this->createTestOrderRequest();

        $response = $this->apiClient->sendOrder($orderRequest);

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

    /**
     * Tests the retrieval of order valuation and verifies that valid ZaslatOrderValuation responses are returned.
     *
     * This test requests shipping rate quotes from the Zaslat API for a given order configuration.
     * It verifies that:
     * - An array is returned
     * - The array contains at least one element
     * - Each element is an instance of ZaslatOrderValuation
     *
     * @return void
     */
    public function testGetOrderValuation(): void
    {
        $orderRequest = $this->createTestOrderRequest();
        // Override COD amount for valuation test
        $orderRequest->cod->amount = 500;

        $response = $this->apiClient->getOrderValuation($orderRequest);

        $this->assertInstanceOf(OrderValuationResponse::class, $response);
        $this->assertNotEmpty($response->valuations);
    }

    /**
     * Tests retrieving a single order by ID and verifies that a valid ZaslatOrderResponse is returned.
     *
     * This test fetches order details from the Zaslat API using a known order ID.
     * It verifies that the API returns the appropriate response object type with order information.
     *
     * @return void
     */
    public function testGetOrder(): void
    {
        $response = $this->apiClient->getOrder('IZ07BFA2EE08');
        $this->assertInstanceOf(ZaslatOrderResponse::class, $response);
    }

    /**
     * Tests the retrieval of contact list and verifies that valid ZaslatContact objects are returned.
     *
     * This test fetches the list of saved contacts/addresses from the Zaslat API.
     * It verifies that:
     * - An array is returned
     * - Each element in the array is an instance of ZaslatContact
     *
     * @return void
     */
    public function testGetContactsList(): void
    {
        $contacts = $this->apiClient->getContactList();

        $this->assertIsArray($contacts);

        foreach ($contacts as $contact) {
            $this->assertInstanceOf(ZaslatContact::class, $contact);
        }
    }

    /**
     * Test verifying that the getServiceStructure method returns a ServiceStructure with hardcoded Zaslat services.
     *
     * @return void
     */
    public function testGetServiceStructure(): void
    {
        $response = $this->apiClient->getServiceStructure();
        $this->assertInstanceOf(\Alsendo\AlsendoWrapper\Model\Service\ServiceStructure::class, $response);
        $this->assertNotEmpty($response->services);
    }

    /**
     * Test verifying the retrieval of multiple order details at once.
     * 
     * The getOrders() method allows fetching information about multiple orders in a single request.
     * The test verifies that:
     * - An array is returned
     * - Each array element is an instance of ZaslatOrderResponse
     * - Array keys correspond to order IDs
     *
     * @return void
     */
    public function testGetOrders(): void
    {
        // Use test order ID or default one
        $orderId1 = $this->orderId ?? 'IZ07BFA2EE08';
        $orderId2 = 'IZ07BFA2EE08'; // We can use the same ID for testing

        $response = $this->apiClient->getOrders([$orderId1, $orderId2]);

        $this->assertIsArray($response);
        $this->assertNotEmpty($response);

        foreach ($response as $orderId => $order) {
            $this->assertIsString($orderId);
            $this->assertInstanceOf(ZaslatOrderResponse::class, $order);
            $this->assertEquals($orderId, $order->id);
        }
    }

    /**
     * Test verifying the retrieval of a waybill for an order.
     * 
     * The getWayBill() method retrieves the shipping label in PDF format (base64).
     * The test verifies that:
     * - A WayBill object is returned
     * - The object contains waybill data
     * - The data is not empty
     *
     * @return void
     */
    public function testGetWayBill(): void
    {
        // Use test order ID or default one
        $orderId = $this->orderId ?? 'IZ07BFA2EE08';

        $response = $this->apiClient->getWayBill($orderId);

        $this->assertInstanceOf(\Alsendo\AlsendoWrapper\Model\WayBill\WayBill::class, $response);
        $this->assertNotEmpty($response->waybill);
        $this->assertIsString($response->waybill);
    }

    /**
     * Test verifying order cancellation.
     * 
     * The cancelOrder() method sends a shipment cancellation request to the Zaslat API.
     * The test verifies that:
     * - The method returns true after successful cancellation
     * - The request is correctly processed by the API
     * 
     * NOTE: This test may not work if the order cannot be cancelled
     * (e.g., it has already been cancelled or is in a state that prevents cancellation)
     *
     * @return void
     */
    public function testCancelOrder(): void
    {
        // For cancellation testing, we can use an existing ID
        // In production environment, an order should be created first
        $orderId = $this->orderId ?? 'IZ07BFA2EE08';

        try {
            $response = $this->apiClient->cancelOrder($orderId);
            $this->assertTrue($response);
        } catch (\Alsendo\AlsendoWrapper\Exception\ValidationException $e) {
            // API returned field-level validation errors (e.g., order cannot be cancelled)
            $this->assertNotEmpty($e->getErrors());
        } catch (\Alsendo\AlsendoWrapper\Exception\ResponseException $e) {
            // If the order cannot be cancelled (e.g., it's already cancelled)
            // we accept this as valid behavior
            $this->assertStringContainsString('storno', strtolower($e->getMessage()));
        }
    }

    /**
     * Test verifying the retrieval of a manifest list for orders.
     * 
     * The getTurnInList() method retrieves the manifest (handover list) for specified shipments.
     * A manifest is a document confirming the handover of packages to the courier.
     * 
     * The test verifies that:
     * - The method returns manifest data
     * - The data is not empty
     * - The data format matches expectations (string with PDF data)
     *
     * @return void
     */
    public function testGetTurnInList(): void
    {
        // Use test order ID or default one
        $orderId1 = $this->orderId ?? 'IZ07BFA2EE08';
        $orderId2 = 'IZ07BFA2EE08';

        $response = $this->apiClient->getTurnInList([$orderId1, $orderId2]);

        $this->assertNotEmpty($response);
        // Manifest is returned as a string with PDF data in base64
        $this->assertIsString($response);
    }



}