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

<?php

declare(strict_types=1);

namespace DeskMoloni\Tests\Unit;

use PHPUnit\Framework\TestCase;
use Mockery;

/**
 * Unit Test: Validation Service Business Logic
 * 
 * This test MUST FAIL initially as part of TDD methodology.
 * Tests validation rules and business logic without external dependencies.
 * 
 * @group unit
 * @group validation
 */
class ValidationServiceTest extends TestCase
{
    private \DeskMoloni\ValidationService $validationService;

    protected function setUp(): void
    {
        // This will fail initially until ValidationService is implemented
        $this->validationService = new \DeskMoloni\ValidationService();
    }

    protected function tearDown(): void
    {
        Mockery::close();
    }

    /**
     * Test client data validation rules
     * This test will initially fail until validation implementation exists
     */
    public function testClientDataValidation(): void
    {
        // Valid client data
        $validClient = [
            'company' => 'Valid Company Name',
            'vat' => '123456789',
            'email' => 'valid@example.com',
            'phone' => '+351910000000',
            'address' => 'Valid Address 123',
            'city' => 'Lisboa',
            'zip' => '1000-001',
            'country' => 'Portugal'
        ];

        $result = $this->validationService->validateClientData($validClient);

        $this->assertIsArray($result);
        $this->assertTrue($result['valid']);
        $this->assertEmpty($result['errors']);

        // Invalid client data - missing required fields
        $invalidClient = [
            'company' => '',
            'vat' => '',
            'email' => 'invalid-email',
            'phone' => 'invalid-phone'
        ];

        $result = $this->validationService->validateClientData($invalidClient);

        $this->assertIsArray($result);
        $this->assertFalse($result['valid']);
        $this->assertNotEmpty($result['errors']);
        $this->assertArrayHasKey('company', $result['errors']);
        $this->assertArrayHasKey('vat', $result['errors']);
        $this->assertArrayHasKey('email', $result['errors']);
        $this->assertArrayHasKey('phone', $result['errors']);
    }

    /**
     * Test VAT number validation for different countries
     */
    public function testVatNumberValidation(): void
    {
        $validVatNumbers = [
            'PT123456789', // Portugal
            'ES12345678Z', // Spain
            'FR12345678901', // France
            'DE123456789', // Germany
            'IT12345678901', // Italy
            '123456789' // Default format
        ];

        foreach ($validVatNumbers as $vatNumber) {
            $this->assertTrue(
                $this->validationService->isValidVAT($vatNumber),
                "VAT number should be valid: {$vatNumber}"
            );
        }

        $invalidVatNumbers = [
            '', // Empty
            '123', // Too short
            'INVALID', // Non-numeric
            'PT123', // Wrong format for Portugal
            str_repeat('1', 20) // Too long
        ];

        foreach ($invalidVatNumbers as $vatNumber) {
            $this->assertFalse(
                $this->validationService->isValidVAT($vatNumber),
                "VAT number should be invalid: {$vatNumber}"
            );
        }
    }

    /**
     * Test email validation with business rules
     */
    public function testEmailValidation(): void
    {
        $validEmails = [
            'user@example.com',
            'user.name@example.com',
            'user+tag@example.com',
            'user123@example-domain.com',
            'test@subdomain.example.com'
        ];

        foreach ($validEmails as $email) {
            $this->assertTrue(
                $this->validationService->isValidEmail($email),
                "Email should be valid: {$email}"
            );
        }

        $invalidEmails = [
            '', // Empty
            'invalid', // No @ symbol
            'invalid@', // No domain
            '@example.com', // No user
            'user@', // No domain
            'user space@example.com', // Space in email
            'user@example', // No TLD
            'user@.com' // Invalid domain
        ];

        foreach ($invalidEmails as $email) {
            $this->assertFalse(
                $this->validationService->isValidEmail($email),
                "Email should be invalid: {$email}"
            );
        }
    }

