<?php

declare(strict_types=1);

use Tests\Support\TestLaneTrendFixtures;

function validateTrendSchema(mixed $value, array $schema, array $defs, string $path = '$'): array
{
    if (isset($schema['$ref']) && is_string($schema['$ref'])) {
        $ref = $schema['$ref'];
        $definitionKey = str_replace('#/$defs/', '', $ref);

        return validateTrendSchema($value, $defs[$definitionKey] ?? [], $defs, $path);
    }

    $errors = [];
    $types = $schema['type'] ?? null;

    if (isset($schema['const']) && $value !== $schema['const']) {
        $errors[] = sprintf('%s must equal %s.', $path, var_export($schema['const'], true));
    }

    if (isset($schema['enum']) && ! in_array($value, $schema['enum'], true)) {
        $errors[] = sprintf('%s must be one of [%s].', $path, implode(', ', array_map(static fn (mixed $item): string => var_export($item, true), $schema['enum'])));
    }

    if (is_string($types)) {
        $types = [$types];
    }

    if (is_array($types)) {
        $typeValid = false;

        foreach ($types as $type) {
            $typeValid = match ($type) {
                'array' => is_array($value) && array_is_list($value),
                'object' => is_array($value) && ! array_is_list($value),
                'string' => is_string($value),
                'integer' => is_int($value),
                'number' => is_int($value) || is_float($value),
                'boolean' => is_bool($value),
                'null' => $value === null,
                default => false,
            };

            if ($typeValid) {
                break;
            }
        }

        if (! $typeValid) {
            $errors[] = sprintf('%s has the wrong type.', $path);

            return $errors;
        }
    }

    if (is_array($value) && array_is_list($value)) {
        if (isset($schema['minItems']) && count($value) < (int) $schema['minItems']) {
            $errors[] = sprintf('%s must contain at least %d item(s).', $path, (int) $schema['minItems']);
        }

        if (isset($schema['items']) && is_array($schema['items'])) {
            foreach ($value as $index => $item) {
                $errors = array_merge($errors, validateTrendSchema($item, $schema['items'], $defs, sprintf('%s[%d]', $path, $index)));
            }
        }

        return $errors;
    }

    if (is_array($value) && ! array_is_list($value)) {
        foreach ($schema['required'] ?? [] as $requiredKey) {
            if (! array_key_exists((string) $requiredKey, $value)) {
                $errors[] = sprintf('%s is missing required key [%s].', $path, $requiredKey);
            }
        }

        if (($schema['additionalProperties'] ?? true) === false) {
            $allowedKeys = array_keys($schema['properties'] ?? []);

            foreach (array_keys($value) as $key) {
                if (! in_array($key, $allowedKeys, true)) {
                    $errors[] = sprintf('%s contains unsupported key [%s].', $path, $key);
                }
            }
        }

        foreach ($schema['properties'] ?? [] as $key => $propertySchema) {
            if (! array_key_exists($key, $value)) {
                continue;
            }

            $errors = array_merge($errors, validateTrendSchema($value[$key], $propertySchema, $defs, $path.'.'.$key));
        }

        return $errors;
    }

    if ((is_int($value) || is_float($value)) && isset($schema['minimum']) && $value < $schema['minimum']) {
        $errors[] = sprintf('%s must be >= %s.', $path, $schema['minimum']);
    }

    return $errors;
}

it('keeps the generated trend-history artifact synchronized with the checked-in JSON schema contract', function (): void {
    $schemaPath = repo_path('specs/211-runtime-trend-recalibration/contracts/test-runtime-trend-history.schema.json');
    $artifactDirectory = TestLaneTrendFixtures::artifactDirectory('trend-schema-contract');
    $report = TestLaneTrendFixtures::buildReport(
        laneId: 'fast-feedback',
        wallClockSeconds: 184.6,
        durationsByFile: [
            'tests/Feature/Guards/TestLaneTrendContractSchemaTest.php' => 16.4,
            'tests/Feature/Guards/TestLaneArtifactsContractTest.php' => 11.2,
        ],
        artifactDirectory: $artifactDirectory,
        ciContext: [
            'workflowId' => 'pr-fast-feedback',
            'triggerClass' => 'pull-request',
            'entryPointResolved' => true,
            'workflowLaneMatched' => true,
        ],
        comparisonProfile: 'shared-test-fixture-slimming',
    );

    /** @var array<string, mixed> $schema */
    $schema = json_decode((string) file_get_contents($schemaPath), true, 512, JSON_THROW_ON_ERROR);
    $artifact = $report['trendHistoryArtifact'];
    $errors = validateTrendSchema($artifact, $schema, $schema['$defs'] ?? []);

    expect($artifact['schemaVersion'])->toBe('1.0.0')
        ->and($artifact['laneId'])->toBe('fast-feedback')
        ->and($artifact['workflowProfile'])->toBe('pr-fast-feedback')
        ->and($artifact['history'][0]['artifactRefs']['trendHistory'])->toBe($artifactDirectory.'/fast-feedback-latest.trend-history.json')
        ->and($errors)->toBe([]);
});
