<?php

declare(strict_types=1);

use App\Framework\Reflection\CachedReflectionProvider;
use App\Framework\Validation\GroupAware;
use App\Framework\Validation\Rules\Email;
use App\Framework\Validation\Rules\Required;
use App\Framework\Validation\Rules\StringLength;
use App\Framework\Validation\ValidationResult;
use App\Framework\Validation\ValidationRule;
use App\Framework\Validation\Validator;

beforeEach(function () {
    $reflectionProvider = new CachedReflectionProvider();
    $this->validator = new Validator($reflectionProvider);
});

test('validation passes for valid object', function () {
    $object = new ValidTestObject('test@example.com', 'John Doe');
    $result = $this->validator->validate($object);

    expect($result->hasErrors())->toBeFalse();
});

test('validation fails for invalid email', function () {
    $object = new ValidTestObject('invalid-email', 'John Doe');
    $result = $this->validator->validate($object);

    expect($result->hasErrors())->toBeTrue()
        ->and($result->getFieldErrors('email'))->not->toBeEmpty();
});

test('validation fails for empty required field', function () {
    $object = new ValidTestObject('test@example.com', '');
    $result = $this->validator->validate($object);

    expect($result->hasErrors())->toBeTrue()
        ->and($result->getFieldErrors('name'))->not->toBeEmpty();
});

test('validation fails for too short string', function () {
    $object = new ValidTestObject('test@example.com', 'Jo'); // Too short
    $result = $this->validator->validate($object);

    expect($result->hasErrors())->toBeTrue()
        ->and($result->getFieldErrors('name'))->not->toBeEmpty();
});

test('validation with groups only validates specified group', function () {
    $object = new GroupedTestObject('test@example.com', '');

    // Validate only 'basic' group - should pass
    $result = $this->validator->validate($object, 'basic');
    expect($result->hasErrors())->toBeFalse();

    // Validate 'extended' group - should fail due to empty name
    $result = $this->validator->validate($object, 'extended');
    expect($result->hasErrors())->toBeTrue()
        ->and($result->getFieldErrors('name'))->not->toBeEmpty();
});

test('validation handles uninitialized non-nullable properties', function () {
    $object = new UninitializedTestObject();
    $result = $this->validator->validate($object);

    expect($result->hasErrors())->toBeTrue()
        ->and($result->getFieldErrors('requiredField'))->not->toBeEmpty();
});

test('validation handles nullable properties correctly', function () {
    $object = new NullableTestObject();
    $result = $this->validator->validate($object);

    expect($result->hasErrors())->toBeFalse();
});

test('multiple validation errors are collected', function () {
    $object = new ValidTestObject('invalid', ''); // Both email and name invalid
    $result = $this->validator->validate($object);

    expect($result->hasErrors())->toBeTrue()
        ->and($result->getFieldErrors('email'))->not->toBeEmpty()
        ->and($result->getFieldErrors('name'))->not->toBeEmpty()
        ->and($result->getAllErrorMessages())->toHaveCount(2);
});

test('validation result can be merged', function () {
    $result1 = new ValidationResult();
    $result1->addError('field1', 'Error 1');

    $result2 = new ValidationResult();
    $result2->addError('field2', 'Error 2');

    $merged = $result1->merge($result2);

    expect($merged->getFieldErrors('field1'))->toContain('Error 1')
        ->and($merged->getFieldErrors('field2'))->toContain('Error 2')
        ->and($merged->getAllErrorMessages())->toHaveCount(2);
});

// Test fixtures
class ValidTestObject
{
    public function __construct(
        #[Email]
        public string $email,
        #[Required]
        #[StringLength(min: 3, max: 50)]
        public string $name
    ) {
    }
}

class GroupedTestObject
{
    public function __construct(
        #[Email]
        #[TestValidationGroup('basic')]
        public string $email,
        #[Required]
        #[TestValidationGroup('extended')]
        public string $name
    ) {
    }
}

class UninitializedTestObject
{
    #[Required]
    public string $requiredField;
}

class NullableTestObject
{
    #[Email]
    public ?string $optionalEmail = null;
}

// Custom validation rule for testing groups
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class TestValidationGroup implements ValidationRule, GroupAware
{
    public function __construct(
        private string $group
    ) {
    }

    public function validate(mixed $value): bool
    {
        return true; // Always pass - we're just testing group functionality
    }

    public function getErrorMessages(): array
    {
        return [];
    }

    public function belongsToGroup(string $group): bool
    {
        return $this->group === $group;
    }
}
