/**
 * Descomplicar® Crescimento Digital
 * https://descomplicar.pt
 */

<?php

defined('BASEPATH') or exit('No direct script access allowed');

/**
 * Moloni API Contract Tests
 * 
 * Verifies that API implementation matches the OpenAPI specification
 * Tests all endpoints defined in moloni-api.yaml
 * 
 * @package     DeskMoloni
 * @author      Descomplicar®
 * @copyright   2025 Descomplicar
 * @version     3.0.0
 */
class MoloniApiContractTest extends PHPUnit\Framework\TestCase
{
    private $CI;
    private $api_client;
    private $contract_spec;
    private $test_company_id;
    
    protected function setUp(): void
    {
        // Get CodeIgniter instance
        $this->CI = &get_instance();
        
        // Load API client
        $this->CI->load->library('desk_moloni/moloniapiclient');
        $this->api_client = $this->CI->moloniapiclient;
        
        // Load contract specification
        $this->loadContractSpec();
        
        // Test company ID
        $this->test_company_id = 12345;
        
        // Set up authentication for contract tests
        $this->setupContractAuth();
    }
    
    /**
     * Load OpenAPI contract specification
     */
    private function loadContractSpec()
    {
        $spec_file = FCPATH . '../specs/001-desk-moloni-integration/contracts/moloni-api.yaml';
        
        if (file_exists($spec_file)) {
            $this->contract_spec = yaml_parse_file($spec_file);
        } else {
            // Fallback to embedded spec for testing
            $this->contract_spec = $this->getEmbeddedSpec();
        }
    }
    
    /**
     * Set up authentication for contract testing
     */
    private function setupContractAuth()
    {
        $this->CI->load->library('desk_moloni/molonioauth');
        $this->CI->load->library('desk_moloni/tokenmanager');
        
        // Configure OAuth
        $this->CI->molonioauth->configure('test_client_id', 'test_client_secret');
        
        // Add mock tokens
        $this->CI->tokenmanager->save_tokens([
            'access_token' => 'contract_test_token',
            'expires_in' => 3600
        ]);
    }
    
    /**
     * Test OAuth 2.0 Token Exchange endpoint
     * POST /oauth2/token
     */
    public function testOAuthTokenExchangeContract()
    {
        $endpoint_spec = $this->getEndpointSpec('post', '/oauth2/token');
        
        // Test authorization_code grant
        $auth_code_data = [
            'grant_type' => 'authorization_code',
            'code' => 'test_auth_code',
            'client_id' => 'test_client_id',
            'client_secret' => 'test_client_secret',
            'redirect_uri' => 'https://test.com/callback'
        ];
        
        $this->validateRequestSchema($auth_code_data, $endpoint_spec['requestBody']);
        
        // Test refresh_token grant
        $refresh_token_data = [
            'grant_type' => 'refresh_token',
            'refresh_token' => 'test_refresh_token',
            'client_id' => 'test_client_id',
            'client_secret' => 'test_client_secret'
        ];
        
        $this->validateRequestSchema($refresh_token_data, $endpoint_spec['requestBody']);
        
        // Validate response schema
        $mock_response = [
            'access_token' => 'access_token_value',
            'token_type' => 'Bearer',
            'expires_in' => 3600,
            'refresh_token' => 'refresh_token_value',
            'scope' => 'read write'
        ];
        
        $this->validateResponseSchema($mock_response, $endpoint_spec['responses']['200']);
    }
    
