<?php

app_require('modules/selfserve/classes/selfserve_config_versioning.php');
app_require('objects/selfserve_config_versions_o.php');

use modules\selfserve\classes\selfserve_config_versioning;
use objects\selfserve_config_versions_o;

function selfserve_config_versioning_without_constructor(): selfserve_config_versioning
{
    $reflection = new ReflectionClass(selfserve_config_versioning::class);
    return $reflection->newInstanceWithoutConstructor();
}

it('validates typed task gates for known references', function (): void {
    $service = selfserve_config_versioning_without_constructor();

    $validation = $service->validateConfig([
        'questions' => [
            ['id' => 10],
        ],
        'conditions' => [
            ['id' => 20],
        ],
        'rules' => [],
        'tasks' => [
            [
                'id' => 100,
                'gate_type' => 'ALWAYS',
                'gate_ref_id' => null,
            ],
            [
                'id' => 101,
                'gate_type' => 'CONDITION',
                'gate_ref_id' => 20,
            ],
            [
                'id' => 102,
                'gate_type' => 'QUESTION',
                'gate_ref_id' => 10,
            ],
        ],
    ]);

    expect($validation['valid'])->toBeTrue();
    expect($validation['errors'])->toBe([]);
});

it('fails validation when typed task gates reference unknown entities', function (): void {
    $service = selfserve_config_versioning_without_constructor();

    $validation = $service->validateConfig([
        'questions' => [
            ['id' => 1],
        ],
        'conditions' => [
            ['id' => 2],
        ],
        'rules' => [],
        'tasks' => [
            [
                'id' => 200,
                'gate_type' => 'CONDITION',
                'gate_ref_id' => 999,
            ],
            [
                'id' => 201,
                'gate_type' => 'QUESTION',
                'gate_ref_id' => 998,
            ],
            [
                'id' => 202,
                'gate_type' => 'QUESTION',
                'gate_ref_id' => null,
            ],
        ],
    ]);

    expect($validation['valid'])->toBeFalse();
    expect(implode("\n", $validation['errors']))->toContain('unknown condition gate_ref_id 999');
    expect(implode("\n", $validation['errors']))->toContain('unknown question gate_ref_id 998');
    expect(implode("\n", $validation['errors']))->toContain('requires gate_ref_id');
});

it('fails validation when nested conditions form cycles', function (): void {
    $service = selfserve_config_versioning_without_constructor();

    $validation = $service->validateConfig([
        'questions' => [],
        'conditions' => [
            ['id' => 10, 'condition_id' => 12],
            ['id' => 11, 'condition_id' => 10],
            ['id' => 12, 'condition_id' => 11],
        ],
        'rules' => [],
        'tasks' => [],
    ]);

    expect($validation['valid'])->toBeFalse();
    expect(implode("\n", $validation['errors']))->toContain('Condition cycle detected');
});

it('migrates legacy AND and OR rules into grouped v2 condition expressions', function (): void {
    $service = selfserve_config_versioning_without_constructor();

    $config = $service->migrateLegacyConfigToV2([
        'department_id' => 6,
        'questions' => [
            ['id' => 1],
            ['id' => 2],
            ['id' => 3],
        ],
        'conditions' => [
            ['id' => 10, 'name' => 'Ready'],
        ],
        'rules' => [
            ['id' => 100, 'condition_id' => 10, 'type' => 'IS_TRUE', 'object_type' => 'question', 'object_id' => 1],
            ['id' => 101, 'condition_id' => 10, 'type' => 'IS_TRUE_OR_ANY_TRUE', 'object_type' => 'question', 'object_id' => 2],
            ['id' => 102, 'condition_id' => 10, 'type' => 'IS_TRUE_OR_ANY_TRUE', 'object_type' => 'question', 'object_id' => 3],
        ],
        'tasks' => [],
    ]);

    $expression = $config['conditions'][0]['expression'];

    expect($config['schema_version'])->toBe(2);
    expect($config['rules'])->toBe([]);
    expect($expression['operator'])->toBe('ALL');
    expect($expression['children'][0])->toMatchArray([
        'type' => 'predicate',
        'subject_type' => 'question',
        'subject_id' => 1,
        'operator' => 'IS_TRUE',
    ]);
    expect($expression['children'][1]['operator'])->toBe('ANY');
    expect(array_column($expression['children'][1]['children'], 'subject_id'))->toBe([2, 3]);
});

