<?php
/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 */

declare(strict_types=1);

namespace DeskMoloni\Tests\Contract;

use PHPUnit\Framework\TestCase;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

/**
 * Contract Test: Moloni API Endpoint Validation
 * 
 * This test MUST FAIL initially as part of TDD methodology.
 * Tests validate API contracts with real Moloni sandbox environment.
 * 
 * @group contract
 * @group moloni-api
 */
class MoloniApiContractTest extends TestCase
{
    private Client $httpClient;
    private array $config;
    private ?string $accessToken = null;

    protected function setUp(): void
    {
        global $testConfig;
        $this->config = $testConfig['moloni'];
        
        $this->httpClient = new Client([
            'base_uri' => $this->config['sandbox'] ? MOLONI_SANDBOX_URL : MOLONI_PRODUCTION_URL,
            'timeout' => 30,
            'headers' => [
                'Content-Type' => 'application/json',
                'Accept' => 'application/json',
                'User-Agent' => 'Desk-Moloni/3.0.0 PHPUnit-Test'
            ]
        ]);
    }

    /**
     * Test OAuth 2.0 token endpoint contract
     * This test will initially fail until OAuth implementation exists
     */
    public function testOAuthTokenEndpointContract(): void
    {
        $response = $this->httpClient->post('v1/grant', [
            'json' => [
                'grant_type' => 'client_credentials',
                'client_id' => $this->config['client_id'],
                'client_secret' => $this->config['client_secret'],
                'scope' => ''
            ]
        ]);

        $this->assertEquals(200, $response->getStatusCode());
        
        $data = json_decode($response->getBody()->getContents(), true);
        
        // Validate response structure
        $this->assertArrayHasKey('access_token', $data);
        $this->assertArrayHasKey('token_type', $data);
        $this->assertArrayHasKey('expires_in', $data);
        $this->assertEquals('Bearer', $data['token_type']);
        $this->assertIsString($data['access_token']);
        $this->assertIsInt($data['expires_in']);
        $this->assertGreaterThan(0, $data['expires_in']);
        
        // Store token for subsequent tests
        $this->accessToken = $data['access_token'];
    }

    /**
     * Test company list endpoint contract
     * @depends testOAuthTokenEndpointContract
     */
    public function testCompanyListEndpointContract(): void
    {
        if (!$this->accessToken) {
            $this->markTestSkipped('Access token not available');
        }

        $response = $this->httpClient->post('v1/companies/getAll', [
            'json' => [
                'access_token' => $this->accessToken
            ]
        ]);

        $this->assertEquals(200, $response->getStatusCode());
        
        $data = json_decode($response->getBody()->getContents(), true);
        
        // Validate Moloni response structure
        $this->assertArrayHasKey('valid', $data);
        $this->assertArrayHasKey('data', $data);
        $this->assertEquals(1, $data['valid']);
        $this->assertIsArray($data['data']);
        
        if (!empty($data['data'])) {
            $company = $data['data'][0];
            $this->assertArrayHasKey('company_id', $company);
            $this->assertArrayHasKey('name', $company);
            $this->assertIsInt($company['company_id']);
            $this->assertIsString($company['name']);
        }
    }

    /**
     * Test customer creation endpoint contract
     * @depends testOAuthTokenEndpointContract
     */
    public function testCustomerCreateEndpointContract(): void
    {
        if (!$this->accessToken) {
            $this->markTestSkipped('Access token not available');
        }

        $testCustomer = [
            'access_token' => $this->accessToken,
            'company_id' => 1, // Test company ID
            'vat' => '999999990', // Test VAT number
            'number' => 'TEST-' . time(),
            'name' => 'Test Customer Contract',
            'email' => 'test@contract-test.com',
            'phone' => '+351910000000',
            'address' => 'Test Address',
            'zip_code' => '1000-001',
            'city' => 'Lisboa',
            'country_id' => 1 // Portugal
        ];

        $response = $this->httpClient->post('v1/customers/insert', [
            'json' => $testCustomer
        ]);

        $this->assertEquals(200, $response->getStatusCode());
        
        $data = json_decode($response->getBody()->getContents(), true);
        
        // Validate response structure
        $this->assertArrayHasKey('valid', $data);
        
        if ($data['valid'] === 1) {
            $this->assertArrayHasKey('data', $data);
            $this->assertArrayHasKey('customer_id', $data['data']);
            $this->assertIsInt($data['data']['customer_id']);
            $this->assertGreaterThan(0, $data['data']['customer_id']);
        } else {
            // Validate error structure
            $this->assertArrayHasKey('errors', $data);
            $this->assertIsArray($data['errors']);
            $this->assertNotEmpty($data['errors']);
        }
    }