    /**
     * Test Customers List endpoint
     * GET /customers
     */
    public function testCustomersListContract()
    {
        $endpoint_spec = $this->getEndpointSpec('get', '/customers');
        
        // Test required parameters
        $params = [
            'company_id' => $this->test_company_id,
            'qty' => 50,
            'offset' => 0
        ];
        
        $this->validateQueryParameters($params, $endpoint_spec['parameters']);
        
        // Validate response structure
        $mock_customers = [
            [
                'customer_id' => 1,
                'number' => 'CUST001',
                'name' => 'Test Customer',
                'vat' => '123456789',
                'email' => 'test@example.com',
                'phone' => '+351912345678',
                'address' => 'Test Address',
                'zip_code' => '4000-000',
                'city' => 'Porto',
                'country_id' => 1,
                'website' => 'https://example.com',
                'notes' => 'Test notes'
            ]
        ];
        
        $this->validateResponseSchema($mock_customers, $endpoint_spec['responses']['200']);
    }
    
    /**
     * Test Customer Creation endpoint
     * POST /customers
     */
    public function testCustomerCreateContract()
    {
        $endpoint_spec = $this->getEndpointSpec('post', '/customers');
        
        // Test valid customer creation data
        $customer_data = [
            'company_id' => $this->test_company_id,
            'name' => 'New Test Customer',
            'vat' => '987654321',
            'email' => 'newcustomer@example.com',
            'phone' => '+351987654321',
            'address' => 'New Address',
            'zip_code' => '1000-000',
            'city' => 'Lisboa',
            'country_id' => 1,
            'website' => 'https://newcustomer.com',
            'notes' => 'New customer notes'
        ];
        
        $this->validateRequestSchema($customer_data, $endpoint_spec['requestBody']);
        
        // Test required fields validation
        $required_fields = ['company_id', 'name', 'vat'];
        foreach ($required_fields as $field) {
            $this->assertArrayHasKey($field, $customer_data, "Required field '{$field}' missing");
        }
        
        // Test response schema
        $mock_response = $customer_data;
        $mock_response['customer_id'] = 123;
        $mock_response['number'] = 'CUST123';
        
        $this->validateResponseSchema($mock_response, $endpoint_spec['responses']['201']);
    }
    
    /**
     * Test Customer Get endpoint
     * GET /customers/{customer_id}
     */
    public function testCustomerGetContract()
    {
        $endpoint_spec = $this->getEndpointSpec('get', '/customers/{customer_id}');
        
        // Test path parameters
        $customer_id = 123;
        $this->assertIsInt($customer_id);
        
        // Test query parameters
        $params = [
            'company_id' => $this->test_company_id
        ];
        
        $this->validateQueryParameters($params, $endpoint_spec['parameters']);
        
        // Test response schema
        $mock_customer = [
            'customer_id' => $customer_id,
            'number' => 'CUST123',
            'name' => 'Retrieved Customer',
            'vat' => '123456789',
            'email' => 'customer@example.com',
            'phone' => '+351912345678',
            'address' => 'Customer Address',
            'zip_code' => '4000-000',
            'city' => 'Porto',
            'country_id' => 1,
            'website' => 'https://customer.com',
            'notes' => 'Customer notes'
        ];
        
        $this->validateResponseSchema($mock_customer, $endpoint_spec['responses']['200']);
    }
    
    /**
     * Test Customer Update endpoint
     * PUT /customers/{customer_id}
     */
    public function testCustomerUpdateContract()
    {
        $endpoint_spec = $this->getEndpointSpec('put', '/customers/{customer_id}');
        
        // Test update data
        $update_data = [
            'customer_id' => 123,
            'company_id' => $this->test_company_id,
            'name' => 'Updated Customer Name',
            'email' => 'updated@example.com',
            'phone' => '+351999888777',
            'address' => 'Updated Address',
            'city' => 'Braga',
            'notes' => 'Updated notes'
        ];
        
        $this->validateRequestSchema($update_data, $endpoint_spec['requestBody']);
        
        // Test required fields for update
        $required_fields = ['customer_id', 'company_id'];
        foreach ($required_fields as $field) {
            $this->assertArrayHasKey($field, $update_data, "Required field '{$field}' missing");
        }
        
        // Test response schema
        $this->validateResponseSchema($update_data, $endpoint_spec['responses']['200']);
    }
    