it('repairs legacy-defaulted always task gates during v2 normalization', function (): void {
    $service = selfserve_config_versioning_without_constructor();

    $config = $service->migrateLegacyConfigToV2([
        'department_id' => 6,
        'questions' => [
            ['id' => 1],
        ],
        'conditions' => [
            ['id' => 20, 'name' => 'Machine wash allowed'],
        ],
        'rules' => [
            ['id' => 100, 'condition_id' => 20, 'type' => 'IS_TRUE', 'object_type' => 'question', 'object_id' => 1],
        ],
        'tasks' => [
            [
                'id' => 200,
                'condition_id' => 20,
                'gate_type' => 'ALWAYS',
                'gate_ref_id' => null,
            ],
        ],
    ]);

    expect($config['tasks'][0]['gate_type'])->toBe('CONDITION');
    expect($config['tasks'][0]['gate_ref_id'])->toBe(20);
    expect($service->validateConfig($config)['valid'])->toBeTrue();
});

it('does not let legacy-defaulted always task gates bypass validation', function (): void {
    $service = selfserve_config_versioning_without_constructor();

    $validation = $service->validateConfig([
        'schema_version' => 2,
        'questions' => [
            ['id' => 1],
        ],
        'conditions' => [
            [
                'id' => 20,
                'expression' => [
                    'type' => 'predicate',
                    'subject_type' => 'question',
                    'subject_id' => 1,
                    'operator' => 'IS_TRUE',
                ],
            ],
        ],
        'tasks' => [
            [
                'id' => 201,
                'condition_id' => 999,
                'gate_type' => 'ALWAYS',
                'gate_ref_id' => null,
            ],
        ],
    ]);

    expect($validation['valid'])->toBeFalse();
    expect(implode("\n", $validation['errors']))->toContain('unknown question gate_ref_id 999');
});

it('rejects unsupported legacy task-target rules after migration', function (): void {
    $service = selfserve_config_versioning_without_constructor();

    $config = $service->migrateLegacyConfigToV2([
        'department_id' => 6,
        'questions' => [['id' => 1]],
        'conditions' => [['id' => 10, 'name' => 'Ready']],
        'rules' => [
            ['id' => 100, 'condition_id' => 10, 'type' => 'IS_TRUE', 'object_type' => 'task', 'object_id' => 50],
        ],
        'tasks' => [['id' => 50]],
    ]);
    $validation = $service->validateConfig($config);

    expect($validation['valid'])->toBeFalse();
    expect(implode("\n", $validation['errors']))->toContain('unsupported object_type `task`');
});

it('validates v2 expressions for empty used conditions, missing refs, invalid operators, and cycles', function (): void {
    $service = selfserve_config_versioning_without_constructor();

    $validation = $service->validateConfig([
        'schema_version' => 2,
        'questions' => [
            ['id' => 1, 'condition_id' => 10],
        ],
        'conditions' => [
            ['id' => 10, 'expression' => ['type' => 'group', 'operator' => 'ALL', 'children' => []]],
            [
                'id' => 20,
                'expression' => [
                    'type' => 'group',
                    'operator' => 'ALL',
                    'children' => [
                        ['type' => 'predicate', 'subject_type' => 'question', 'subject_id' => 999, 'operator' => 'IS_TRUE'],
                        ['type' => 'predicate', 'subject_type' => 'condition', 'subject_id' => 30, 'operator' => 'IS_TRUE'],
                    ],
                ],
            ],
            [
                'id' => 30,
                'expression' => [
                    'type' => 'group',
                    'operator' => 'ALL',
                    'children' => [
                        ['type' => 'predicate', 'subject_type' => 'condition', 'subject_id' => 20, 'operator' => 'NOPE'],
                    ],
                ],
            ],
        ],
        'tasks' => [
            ['id' => 100, 'gate_type' => 'CONDITION', 'gate_ref_id' => 10],
        ],
    ]);

    $errors = implode("\n", $validation['errors']);

    expect($validation['valid'])->toBeFalse();
    expect($errors)->toContain('Condition 10 is used but has an empty expression.');
    expect($errors)->toContain('unknown question predicate subject_id 999');
    expect($errors)->toContain('invalid predicate operator `NOPE`');
    expect($errors)->toContain('Condition cycle detected');
});