    /**
     * Test customer update endpoint contract
     * @depends testOAuthTokenEndpointContract
     */
    public function testCustomerUpdateEndpointContract(): void
    {
        if (!$this->accessToken) {
            $this->markTestSkipped('Access token not available');
        }

        // First create a customer to update
        $createResponse = $this->httpClient->post('v1/customers/insert', [
            'json' => [
                'access_token' => $this->accessToken,
                'company_id' => 1,
                'vat' => '999999991',
                'number' => 'TEST-UPDATE-' . time(),
                'name' => 'Test Customer Update',
                'email' => 'test-update@contract-test.com'
            ]
        ]);

        $createData = json_decode($createResponse->getBody()->getContents(), true);
        
        if ($createData['valid'] !== 1) {
            $this->markTestSkipped('Could not create test customer for update test');
        }

        $customerId = $createData['data']['customer_id'];

        // Now test update
        $updateResponse = $this->httpClient->post('v1/customers/update', [
            'json' => [
                'access_token' => $this->accessToken,
                'company_id' => 1,
                'customer_id' => $customerId,
                'name' => 'Updated Test Customer',
                'email' => 'updated@contract-test.com'
            ]
        ]);

        $this->assertEquals(200, $updateResponse->getStatusCode());
        
        $updateData = json_decode($updateResponse->getBody()->getContents(), true);
        
        // Validate response structure
        $this->assertArrayHasKey('valid', $updateData);
        $this->assertArrayHasKey('data', $updateData);
        
        if ($updateData['valid'] === 1) {
            $this->assertEquals($customerId, $updateData['data']['customer_id']);
        }
    }

    /**
     * Test product creation endpoint contract
     * @depends testOAuthTokenEndpointContract
     */
    public function testProductCreateEndpointContract(): void
    {
        if (!$this->accessToken) {
            $this->markTestSkipped('Access token not available');
        }

        $testProduct = [
            'access_token' => $this->accessToken,
            'company_id' => 1,
            'category_id' => 1,
            'type' => 1, // Product type
            'name' => 'Test Product Contract',
            'summary' => 'Test product for contract validation',
            'reference' => 'TEST-PROD-' . time(),
            'price' => 100.00,
            'unit_id' => 1, // Units
            'has_stock' => 1,
            'stock' => 10,
            'pos_favorite' => 0
        ];

        $response = $this->httpClient->post('v1/products/insert', [
            'json' => $testProduct
        ]);

        $this->assertEquals(200, $response->getStatusCode());
        
        $data = json_decode($response->getBody()->getContents(), true);
        
        // Validate response structure
        $this->assertArrayHasKey('valid', $data);
        
        if ($data['valid'] === 1) {
            $this->assertArrayHasKey('data', $data);
            $this->assertArrayHasKey('product_id', $data['data']);
            $this->assertIsInt($data['data']['product_id']);
            $this->assertGreaterThan(0, $data['data']['product_id']);
        } else {
            $this->assertArrayHasKey('errors', $data);
            $this->assertIsArray($data['errors']);
        }
    }

    /**
     * Test invoice creation endpoint contract
     * @depends testOAuthTokenEndpointContract
     */
    public function testInvoiceCreateEndpointContract(): void
    {
        if (!$this->accessToken) {
            $this->markTestSkipped('Access token not available');
        }

        $testInvoice = [
            'access_token' => $this->accessToken,
            'company_id' => 1,
            'document_set_id' => 1,
            'customer_id' => 1, // Use existing customer
            'date' => date('Y-m-d'),
            'products' => [
                [
                    'product_id' => 1, // Use existing product
                    'name' => 'Test Product Line',
                    'qty' => 1,
                    'price' => 100.00,
                    'discount' => 0,
                    'order' => 0,
                    'exemption_reason' => 'M99',
                    'taxes' => [
                        [
                            'tax_id' => 1,
                            'value' => 23,
                            'order' => 0,
                            'cumulative' => 0
                        ]
                    ]
                ]
            ],
            'payment_method_id' => 1,
            'notes' => 'Test invoice for contract validation'
        ];

        $response = $this->httpClient->post('v1/invoices/insert', [
            'json' => $testInvoice
        ]);

        $this->assertEquals(200, $response->getStatusCode());
        
        $data = json_decode($response->getBody()->getContents(), true);
        
        // Validate response structure
        $this->assertArrayHasKey('valid', $data);
        
        if ($data['valid'] === 1) {
            $this->assertArrayHasKey('data', $data);
            $this->assertArrayHasKey('document_id', $data['data']);
            $this->assertIsInt($data['data']['document_id']);
            $this->assertGreaterThan(0, $data['data']['document_id']);
        } else {
            $this->assertArrayHasKey('errors', $data);
            $this->assertIsArray($data['errors']);
        }
    }

