<?php

declare(strict_types=1);

use App\Support\Diff\DiffRowStatus;
use App\Support\Diff\RbacRoleDefinitionDiffBuilder;

it('builds RBAC rows in deterministic review order with matching summary counts', function (): void {
    $presentation = app(RbacRoleDefinitionDiffBuilder::class)->build(rbacBuilderEvidenceFixture());

    expect(array_map(
        static fn ($row): string => $row->key,
        $presentation->rows,
    ))->toBe([
        'Role definition > Display name',
        'Role definition > Description',
        'Role definition > Role source',
        'Role definition > Permission blocks',
        'Role definition > Scope tag IDs',
        'Permission block 1 > Allowed actions',
        'Permission block 1 > Denied actions',
        'Permission block 1 > Conditions',
    ]);

    $rows = collect($presentation->rows)->keyBy('key');

    expect($presentation->summary->changedCount)->toBe(2)
        ->and($presentation->summary->addedCount)->toBe(1)
        ->and($presentation->summary->removedCount)->toBe(1)
        ->and($presentation->summary->unchangedCount)->toBe(4)
        ->and($presentation->summary->message)->toBeNull()
        ->and($rows->get('Role definition > Description')?->status)->toBe(DiffRowStatus::Changed)
        ->and($rows->get('Permission block 1 > Allowed actions')?->status)->toBe(DiffRowStatus::Changed)
        ->and($rows->get('Permission block 1 > Denied actions')?->status)->toBe(DiffRowStatus::Removed)
        ->and($rows->get('Permission block 1 > Conditions')?->status)->toBe(DiffRowStatus::Added)
        ->and($rows->get('Permission block 1 > Allowed actions')?->isListLike)->toBeTrue()
        ->and($rows->get('Permission block 1 > Allowed actions')?->addedItems)->toBe([
            'Microsoft.Intune/deviceConfigurations/create',
        ])
        ->and($rows->get('Permission block 1 > Allowed actions')?->removedItems)->toBe([
            'Microsoft.Intune/deviceConfigurations/delete',
        ])
        ->and($rows->get('Permission block 1 > Allowed actions')?->unchangedItems)->toBe([
            'Microsoft.Intune/deviceConfigurations/read',
        ]);
});

it('preserves null boolean scalar and empty-list values for shared formatting', function (): void {
    $presentation = app(RbacRoleDefinitionDiffBuilder::class)->build(rbacBuilderEvidenceFixture([
        'changed_keys' => [
            'Role definition > Description',
            'Role definition > Preview enabled',
            'Role definition > Scope tag IDs',
        ],
        'baseline' => [
            'normalized' => [
                'Role definition > Description' => null,
                'Role definition > Preview enabled' => false,
                'Role definition > Scope tag IDs' => [],
            ],
            'is_built_in' => false,
            'role_permission_count' => 0,
        ],
        'current' => [
            'normalized' => [
                'Role definition > Description' => 'Updated description',
                'Role definition > Preview enabled' => true,
                'Role definition > Scope tag IDs' => ['scope-1'],
            ],
            'is_built_in' => false,
            'role_permission_count' => 0,
        ],
    ]));

    $rows = collect($presentation->rows)->keyBy('key');

    expect($rows->get('Role definition > Description')?->oldValue)->toBeNull()
        ->and($rows->get('Role definition > Description')?->newValue)->toBe('Updated description')
        ->and($rows->get('Role definition > Preview enabled')?->oldValue)->toBeFalse()
        ->and($rows->get('Role definition > Preview enabled')?->newValue)->toBeTrue()
        ->and($rows->get('Role definition > Scope tag IDs')?->oldValue)->toBe([])
        ->and($rows->get('Role definition > Scope tag IDs')?->newValue)->toBe(['scope-1'])
        ->and($rows->get('Role definition > Scope tag IDs')?->isListLike)->toBeTrue()
        ->and($rows->get('Role definition > Scope tag IDs')?->addedItems)->toBe(['scope-1'])
        ->and($rows->get('Role definition > Scope tag IDs')?->removedItems)->toBe([])
        ->and($rows->get('Role definition > Role source')?->newValue)->toBe('Custom')
        ->and($rows->get('Role definition > Permission blocks')?->newValue)->toBe(0);
});