    /**
     * Test Products List endpoint
     * GET /products
     */
    public function testProductsListContract()
    {
        $endpoint_spec = $this->getEndpointSpec('get', '/products');
        
        // Test parameters
        $params = [
            'company_id' => $this->test_company_id
        ];
        
        $this->validateQueryParameters($params, $endpoint_spec['parameters']);
        
        // Test response schema
        $mock_products = [
            [
                'product_id' => 1,
                'name' => 'Test Product',
                'summary' => 'Product description',
                'reference' => 'PROD001',
                'price' => 99.99,
                'unit_id' => 1,
                'has_stock' => 0,
                'stock' => 0.0,
                'minimum_stock' => 0.0
            ]
        ];
        
        $this->validateResponseSchema($mock_products, $endpoint_spec['responses']['200']);
    }
    
    /**
     * Test Product Creation endpoint
     * POST /products
     */
    public function testProductCreateContract()
    {
        $endpoint_spec = $this->getEndpointSpec('post', '/products');
        
        // Test product creation data
        $product_data = [
            'company_id' => $this->test_company_id,
            'name' => 'New Product',
            'summary' => 'New product description',
            'reference' => 'NEWPROD001',
            'price' => 149.99,
            'unit_id' => 1,
            'has_stock' => 0
        ];
        
        $this->validateRequestSchema($product_data, $endpoint_spec['requestBody']);
        
        // Test required fields
        $required_fields = ['company_id', 'name', 'price'];
        foreach ($required_fields as $field) {
            $this->assertArrayHasKey($field, $product_data, "Required field '{$field}' missing");
        }
        
        // Test price is numeric
        $this->assertIsNumeric($product_data['price']);
        
        // Test response schema
        $mock_response = $product_data;
        $mock_response['product_id'] = 456;
        
        $this->validateResponseSchema($mock_response, $endpoint_spec['responses']['201']);
    }
    
    /**
     * Test Invoice Creation endpoint
     * POST /invoices
     */
    public function testInvoiceCreateContract()
    {
        $endpoint_spec = $this->getEndpointSpec('post', '/invoices');
        
        // Test invoice creation data
        $invoice_data = [
            'company_id' => $this->test_company_id,
            'customer_id' => 123,
            'date' => date('Y-m-d'),
            'expiration_date' => date('Y-m-d', strtotime('+30 days')),
            'document_set_id' => 1,
            'products' => [
                [
                    'product_id' => 1,
                    'name' => 'Invoice Product',
                    'summary' => 'Product for invoice',
                    'qty' => 2.0,
                    'price' => 50.0,
                    'discount' => 0.0,
                    'tax' => 23.0
                ]
            ],
            'notes' => 'Invoice notes'
        ];
        
        $this->validateRequestSchema($invoice_data, $endpoint_spec['requestBody']);
        
        // Test required fields
        $required_fields = ['company_id', 'customer_id', 'date', 'products'];
        foreach ($required_fields as $field) {
            $this->assertArrayHasKey($field, $invoice_data, "Required field '{$field}' missing");
        }
        
        // Test products array
        $this->assertIsArray($invoice_data['products']);
        $this->assertNotEmpty($invoice_data['products']);
        
        // Test product structure
        $product = $invoice_data['products'][0];
        $product_required_fields = ['product_id', 'name', 'qty', 'price'];
        foreach ($product_required_fields as $field) {
            $this->assertArrayHasKey($field, $product, "Product required field '{$field}' missing");
        }
        
        // Test response schema
        $mock_response = [
            'document_id' => 789,
            'number' => 'INV001/2025',
            'date' => $invoice_data['date'],
            'customer_id' => $invoice_data['customer_id'],
            'net_value' => 100.0,
            'tax_value' => 23.0,
            'gross_value' => 123.0,
            'status' => 1,
            'products' => [
                [
                    'product_id' => 1,
                    'name' => 'Invoice Product',
                    'summary' => 'Product for invoice',
                    'qty' => 2.0,
                    'price' => 50.0,
                    'discount' => 0.0,
                    'tax' => 23.0
                ]
            ]
        ];
        
        $this->validateResponseSchema($mock_response, $endpoint_spec['responses']['201']);
    }
    