    /**
     * Test rate limiting endpoint behavior
     * @depends testOAuthTokenEndpointContract
     */
    public function testApiRateLimitingBehavior(): void
    {
        if (!$this->accessToken) {
            $this->markTestSkipped('Access token not available');
        }

        $requestCount = 0;
        $rateLimitHit = false;

        // Make rapid requests to test rate limiting
        for ($i = 0; $i < 10; $i++) {
            try {
                $response = $this->httpClient->post('v1/companies/getAll', [
                    'json' => [
                        'access_token' => $this->accessToken
                    ]
                ]);
                
                $requestCount++;
                
                // Check for rate limit headers if present
                if ($response->hasHeader('X-RateLimit-Remaining')) {
                    $remaining = (int)$response->getHeaderLine('X-RateLimit-Remaining');
                    if ($remaining <= 0) {
                        $rateLimitHit = true;
                        break;
                    }
                }
                
                // Small delay to avoid overwhelming the API
                usleep(100000); // 100ms
                
            } catch (GuzzleException $e) {
                if (strpos($e->getMessage(), '429') !== false) {
                    $rateLimitHit = true;
                    break;
                }
                throw $e;
            }
        }

        // Validate rate limiting behavior
        $this->assertGreaterThan(0, $requestCount, 'Should be able to make some requests');
        
        // Note: Rate limiting test is informational - Moloni's exact rate limits may vary
        // The test documents the API's rate limiting behavior for our implementation
    }

    /**
     * Test error handling contract
     * @depends testOAuthTokenEndpointContract
     */
    public function testErrorHandlingContract(): void
    {
        // Test with invalid access token
        $response = $this->httpClient->post('v1/companies/getAll', [
            'json' => [
                'access_token' => 'invalid_token'
            ]
        ]);

        $this->assertEquals(200, $response->getStatusCode()); // Moloni returns 200 even for errors
        
        $data = json_decode($response->getBody()->getContents(), true);
        
        // Validate error response structure
        $this->assertArrayHasKey('valid', $data);
        $this->assertEquals(0, $data['valid']);
        $this->assertArrayHasKey('errors', $data);
        $this->assertIsArray($data['errors']);
        $this->assertNotEmpty($data['errors']);
        
        // Check error format
        $error = $data['errors'][0];
        $this->assertIsArray($error);
        $this->assertArrayHasKey('field', $error);
        $this->assertArrayHasKey('message', $error);
    }

    /**
     * Test required fields validation contract
     */
    public function testRequiredFieldsValidationContract(): void
    {
        // Test customer creation with missing required fields
        $response = $this->httpClient->post('v1/customers/insert', [
            'json' => [
                'access_token' => 'test_token',
                'company_id' => 1
                // Missing required fields like name, vat, etc.
            ]
        ]);

        $this->assertEquals(200, $response->getStatusCode());
        
        $data = json_decode($response->getBody()->getContents(), true);
        
        // Should return validation errors
        $this->assertArrayHasKey('valid', $data);
        $this->assertEquals(0, $data['valid']);
        $this->assertArrayHasKey('errors', $data);
        $this->assertIsArray($data['errors']);
        $this->assertNotEmpty($data['errors']);
    }

    /**
     * Test field length limits contract
     * @depends testOAuthTokenEndpointContract
     */
    public function testFieldLengthLimitsContract(): void
    {
        if (!$this->accessToken) {
            $this->markTestSkipped('Access token not available');
        }

        // Test with excessively long field values
        $longString = str_repeat('A', 1000);
        
        $response = $this->httpClient->post('v1/customers/insert', [
            'json' => [
                'access_token' => $this->accessToken,
                'company_id' => 1,
                'vat' => '999999992',
                'number' => 'TEST-LONG-' . time(),
                'name' => $longString, // Excessively long name
                'email' => 'test@example.com'
            ]
        ]);

        $this->assertEquals(200, $response->getStatusCode());
        
        $data = json_decode($response->getBody()->getContents(), true);
        
        // Should either succeed with truncated data or fail with validation error
        $this->assertArrayHasKey('valid', $data);
        
        if ($data['valid'] === 0) {
            $this->assertArrayHasKey('errors', $data);
            $this->assertNotEmpty($data['errors']);
        }
    }
}