it('derives identical fallback rows into a no-change summary when normalized metadata is sparse', function (): void {
    $presentation = app(RbacRoleDefinitionDiffBuilder::class)->build([
        'changed_keys' => [],
        'baseline' => [
            'normalized' => [],
            'is_built_in' => true,
            'role_permission_count' => 1,
        ],
        'current' => [
            'normalized' => [],
            'is_built_in' => true,
            'role_permission_count' => 1,
        ],
    ]);

    expect(array_map(
        static fn ($row): string => $row->key,
        $presentation->rows,
    ))->toBe([
        'Role definition > Role source',
        'Role definition > Permission blocks',
    ])
        ->and($presentation->summary->changedCount)->toBe(0)
        ->and($presentation->summary->addedCount)->toBe(0)
        ->and($presentation->summary->removedCount)->toBe(0)
        ->and($presentation->summary->unchangedCount)->toBe(2)
        ->and($presentation->summary->message)->toBe('No changes detected.');
});

it('returns a no-data presentation for empty or invalid RBAC payloads', function (): void {
    $presentation = app(RbacRoleDefinitionDiffBuilder::class)->build([
        'changed_keys' => ['Ghost key'],
        'baseline' => ['normalized' => ['' => 'ignored']],
        'current' => ['normalized' => ['   ' => 'ignored']],
    ]);

    expect($presentation->rows)->toBe([])
        ->and($presentation->summary->hasRows)->toBeFalse()
        ->and($presentation->summary->changedCount)->toBe(0)
        ->and($presentation->summary->addedCount)->toBe(0)
        ->and($presentation->summary->removedCount)->toBe(0)
        ->and($presentation->summary->unchangedCount)->toBe(0)
        ->and($presentation->summary->message)->toBe('No diff data available.');
});

/**
 * @param  array<string, mixed>  $overrides
 * @return array<string, mixed>
 */
function rbacBuilderEvidenceFixture(array $overrides = []): array
{
    return rbacBuilderFixtureMerge([
        'changed_keys' => [
            'Role definition > Description',
            'Permission block 1 > Allowed actions',
        ],
        'baseline' => [
            'normalized' => [
                'Role definition > Display name' => 'Security Reader',
                'Role definition > Description' => 'Baseline description',
                'Role definition > Scope tag IDs' => ['0', 'scope-1'],
                'Permission block 1 > Allowed actions' => [
                    'Microsoft.Intune/deviceConfigurations/delete',
                    'Microsoft.Intune/deviceConfigurations/read',
                ],
                'Permission block 1 > Denied actions' => [
                    'Microsoft.Intune/deviceConfigurations/wipe',
                ],
            ],
            'is_built_in' => false,
            'role_permission_count' => 1,
        ],
        'current' => [
            'normalized' => [
                'Role definition > Display name' => 'Security Reader',
                'Role definition > Description' => 'Updated description',
                'Role definition > Scope tag IDs' => ['0', 'scope-1'],
                'Permission block 1 > Allowed actions' => [
                    'Microsoft.Intune/deviceConfigurations/create',
                    'Microsoft.Intune/deviceConfigurations/read',
                ],
                'Permission block 1 > Conditions' => [
                    '@Resource[Microsoft.Intune/deviceConfigurations] Exists',
                ],
            ],
            'is_built_in' => false,
            'role_permission_count' => 1,
        ],
    ], $overrides);
}

/**
 * @param  array<string, mixed>  $base
 * @param  array<string, mixed>  $overrides
 * @return array<string, mixed>
 */
function rbacBuilderFixtureMerge(array $base, array $overrides): array
{
    foreach ($overrides as $key => $value) {
        if ($key === 'normalized') {
            $base[$key] = $value;

            continue;
        }

        if (
            is_string($key)
            && array_key_exists($key, $base)
            && is_array($value)
            && is_array($base[$key])
            && ! array_is_list($value)
            && ! array_is_list($base[$key])
        ) {
            $base[$key] = rbacBuilderFixtureMerge($base[$key], $value);

            continue;
        }

        $base[$key] = $value;
    }

    return $base;
}