it('validates nested v2 branch and case expressions', function (): void {
    $service = selfserve_config_versioning_without_constructor();

    $validation = $service->validateConfig([
        'schema_version' => 2,
        'questions' => [
            ['id' => 1],
            ['id' => 2],
            ['id' => 3],
        ],
        'conditions' => [
            [
                'id' => 10,
                'expression' => [
                    'type' => 'branch',
                    'branches' => [
                        [
                            'kind' => 'if',
                            'when' => ['type' => 'predicate', 'subject_type' => 'question', 'subject_id' => 1, 'operator' => 'IS_TRUE'],
                            'then' => ['type' => 'predicate', 'subject_type' => 'question', 'subject_id' => 2, 'operator' => 'IS_TRUE'],
                        ],
                        [
                            'kind' => 'else',
                            'else' => true,
                            'then' => ['type' => 'predicate', 'subject_type' => 'question', 'subject_id' => 3, 'operator' => 'IS_FALSE_OR_NOT_SET'],
                        ],
                    ],
                ],
            ],
            [
                'id' => 20,
                'expression' => [
                    'type' => 'case',
                    'subject_type' => 'condition',
                    'subject_id' => 10,
                    'cases' => [
                        [
                            'value' => true,
                            'then' => ['type' => 'predicate', 'subject_type' => 'question', 'subject_id' => 2, 'operator' => 'IS_TRUE'],
                        ],
                        [
                            'value' => false,
                            'then' => ['type' => 'predicate', 'subject_type' => 'question', 'subject_id' => 3, 'operator' => 'IS_FALSE'],
                        ],
                    ],
                ],
            ],
        ],
        'tasks' => [
            ['id' => 100, 'gate_type' => 'CONDITION', 'gate_ref_id' => 20],
        ],
    ]);

    expect($validation['valid'])->toBeTrue();
    expect($validation['errors'])->toBe([]);
});

it('encodes config json payloads with apostrophes before persistence', function (): void {
    $version = new class extends selfserve_config_versions_o {
        /** @var array<string,mixed> */
        public array $capturedPayload = [];

        public function __construct()
        {
            // Skip db bootstrap for this unit test.
        }

        public function add_object(array $data): int
        {
            $this->capturedPayload = $data;
            return 123;
        }

        public function getObjectProperties(): void
        {
            // No-op for this unit test.
        }

        public function objectChanged(): void
        {
            // No-op for this unit test.
        }
    };

    $version->add(
        42,
        selfserve_config_versioning::STATUS_DRAFT,
        1,
        [
            'questions' => [
                [
                    'id' => 1,
                    'question' => "Driver's side check",
                ],
            ],
            'conditions' => [],
            'rules' => [],
            'tasks' => [],
        ],
        [
            'valid' => true,
            'errors' => [],
            'warnings' => [],
        ],
    );

    $configJson = $version->capturedPayload['config_json'] ?? null;
    $validationJson = $version->capturedPayload['validation_result_json'] ?? null;

    expect($configJson)->toBeString();
    expect($validationJson)->toBeString();
    expect(json_decode($configJson, true)['questions'][0]['question'] ?? null)->toBe("Driver's side check");
    expect(json_decode($validationJson, true)['valid'] ?? null)->toBeTrue();
});
