<?php

declare(strict_types=1);

namespace Tests\Support;

use PHPUnit\Framework\Assert;

final class AssertsDriftEvidenceContract
{
    /**
     * @var list<string>
     */
    private const array AllowedSummaryKinds = [
        'policy_snapshot',
        'policy_assignments',
        'policy_scope_tags',
        'rbac_role_definition',
    ];

    /**
     * @var list<string>
     */
    private const array AllowedChangeTypes = [
        'missing_policy',
        'unexpected_policy',
        'different_version',
    ];

    /**
     * @var list<string>
     */
    private const array AllowedFidelity = [
        'content',
        'meta',
        'mixed',
    ];

    /**
     * @param  array<string, mixed>  $evidence
     */
    public static function assertValid(array $evidence): void
    {
        foreach (['change_type', 'policy_type', 'subject_key', 'summary', 'baseline', 'current', 'fidelity', 'provenance'] as $key) {
            Assert::assertArrayHasKey($key, $evidence);
        }

        $changeType = is_string($evidence['change_type'] ?? null) ? (string) $evidence['change_type'] : '';
        Assert::assertContains($changeType, self::AllowedChangeTypes);

        $summary = is_array($evidence['summary'] ?? null) ? $evidence['summary'] : [];
        Assert::assertArrayHasKey('kind', $summary);

        $kind = is_string($summary['kind'] ?? null) ? (string) $summary['kind'] : '';
        Assert::assertContains($kind, self::AllowedSummaryKinds);

        $baseline = is_array($evidence['baseline'] ?? null) ? $evidence['baseline'] : [];
        $current = is_array($evidence['current'] ?? null) ? $evidence['current'] : [];

        Assert::assertArrayHasKey('policy_version_id', $baseline);
        Assert::assertArrayHasKey('policy_version_id', $current);

        $baselineId = is_numeric($baseline['policy_version_id'] ?? null) ? (int) $baseline['policy_version_id'] : null;
        $currentId = is_numeric($current['policy_version_id'] ?? null) ? (int) $current['policy_version_id'] : null;

        $expectedFidelity = self::expectedFidelity($baselineId, $currentId);

        $fidelity = is_string($evidence['fidelity'] ?? null) ? (string) $evidence['fidelity'] : '';
        Assert::assertContains($fidelity, self::AllowedFidelity);
        Assert::assertSame($expectedFidelity, $fidelity);

        $provenance = is_array($evidence['provenance'] ?? null) ? $evidence['provenance'] : [];

        foreach (['baseline_profile_id', 'baseline_snapshot_id', 'compare_operation_run_id', 'inventory_sync_run_id'] as $key) {
            Assert::assertArrayHasKey($key, $provenance);
        }

        Assert::assertIsInt($provenance['baseline_profile_id']);
        Assert::assertIsInt($provenance['baseline_snapshot_id']);
        Assert::assertIsInt($provenance['compare_operation_run_id']);

        $inventorySyncRunId = $provenance['inventory_sync_run_id'] ?? null;

        Assert::assertTrue($inventorySyncRunId === null || is_numeric($inventorySyncRunId));
    }

    public static function isDiffRenderable(array $evidence): bool
    {
        $changeType = is_string($evidence['change_type'] ?? null) ? (string) $evidence['change_type'] : '';
        $baseline = is_array($evidence['baseline'] ?? null) ? $evidence['baseline'] : [];
        $current = is_array($evidence['current'] ?? null) ? $evidence['current'] : [];

        $baselineId = is_numeric($baseline['policy_version_id'] ?? null) ? (int) $baseline['policy_version_id'] : null;
        $currentId = is_numeric($current['policy_version_id'] ?? null) ? (int) $current['policy_version_id'] : null;

        return match ($changeType) {
            'missing_policy' => $baselineId !== null,
            'unexpected_policy' => $currentId !== null,
            default => $baselineId !== null && $currentId !== null,
        };
    }

    private static function expectedFidelity(?int $baselinePolicyVersionId, ?int $currentPolicyVersionId): string
    {
        if ($baselinePolicyVersionId !== null && $currentPolicyVersionId !== null) {
            return 'content';
        }

        if ($baselinePolicyVersionId !== null || $currentPolicyVersionId !== null) {
            return 'mixed';
        }

        return 'meta';
    }
}
