<?php
/**
 * Copyright 2022 Adobe
 * All Rights Reserved.
 *
 * NOTICE: Adobe permits you to use, modify, and distribute this file in
 * accordance with the terms of the Adobe license agreement accompanying
 * it.
 */
declare(strict_types=1);

namespace Magento\Mray\Unit\Index\Persistence;

use Magento\Mray\Index\Persistence\ReferenceUpdate;
use Magento\Mray\MagentoApiIndex\Api\GetIndexedVersions;
use Magento\Mray\MagentoApiIndex\Model\Versions;
use PHPUnit\Framework\TestCase;
use function PHPUnit\Framework\assertEquals;

class ReferenceUpdateTest extends TestCase
{
    /**
     * @var ReferenceUpdate
     */
    private $service;

    /**
     * @var Versions
     */
    private $versions;

    protected function setUp(): void
    {
        $getIndexedVersions = $this->createMock(GetIndexedVersions::class);
        $getIndexedVersions
            ->expects($this->any())
            ->method('execute')
            ->willReturn(
                array_flip(
                    // array representing all available versions for test suite and their corresponding
                    // bit in versions bitmask:
                    ['2.4.0' => 0, '2.4.1' => 1, '2.4.2' => 2, '2.4.3' => 3, '2.4.4' => 4]
                )
            );
        $this->versions = new Versions($getIndexedVersions);
        $this->service = new ReferenceUpdate($this->versions);
    }

    /**
     * @param array $referenceArray
     * @param string $ref
     * @param string $version
     * @param array $updatedReferenceArray
     * @dataProvider dictionaryProvider
     */
    public function testExecute(array $referenceArray, string $ref, string $version, array $updatedReferenceArray): void
    {
        assertEquals(
            $this->service->execute(
                $this->mapVersionsArrayToBitmask($referenceArray),
                $ref,
                $version
            ),
            $this->mapVersionsArrayToBitmask($updatedReferenceArray)
        );
    }

    /**
     * @return array
     */
    public function dictionaryProvider(): array
    {
        // Testing scenario in this test suite uses data simplification to make them
        // easier to verify and update in the future if needed.
        //
        // Real data (taken from ./data/api/2.php file in magento-api-index v.5.3.3):
        // [
        //  '2ukEwbn99dq2MENrX55Ruw'=>['4294967295|1'],
        //  '2Cn8LilSsw3BMy86DWCh5A'=>['4294967295|1'],
        //  '2ZWA47NRi8a7LIqgtYaNvw'=>['8388607']
        //  (...cut for brevity...)
        // ]
        //
        // is mocked as sample data in following format:
        //
        // [
        // '(a)' => ['2.4.0', '2.4.1'],
        // '(b)' => ['2.4.0', '2.4.1'],
        // '(c)' => ['2.4.0', '2.4.1']
        // ]
        //
        // where bitmask map representing referenced versions (eg. ['4294967295|1']) was changed into
        // an array of actual versions identifiers and (a), (b) and (c) represents reference
        // dictionary keys (eg. 2ukEwbn99dq2MENrX55Ruw from real data).
        // Mapping between array of versions and actual bitmask is done before test is executed,
        // so this notation does not affect test results. Dictionary keys values has no meaning
        // for tests at all.
        //
        return [
            // ReferenceUpdate::execute() - Scenario 1.
            // A new value (a) is added to version 2.4.4. Currently 2.4.4 is not present in
            // dictionary, so this is a simple operation of adding new version to (a).
            //
            // Real life example: @deprecated annotation represented in dictionary
            // by (a) was added to a method ClassName::method() in version 2.4.4 and no other
            // annotations were present earlier (but this annotation was already present in
            // versions 2.4.0 and 2.4.1 of ClassName::method()):
            [
                [
                    '(a)' => ['2.4.0', '2.4.1'],
                    '(b)' => ['2.4.0', '2.4.1'],
                    '(c)' => ['2.4.0', '2.4.1']
                ],
                '(a)',
                '2.4.4',
                [
                    '(a)' => ['2.4.0', '2.4.1', '2.4.4'],
                    '(b)' => ['2.4.0', '2.4.1'],
                    '(c)' => ['2.4.0', '2.4.1']
                ]
            ],
            [
                [
                    '(a)' => ['2.4.2'],
                    '(b)' => ['2.4.0', '2.4.2'],
                    '(c)' => ['2.4.0', '2.4.1', '2.4.2']
                ],
                '(b)',
                '2.4.3',
                [
                    '(a)' => ['2.4.2'],
                    '(b)' => ['2.4.0', '2.4.2', '2.4.3'],
                    '(c)' => ['2.4.0', '2.4.1', '2.4.2']
                ]
            ],

            // ReferenceUpdate::execute() - Scenario 2.
            // A new value (c) is added to version 2.4.4, but there is previous value (a)
            // already present in dictionary. We need to REMOVE version 2.4.4
            // from (a) and then add 2.4.4 to (c).
            //
            // Real life example: @deprecated annotation represented by (a) is going to be replaced
            // by @api 102.0.0 annotation (represented by (c)). First, referencing 2.4.4 in (a) needs
            // to be removed from dictionary, then 2.4.4 needs to be added to (c):
            [
                [
                    '(a)' => ['2.4.0', '2.4.1', '2.4.4'],
                    '(b)' => ['2.4.1', '2.4.2'],
                    '(c)' => ['2.4.0', '2.4.1']
                ],
                '(c)',
                '2.4.4',
                [
                    '(a)' => ['2.4.0', '2.4.1'],
                    '(b)' => ['2.4.1', '2.4.2'],
                    '(c)' => ['2.4.0', '2.4.1', '2.4.4']
                ]
            ],
            [
                [
                    '(a)' => ['2.4.0', '2.4.1', '2.4.2', '2.4.3', '2.4.4'],
                    '(b)' => ['2.4.0', '2.4.1', '2.4.2', '2.4.4'],
                    '(c)' => ['2.4.0', '2.4.1', '2.4.2', '2.4.4']
                ],
                '(c)',
                '2.4.3',
                [
                    '(a)' => ['2.4.0', '2.4.1', '2.4.2', '2.4.4'],
                    '(b)' => ['2.4.0', '2.4.1', '2.4.2', '2.4.4'],
                    '(c)' => ['2.4.0', '2.4.1', '2.4.2', '2.4.3', '2.4.4']
                ]
            ]
        ];
    }

    /**
     * @param array $versionsArray
     * @return array
     */
    private function mapVersionsArrayToBitmask(array $versionsArray): array
    {
        return array_map(function ($reference) {
            $mask = '';
            foreach ($reference as $currentVersion) {
                $mask = $this->versions->addToMask($currentVersion, $mask);
            }
            return $mask;
        }, $versionsArray);
    }
}