    /**
     * Test phone number validation for international formats
     */
    public function testPhoneNumberValidation(): void
    {
        $validPhoneNumbers = [
            '+351910000000', // Portugal mobile
            '+351210000000', // Portugal landline
            '+34600000000', // Spain mobile
            '+33600000000', // France mobile
            '+49170000000', // Germany mobile
            '910000000', // Local format
            '00351910000000' // International format
        ];

        foreach ($validPhoneNumbers as $phone) {
            $this->assertTrue(
                $this->validationService->isValidPhone($phone),
                "Phone number should be valid: {$phone}"
            );
        }

        $invalidPhoneNumbers = [
            '', // Empty
            '123', // Too short
            'abcdefghij', // Non-numeric
            '+351', // Incomplete
            '+351 910 000 000', // Spaces not allowed in some contexts
            str_repeat('1', 20) // Too long
        ];

        foreach ($invalidPhoneNumbers as $phone) {
            $this->assertFalse(
                $this->validationService->isValidPhone($phone),
                "Phone number should be invalid: {$phone}"
            );
        }
    }

    /**
     * Test product data validation
     */
    public function testProductDataValidation(): void
    {
        $validProduct = [
            'name' => 'Valid Product Name',
            'reference' => 'PROD-001',
            'price' => 99.99,
            'category_id' => 1,
            'has_stock' => true,
            'stock' => 10
        ];

        $result = $this->validationService->validateProductData($validProduct);

        $this->assertTrue($result['valid']);
        $this->assertEmpty($result['errors']);

        // Invalid product data
        $invalidProduct = [
            'name' => '', // Empty name
            'reference' => '', // Empty reference
            'price' => -10, // Negative price
            'category_id' => 'invalid', // Non-numeric category
            'has_stock' => 'yes', // Invalid boolean
            'stock' => -5 // Negative stock
        ];

        $result = $this->validationService->validateProductData($invalidProduct);

        $this->assertFalse($result['valid']);
        $this->assertNotEmpty($result['errors']);
        $this->assertArrayHasKey('name', $result['errors']);
        $this->assertArrayHasKey('price', $result['errors']);
        $this->assertArrayHasKey('stock', $result['errors']);
    }

    /**
     * Test invoice data validation
     */
    public function testInvoiceDataValidation(): void
    {
        $validInvoice = [
            'number' => 'INV-2025-001',
            'client_id' => 1,
            'date' => '2025-09-10',
            'due_date' => '2025-10-10',
            'currency' => 'EUR',
            'subtotal' => 100.00,
            'tax' => 23.00,
            'total' => 123.00,
            'status' => 'pending',
            'items' => [
                [
                    'product_id' => 1,
                    'quantity' => 2,
                    'price' => 50.00,
                    'discount' => 0
                ]
            ]
        ];

        $result = $this->validationService->validateInvoiceData($validInvoice);

        $this->assertTrue($result['valid']);
        $this->assertEmpty($result['errors']);

        // Invalid invoice data
        $invalidInvoice = [
            'number' => '', // Empty number
            'client_id' => 'invalid', // Non-numeric client ID
            'date' => 'invalid-date', // Invalid date format
            'due_date' => '2025-09-09', // Due date before invoice date
            'currency' => 'XXX', // Invalid currency
            'subtotal' => -100, // Negative amount
            'total' => 50, // Total less than subtotal
            'items' => [] // Empty items
        ];

        $result = $this->validationService->validateInvoiceData($invalidInvoice);

        $this->assertFalse($result['valid']);
        $this->assertNotEmpty($result['errors']);
        $this->assertArrayHasKey('number', $result['errors']);
        $this->assertArrayHasKey('client_id', $result['errors']);
        $this->assertArrayHasKey('date', $result['errors']);
        $this->assertArrayHasKey('due_date', $result['errors']);
        $this->assertArrayHasKey('items', $result['errors']);
    }