    /**
     * Test Invoice PDF endpoint
     * GET /invoices/{invoice_id}/getPDF
     */
    public function testInvoicePdfContract()
    {
        $endpoint_spec = $this->getEndpointSpec('get', '/invoices/{invoice_id}/getPDF');
        
        // Test path parameters
        $invoice_id = 789;
        $this->assertIsInt($invoice_id);
        
        // Test query parameters
        $params = [
            'company_id' => $this->test_company_id
        ];
        
        $this->validateQueryParameters($params, $endpoint_spec['parameters']);
        
        // For PDF response, we test that it would return binary data
        // In actual implementation, this would be validated differently
        $this->assertTrue(true, 'PDF endpoint contract structure verified');
    }
    
    /**
     * Test API base URL and versioning
     */
    public function testApiBaseUrlContract()
    {
        $expected_base_url = 'https://api.moloni.pt/v1';
        $spec_servers = $this->contract_spec['servers'];
        
        $this->assertNotEmpty($spec_servers);
        $this->assertEquals($expected_base_url, $spec_servers[0]['url']);
    }
    
    /**
     * Test OAuth 2.0 security scheme
     */
    public function testOAuth2SecurityScheme()
    {
        $security_schemes = $this->contract_spec['components']['securitySchemes'];
        
        $this->assertArrayHasKey('oauth2', $security_schemes);
        
        $oauth2_scheme = $security_schemes['oauth2'];
        $this->assertEquals('oauth2', $oauth2_scheme['type']);
        $this->assertArrayHasKey('flows', $oauth2_scheme);
        $this->assertArrayHasKey('authorizationCode', $oauth2_scheme['flows']);
        
        $auth_code_flow = $oauth2_scheme['flows']['authorizationCode'];
        $this->assertEquals('https://api.moloni.pt/v1/oauth2/authorize', $auth_code_flow['authorizationUrl']);
        $this->assertEquals('https://api.moloni.pt/v1/oauth2/token', $auth_code_flow['tokenUrl']);
    }
    
    /**
     * Test all schema definitions exist
     */
    public function testSchemaDefinitions()
    {
        $schemas = $this->contract_spec['components']['schemas'];
        
        $required_schemas = [
            'TokenResponse',
            'Customer', 'CustomerCreate', 'CustomerUpdate',
            'Product', 'ProductCreate',
            'Invoice', 'InvoiceCreate',
            'InvoiceProduct', 'InvoiceProductCreate'
        ];
        
        foreach ($required_schemas as $schema) {
            $this->assertArrayHasKey($schema, $schemas, "Schema '{$schema}' not defined");
            $this->assertArrayHasKey('type', $schemas[$schema], "Schema '{$schema}' missing type");
            $this->assertArrayHasKey('properties', $schemas[$schema], "Schema '{$schema}' missing properties");
        }
    }
    
    /**
     * Test API client implementation matches contract
     */
    public function testApiClientMethodsMatchContract()
    {
        $paths = $this->contract_spec['paths'];
        
        // Verify API client has methods for all endpoints
        $this->assertTrue(method_exists($this->api_client, 'list_customers'));
        $this->assertTrue(method_exists($this->api_client, 'get_customer'));
        $this->assertTrue(method_exists($this->api_client, 'create_customer'));
        $this->assertTrue(method_exists($this->api_client, 'update_customer'));
        $this->assertTrue(method_exists($this->api_client, 'list_products'));
        $this->assertTrue(method_exists($this->api_client, 'create_product'));
        $this->assertTrue(method_exists($this->api_client, 'create_invoice'));
        $this->assertTrue(method_exists($this->api_client, 'get_invoice_pdf'));
    }
    
