<?php

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


use Alsendo\AlsendoWrapper\Api\Ecolet\EcoletOAuthClient;
use Alsendo\AlsendoWrapper\Api\Ecolet\EcoletTokenStorage;
use Alsendo\AlsendoWrapper\Api\Ecolet\Model\Order\EcoletOrderResponse;
use Alsendo\AlsendoWrapper\Model\Order\SendOrderResponse;
use Alsendo\AlsendoWrapper\Model\Order\Valuation\OrderValuationResponse;
use Alsendo\AlsendoWrapper\ApiClientFactory;
use Alsendo\AlsendoWrapper\ApiClientInterface;
use Alsendo\AlsendoWrapper\Exception\ResponseException;
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 Ecolet's test environment.
 * 
 * Provides a comprehensive suite of tests to validate the full API functionality including:
 * - User authentication and profile retrieval (getMe)
 * - Order creation and sending
 * - Order valuation and rate calculation
 * - Service structure retrieval
 * - Order retrieval (single and multiple)
 * - Waybill generation
 * - Order cancellation
 * - Manifest/turn-in list generation
 *
 * The test setup initializes an OAuth client for authentication, manages access tokens
 * using EcoletTokenStorage, and configures a Romanian locale faker for generating
 * realistic address, phone, and person data.
 *
 * Special considerations:
 * - Uses OAuth 2.0 password grant for authentication
 * - Automatically refreshes expired access tokens
 * - Manages token persistence across test runs
 *
 * All tests verify that API responses conform to expected response types and return
 * appropriate data structures specific to the Ecolet API.
 */
class ApiEcoletTest extends TestCase
{
    private ApiClientInterface $apiClient;

    private EcoletOAuthClient $ecoletOAuthClient;

    private EcoletTokenStorage $tokenStorage;

    private $faker;
    private ?int $orderId = null;

    /**
     * Configures the test environment by loading environment variables, initializing OAuth client,
     * obtaining access token, and creating an API client instance.
     *
     * This method sets up the necessary dependencies for running tests, including:
     * - Loading environment configuration from .env.test.local
     * - Initializing OAuth client with credentials
     * - Obtaining and storing access token using password grant
     * - Creating API client with the access token
     * - Initializing Faker with Romanian locale providers
     *
     * The OAuth token is persisted to 'ecolet_token.json' for reuse across test runs
     * and automatic refresh when expired.
     *
     * @return void
     * @throws ResponseException
     * @throws ValidationException
     * @throws \JsonException
     */
    protected function setUp(): void
    {
        // Load environment variables from .env file
        $dotenv = Dotenv::createImmutable(__DIR__ . '/../../../../', '.env.test.local');
        $dotenv->load();

        $this->ecoletOAuthClient = new EcoletOAuthClient(
            [
                'client_id' => $_ENV['ECOLET_API_CLIENT_ID'],
                'client_secret' => $_ENV['ECOLET_API_CLIENT_SECRET'],
                'redirect_uri' => $_ENV['ECOLET_API_REDIRECT_URI'],
            ],
            true
        );

        $token = $this->ecoletOAuthClient->getAccessTokenWithPassword($_ENV['ECOLET_API_USERNAME'], $_ENV['ECOLET_API_PASSWORD']);

        $this->tokenStorage = new EcoletTokenStorage('ecolet_token.json');
        $this->tokenStorage->saveToken($token);

        $this->apiClient = (new ApiClientFactory())->create(
            ApiClientFactory::ECOLET_TEST,
            [
                'token' => $this->tokenStorage->getToken()->getAccessToken(),
            ]);

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

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

        $orderRequest->address = new Address(
            new Contact(
                'ro',
                'Test Company',
                '',
                '',
                '011318',
                'Bucuresti',
                'Bucuresti',
                1,
                'Test Test',
                'test@ecolet.ro',
                '0214824089',
                '',
                13751,
                'Bucuresti-Ploiesti',
                '172-176',
                '1',
                'A2',
                '1',
                'A3a',
                false,
                null
            ),
            new Contact(
                'ro',
                'Test Company',
                '',
                '',
                '011318',
                'Bucuresti',
                'Bucuresti',
                1,
                'Test Test',
                'test@ecolet.ro',
                '0214824089',
                '',
                13751,
                'Bucuresti-Ploiesti',
                '172-176',
                '1',
                'A2',
                '1',
                'A3a',
                false,
                null
            )
        );

        $orderRequest->shipmentValue = 200;

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

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

        $shipment = new Shipment();
        $shipment->dimension1 = 10;
        $shipment->dimension2 = 10;
        $shipment->dimension3 = 10;
        $shipment->weight = 10;
        $shipment->isNstd = 0;
        $shipment->shipmentTypeCode = 'package';
        $shipment->shape = 'standard';
        $shipment->amount = null;
        $shipment->content = 'book';
        $orderRequest->shipment = [$shipment];

        $orderRequest->content = 'book';

        return $orderRequest;
    }

    /**
     * Refreshes the OAuth access token if it has expired.
     *
     * This helper method checks if the current token is expired and automatically
     * refreshes it using the refresh token. The new token is saved to storage
     * for subsequent requests.
     *
     * @return void
     */
    private function refreshTokenIfNeeded(): void
    {
        $token = $this->tokenStorage->getToken();
        if ($token && $token->isExpired()) {
            $newToken = $this->ecoletOAuthClient->refreshAccessToken($token->refreshToken);
            if ($newToken) {
                $this->tokenStorage->saveToken($newToken);
                // Update API client with new token
                $this->apiClient = (new ApiClientFactory())->create(
                    ApiClientFactory::ECOLET_TEST,
                    ['token' => $newToken->getAccessToken()]
                );
            }
        }
    }

    /**
     * Tests the getMe() endpoint which retrieves the authenticated user's profile information.
     *
     * This test verifies that:
     * - The API returns an array with user profile data
     * - The response is not empty
     *
     * @return void
     */
    public function testGetMe(): void
    {
        $response = $this->apiClient->getMe();

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

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

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

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

    /**
     * Tests the retrieval of order valuation and verifies that a valid EcoletOrderValuation response is returned.
     *
     * This test requests shipping rate quotes from the Ecolet API for a given order configuration.
     * It ensures the OAuth token is valid before making the request and verifies that:
     * - An EcoletOrderValuation object is returned
     * - The valuation contains pricing information
     *
     * @return void
     */
    public function testGetOrderValuation(): void
    {
        $this->refreshTokenIfNeeded();
        $orderRequest = $this->createTestOrderRequest();

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

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

    /**
     * Tests the retrieval of the service structure and verifies that a valid ServiceStructure object is returned.
     *
     * The service structure contains information about available shipping services, their options,
     * and configuration parameters. This test ensures the OAuth token is valid before making the request.
     *
     * @return void
     */
    public function testGetServiceStructure(): void
    {
        $response = $this->apiClient->getServiceStructure();
        $this->assertInstanceOf(ServiceStructure::class, $response);
        $this->assertNotEmpty($response->services);
    }

    public function testGetOrder(): void
    {
        $orderId = $this->orderId ?? 2223205;
        $response = $this->apiClient->getOrder($orderId);
        $this->assertInstanceOf(EcoletOrderResponse::class, $response);
    }


}