    /**
     * Test data sanitization rules
     */
    public function testDataSanitization(): void
    {
        $testCases = [
            // [input, expected_output, field_type]
            ['  Test Company  ', 'Test Company', 'company_name'],
            ['<script>alert("xss")</script>', 'alert("xss")', 'text_field'],
            ['user@EXAMPLE.COM', 'user@example.com', 'email'],
            ['+351 910 000 000', '+351910000000', 'phone'],
            ['PT 123 456 789', 'PT123456789', 'vat'],
            ['PRODUCT-001', 'PRODUCT-001', 'reference'],
            ['   MULTI   SPACE   TEXT   ', 'MULTI SPACE TEXT', 'text_field']
        ];

        foreach ($testCases as [$input, $expected, $fieldType]) {
            $sanitized = $this->validationService->sanitizeField($input, $fieldType);
            $this->assertEquals(
                $expected,
                $sanitized,
                "Field sanitization failed for type {$fieldType}: '{$input}' should become '{$expected}'"
            );
        }
    }

    /**
     * Test business rule validations
     */
    public function testBusinessRuleValidations(): void
    {
        // Test duplicate VAT validation
        $existingVats = ['123456789', '987654321'];
        $mockVatChecker = Mockery::mock('DeskMoloni\VatChecker');
        $mockVatChecker->shouldReceive('exists')->with('123456789')->andReturn(true);
        $mockVatChecker->shouldReceive('exists')->with('999999999')->andReturn(false);

        $this->validationService->setVatChecker($mockVatChecker);

        $this->assertFalse(
            $this->validationService->isUniqueVAT('123456789'),
            'Existing VAT should not be unique'
        );

        $this->assertTrue(
            $this->validationService->isUniqueVAT('999999999'),
            'New VAT should be unique'
        );

        // Test invoice number format validation
        $validInvoiceNumbers = [
            'INV-2025-001',
            'FAT-001',
            '2025001',
            'INVOICE-123'
        ];

        foreach ($validInvoiceNumbers as $number) {
            $this->assertTrue(
                $this->validationService->isValidInvoiceNumber($number),
                "Invoice number should be valid: {$number}"
            );
        }

        $invalidInvoiceNumbers = [
            '', // Empty
            '123', // Too short
            'INV-', // Incomplete
            str_repeat('A', 50) // Too long
        ];

        foreach ($invalidInvoiceNumbers as $number) {
            $this->assertFalse(
                $this->validationService->isValidInvoiceNumber($number),
                "Invoice number should be invalid: {$number}"
            );
        }
    }

    /**
     * Test field length validations
     */
    public function testFieldLengthValidations(): void
    {
        $fieldLimits = [
            'company_name' => ['max' => 255, 'min' => 1],
            'email' => ['max' => 320, 'min' => 5],
            'phone' => ['max' => 20, 'min' => 9],
            'address' => ['max' => 500, 'min' => 5],
            'product_name' => ['max' => 255, 'min' => 1],
            'invoice_notes' => ['max' => 1000, 'min' => 0]
        ];

        foreach ($fieldLimits as $fieldType => $limits) {
            // Test minimum length
            if ($limits['min'] > 0) {
                $shortValue = str_repeat('a', $limits['min'] - 1);
                $this->assertFalse(
                    $this->validationService->validateFieldLength($shortValue, $fieldType),
                    "Field {$fieldType} should reject value shorter than {$limits['min']}"
                );
            }

            // Test valid length
            $validValue = str_repeat('a', $limits['min']);
            $this->assertTrue(
                $this->validationService->validateFieldLength($validValue, $fieldType),
                "Field {$fieldType} should accept valid length value"
            );

            // Test maximum length
            $longValue = str_repeat('a', $limits['max'] + 1);
            $this->assertFalse(
                $this->validationService->validateFieldLength($longValue, $fieldType),
                "Field {$fieldType} should reject value longer than {$limits['max']}"
            );
        }
    }