    /**
     * Validate request data against schema
     */
    private function validateRequestSchema($data, $request_body_spec)
    {
        if (!isset($request_body_spec['content']['application/json']['schema'])) {
            return; // No schema to validate against
        }
        
        $schema_ref = $request_body_spec['content']['application/json']['schema']['$ref'] ?? null;
        
        if ($schema_ref) {
            $schema_name = str_replace('#/components/schemas/', '', $schema_ref);
            $schema = $this->contract_spec['components']['schemas'][$schema_name];
            
            $this->validateDataAgainstSchema($data, $schema);
        }
    }
    
    /**
     * Validate response data against schema
     */
    private function validateResponseSchema($data, $response_spec)
    {
        if (!isset($response_spec['content']['application/json']['schema'])) {
            return; // No schema to validate against
        }
        
        $schema = $response_spec['content']['application/json']['schema'];
        
        if (isset($schema['type']) && $schema['type'] === 'array') {
            $this->assertIsArray($data);
            if (isset($schema['items']['$ref'])) {
                $item_schema_name = str_replace('#/components/schemas/', '', $schema['items']['$ref']);
                $item_schema = $this->contract_spec['components']['schemas'][$item_schema_name];
                
                if (!empty($data)) {
                    $this->validateDataAgainstSchema($data[0], $item_schema);
                }
            }
        } elseif (isset($schema['$ref'])) {
            $schema_name = str_replace('#/components/schemas/', '', $schema['$ref']);
            $schema_def = $this->contract_spec['components']['schemas'][$schema_name];
            
            $this->validateDataAgainstSchema($data, $schema_def);
        }
    }
    
    /**
     * Validate query parameters
     */
    private function validateQueryParameters($params, $parameters_spec)
    {
        foreach ($parameters_spec as $param_spec) {
            if ($param_spec['in'] === 'query' && isset($param_spec['required']) && $param_spec['required']) {
                $param_name = $param_spec['name'];
                $this->assertArrayHasKey($param_name, $params, "Required query parameter '{$param_name}' missing");
            }
        }
    }
    
    /**
     * Validate data against schema definition
     */
    private function validateDataAgainstSchema($data, $schema)
    {
        $this->assertIsArray($data);
        
        if (isset($schema['required'])) {
            foreach ($schema['required'] as $required_field) {
                $this->assertArrayHasKey($required_field, $data, "Required field '{$required_field}' missing");
            }
        }
        
        if (isset($schema['properties'])) {
            foreach ($data as $field => $value) {
                if (isset($schema['properties'][$field])) {
                    $field_schema = $schema['properties'][$field];
                    $this->validateFieldType($value, $field_schema, $field);
                }
            }
        }
    }
    
    /**
     * Validate field type against schema
     */
    private function validateFieldType($value, $field_schema, $field_name)
    {
        if (!isset($field_schema['type'])) {
            return; // No type constraint
        }
        
        switch ($field_schema['type']) {
            case 'string':
                $this->assertIsString($value, "Field '{$field_name}' should be string");
                break;
            case 'integer':
                $this->assertIsInt($value, "Field '{$field_name}' should be integer");
                break;
            case 'number':
                $this->assertIsNumeric($value, "Field '{$field_name}' should be numeric");
                break;
            case 'array':
                $this->assertIsArray($value, "Field '{$field_name}' should be array");
                break;
        }
        
        // Validate format if specified
        if (isset($field_schema['format'])) {
            switch ($field_schema['format']) {
                case 'date':
                    $this->assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2}$/', $value, "Field '{$field_name}' should be valid date");
                    break;
                case 'email':
                    $this->assertFilter($value, FILTER_VALIDATE_EMAIL, "Field '{$field_name}' should be valid email");
                    break;
            }
        }
    }
    
    /**
     * Get endpoint specification from contract
     */
    private function getEndpointSpec($method, $path)
    {
        $paths = $this->contract_spec['paths'];
        
        $this->assertArrayHasKey($path, $paths, "Endpoint '{$path}' not found in contract");
        $this->assertArrayHasKey($method, $paths[$path], "Method '{$method}' not found for endpoint '{$path}'");
        
        return $paths[$path][$method];
    }
    
    /**
     * Get embedded specification for testing when file is not available
     */
    private function getEmbeddedSpec()
    {
        return [
            'openapi' => '3.0.3',
            'info' => [
                'title' => 'Moloni API Integration Contract',
                'version' => '3.0.0'
            ],
            'servers' => [
                ['url' => 'https://api.moloni.pt/v1']
            ],
            'paths' => [
                '/oauth2/token' => [
                    'post' => [
                        'operationId' => 'exchangeToken',
                        'requestBody' => [
                            'content' => [
                                'application/x-www-form-urlencoded' => [
                                    'schema' => [
                                        'type' => 'object',
                                        'properties' => [
                                            'grant_type' => ['type' => 'string'],
                                            'code' => ['type' => 'string'],
                                            'client_id' => ['type' => 'string'],
                                            'client_secret' => ['type' => 'string']
                                        ]
                                    ]
                                ]
                            ]
                        ],
                        'responses' => [
                            '200' => [
                                'content' => [
                                    'application/json' => [
                                        'schema' => ['$ref' => '#/components/schemas/TokenResponse']
                                    ]
                                ]
                            ]
                        ]
                    ]
                ],
                '/customers' => [
                    'get' => [
                        'operationId' => 'listCustomers',
                        'parameters' => [
                            [
                                'name' => 'company_id',
                                'in' => 'query',
                                'required' => true,
                                'schema' => ['type' => 'integer']
                            ]
                        ],
                        'responses' => [
                            '200' => [
                                'content' => [
                                    'application/json' => [
                                        'schema' => [
                                            'type' => 'array',
                                            'items' => ['$ref' => '#/components/schemas/Customer']
                                        ]
                                    ]
                                ]
                            ]
                        ]
                    ],
                    'post' => [
                        'operationId' => 'createCustomer',
                        'requestBody' => [
                            'content' => [
                                'application/json' => [
                                    'schema' => ['$ref' => '#/components/schemas/CustomerCreate']
                                ]
                            ]
                        ],
                        'responses' => [
                            '201' => [
                                'content' => [
                                    'application/json' => [
                                        'schema' => ['$ref' => '#/components/schemas/Customer']
                                    ]
                                ]
                            ]
                        ]
                    ]
                ]
                // Additional endpoints would be defined here...
            ],
            'components' => [
                'securitySchemes' => [
                    'oauth2' => [
                        'type' => 'oauth2',
                        'flows' => [
                            'authorizationCode' => [
                                'authorizationUrl' => 'https://api.moloni.pt/v1/oauth2/authorize',
                                'tokenUrl' => 'https://api.moloni.pt/v1/oauth2/token'
                            ]
                        ]
                    ]
                ],
                'schemas' => [
                    'TokenResponse' => [
                        'type' => 'object',
                        'properties' => [
                            'access_token' => ['type' => 'string'],
                            'token_type' => ['type' => 'string'],
                            'expires_in' => ['type' => 'integer'],
                            'refresh_token' => ['type' => 'string']
                        ]
                    ],
                    'Customer' => [
                        'type' => 'object',
                        'properties' => [
                            'customer_id' => ['type' => 'integer'],
                            'name' => ['type' => 'string'],
                            'vat' => ['type' => 'string'],
                            'email' => ['type' => 'string']
                        ]
                    ],
                    'CustomerCreate' => [
                        'type' => 'object',
                        'required' => ['company_id', 'name', 'vat'],
                        'properties' => [
                            'company_id' => ['type' => 'integer'],
                            'name' => ['type' => 'string'],
                            'vat' => ['type' => 'string'],
                            'email' => ['type' => 'string']
                        ]
                    ]
                ]
            ]
        ];
    }
}