    /**
     * Test currency and amount validations
     */
    public function testCurrencyAndAmountValidations(): void
    {
        $validCurrencies = ['EUR', 'USD', 'GBP', 'CHF'];
        $invalidCurrencies = ['', 'EURO', 'XXX', '123'];

        foreach ($validCurrencies as $currency) {
            $this->assertTrue(
                $this->validationService->isValidCurrency($currency),
                "Currency should be valid: {$currency}"
            );
        }

        foreach ($invalidCurrencies as $currency) {
            $this->assertFalse(
                $this->validationService->isValidCurrency($currency),
                "Currency should be invalid: {$currency}"
            );
        }

        // Test amount validations
        $validAmounts = [0, 0.01, 1.00, 999.99, 9999999.99];
        $invalidAmounts = [-1, -0.01, 'invalid', '', null];

        foreach ($validAmounts as $amount) {
            $this->assertTrue(
                $this->validationService->isValidAmount($amount),
                "Amount should be valid: {$amount}"
            );
        }

        foreach ($invalidAmounts as $amount) {
            $this->assertFalse(
                $this->validationService->isValidAmount($amount),
                "Amount should be invalid: " . var_export($amount, true)
            );
        }
    }

    /**
     * Test date validations
     */
    public function testDateValidations(): void
    {
        $validDates = [
            '2025-09-10',
            '2025-12-31',
            '2024-02-29', // Leap year
            date('Y-m-d') // Current date
        ];

        foreach ($validDates as $date) {
            $this->assertTrue(
                $this->validationService->isValidDate($date),
                "Date should be valid: {$date}"
            );
        }

        $invalidDates = [
            '', // Empty
            'invalid-date',
            '2025-13-01', // Invalid month
            '2025-02-30', // Invalid day
            '2023-02-29', // Not a leap year
            '10/09/2025', // Wrong format
            '2025/09/10' // Wrong format
        ];

        foreach ($invalidDates as $date) {
            $this->assertFalse(
                $this->validationService->isValidDate($date),
                "Date should be invalid: {$date}"
            );
        }

        // Test date range validation
        $startDate = '2025-09-01';
        $endDate = '2025-09-30';

        $this->assertTrue(
            $this->validationService->isValidDateRange($startDate, $endDate),
            'Valid date range should be accepted'
        );

        $this->assertFalse(
            $this->validationService->isValidDateRange($endDate, $startDate),
            'Invalid date range (end before start) should be rejected'
        );
    }

    /**
     * Test composite validation rules
     */
    public function testCompositeValidationRules(): void
    {
        // Test invoice total calculation validation
        $invoiceData = [
            'subtotal' => 100.00,
            'tax_rate' => 23.0,
            'tax_amount' => 23.00,
            'discount' => 10.00,
            'total' => 113.00
        ];

        $this->assertTrue(
            $this->validationService->validateInvoiceTotals($invoiceData),
            'Correct invoice totals should validate'
        );

        $invoiceData['total'] = 150.00; // Incorrect total

        $this->assertFalse(
            $this->validationService->validateInvoiceTotals($invoiceData),
            'Incorrect invoice totals should not validate'
        );

        // Test client address validation
        $addressData = [
            'street' => 'Rua de Teste, 123',
            'city' => 'Lisboa',
            'zip' => '1000-001',
            'country' => 'Portugal'
        ];

        $this->assertTrue(
            $this->validationService->validateAddress($addressData),
            'Complete address should validate'
        );

        unset($addressData['city']);

        $this->assertFalse(
            $this->validationService->validateAddress($addressData),
            'Incomplete address should not validate'
        );
    }

    /**
     * Test validation error message formatting
     */
    public function testValidationErrorMessageFormatting(): void
    {
        $errors = [
            'company' => 'Company name is required',
            'email' => 'Email format is invalid',
            'vat' => 'VAT number must be 9 digits'
        ];

        $formatted = $this->validationService->formatValidationErrors($errors);

        $this->assertIsArray($formatted);
        $this->assertCount(3, $formatted);

        foreach ($formatted as $error) {
            $this->assertArrayHasKey('field', $error);
            $this->assertArrayHasKey('message', $error);
            $this->assertArrayHasKey('code', $error);
        }

        // Test localized error messages
        $localizedErrors = $this->validationService->formatValidationErrors($errors, 'pt');
        
        $this->assertIsArray($localizedErrors);
        $this->assertNotEquals($formatted, $localizedErrors, 'Localized errors should be different');
    }
}