<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\CustomFieldsBundle\Tests\CustomFields;

use Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice;
use Chill\CustomFieldsBundle\Entity\CustomField;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

/**
 * This class cover the test of CustomFieldChoice.
 *
 * Function currently covered:
 *
 * - deserialize
 *
 * @internal
 *
 * @coversNothing
 */
final class CustomFieldsChoiceTest extends KernelTestCase
{
    /**
     * @var CustomFieldChoice
     */
    private $cfChoice;

    private ?object $cfProvider = null;

    protected function setUp(): void
    {
        self::bootKernel();

        $this->cfProvider = self::$kernel->getContainer()
            ->get('chill.custom_field.provider');
        $this->cfChoice = $this->cfProvider->getCustomFieldByType('choice');
    }

    protected function tearDown(): void
    {
        parent::tearDown();
    }

    /**
     * Test if the representation of the data is deserialized to an array text
     * with an "allow_other" field.
     *
     * This test does not covers the case when the selected value is `_other`
     *
     * @param type $data
     *
     * @dataProvider serializedRepresentationDataProvider
     */
    public function testDeserializeMultipleChoiceWithOther($data)
    {
        $customField = $this->generateCustomField([
            CustomFieldChoice::ALLOW_OTHER => true,
            CustomFieldChoice::MULTIPLE => true,
        ]);

        $deserialized = $this->cfChoice->deserialize($data, $customField);

        $this->assertSame(
            ['_other' => '', '_choices' => ['my-value']],
            $deserialized
        );
    }

    /**
     * Test if the representation of the data is deserialized to an array text
     * with an "allow_other" field.
     *
     * This test covers :
     * - the case when the selected value is `_other`
     * - result is null
     */
    public function testDeserializeMultipleChoiceWithOtherOtherCases()
    {
        $customField = $this->generateCustomField([
            CustomFieldChoice::ALLOW_OTHER => true,
            CustomFieldChoice::MULTIPLE => true,
        ]);

        // selected value is _other
        // from single to multiple
        $data = ['_other' => 'something', '_choices' => '_other'];
        $deserialized = $this->cfChoice->deserialize($data, $customField);

        $this->assertSame(
            ['_other' => 'something', '_choices' => ['_other']],
            $deserialized
        );

        // from multiple to multiple
        $data = ['_other' => 'something', '_choices' => ['_other', 'something']];
        $deserialized = $this->cfChoice->deserialize($data, $customField);

        $this->assertSame(
            ['_other' => 'something', '_choices' => ['_other', 'something']],
            $deserialized
        );

        // test with null value
        // from single to multiple
        $data = ['_other' => '', '_choices' => ''];
        $deserialized = $this->cfChoice->deserialize($data, $customField);

        $this->assertSame(
            ['_other' => '', '_choices' => ['']],
            $deserialized
        );

        // from multiple to multiple
        $data = ['_other' => '', '_choices' => []];
        $deserialized = $this->cfChoice->deserialize($data, $customField);

        $this->assertSame(
            ['_other' => '', '_choices' => []],
            $deserialized
        );

        $deserialized = $this->cfChoice->deserialize(['_other' => null, '_choices' => null], $customField);
        $this->assertSame(['_other' => '', '_choices' => [null]], $deserialized);

        $deserialized = $this->cfChoice->deserialize(['_other' => null, '_choices' => ''], $customField);
        $this->assertSame(['_other' => '', '_choices' => ['']], $deserialized);

        $deserialized = $this->cfChoice->deserialize([null], $customField);
        $this->assertSame(['_other' => '', '_choices' => [null]], $deserialized);
    }

    /**
     * Test if the representation of the data is deserialized to an array text
     * **without** an "allow_other" field.
     *
     * @param type $data
     *
     * @dataProvider serializedRepresentationDataProvider
     */
    public function testDeserializeMultipleChoiceWithoutOther($data)
    {
        $customField = $this->generateCustomField([
            CustomFieldChoice::ALLOW_OTHER => false,
            CustomFieldChoice::MULTIPLE => true,
        ]);

        $deserialized = $this->cfChoice->deserialize($data, $customField);

        $this->assertSame(['my-value'], $deserialized);
    }

    /**
     * Test if the representation of the data is deserialized to an array text
     * **without** an "allow_other" field.
     *
     * Covered cases :
     * - NULL values
     */
    public function testDeserializeMultipleChoiceWithoutOtherOtherCases()
    {
        $customField = $this->generateCustomField([
            CustomFieldChoice::ALLOW_OTHER => false,
            CustomFieldChoice::MULTIPLE => true,
        ]);

        // from single to multiple
        $data = 'my-value';
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame(['my-value'], $deserialized);

        // from multiple to multiple
        $data = ['my-value'];
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame(['my-value'], $deserialized);

        // from multiple
        $data = [null];
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame([null], $deserialized);

        $data = ['_other' => null, '_choices' => [null]];
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame([null], $deserialized);

        // from single
        $data = ['_other' => null, '_choices' => null];
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame([null], $deserialized);
    }

    /**
     * Test if the representation of the data is deserialized to a single text
     * with an "allow_other" field.
     *
     * If the value is in _other, the _other value should be in the _other field.
     *
     * @param type $data
     *
     * @dataProvider serializedRepresentationDataProvider
     */
    public function testDeserializeSingleChoiceWithOther($data)
    {
        $customField = $this->generateCustomField([
            CustomFieldChoice::ALLOW_OTHER => true,
            CustomFieldChoice::MULTIPLE => false,
        ]);

        $deserialized = $this->cfChoice->deserialize($data, $customField);

        $this->assertSame(['_other' => '', '_choices' => 'my-value'], $deserialized);
    }

    /**
     * Other cases :.
     *
     *  - Test if the selected value is '_other
     * - Test with null data
     */
    public function testDeserializeSingleChoiceWithOtherOtherCases()
    {
        $customField = $this->generateCustomField([
            CustomFieldChoice::ALLOW_OTHER => true,
            CustomFieldChoice::MULTIPLE => false,
        ]);

        // from a single to a single
        $data = ['_other' => 'something', '_choices' => '_other'];
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame(['_other' => 'something', '_choices' => '_other'], $deserialized);

        // from a multiple to a single
        $data = ['_other' => 'something', '_choices' => ['some', '_other']];
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame(['_other' => 'something', '_choices' => '_other'], $deserialized);

        // test with null data
        // from a single to a single :
        $data = ['_other' => 'something', '_choices' => null];
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame(['_other' => 'something', '_choices' => null], $deserialized);

        $data = ['_other' => 'something', '_choices' => ''];
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame(['_other' => 'something', '_choices' => ''], $deserialized);

        // from a multiple to a signle
        $data = ['_other' => 'something', '_choices' => []];
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame(['_other' => 'something', '_choices' => ''], $deserialized);

        $data = ['_other' => 'something', '_choices' => ['']];
        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $this->assertSame(['_other' => 'something', '_choices' => ''], $deserialized);

        $deserialized = $this->cfChoice->deserialize(['_other' => null, '_choices' => null], $customField);
        $this->assertSame(['_other' => '', '_choices' => null], $deserialized);

        $deserialized = $this->cfChoice->deserialize(['_other' => null, '_choices' => ''], $customField);
        $this->assertSame(['_other' => '', '_choices' => ''], $deserialized);

        $deserialized = $this->cfChoice->deserialize([null], $customField);
        $this->assertSame(['_other' => '', '_choices' => null], $deserialized);
    }

    // ///////////////////////////////////////
    //
    // test function deserialize
    //
    // //////////////////////////////////////

    /**
     * Test if the representation of the data is deserialized to a single text.
     *
     * If the value is in _other, the _other value should not be returned.
     *
     * @param type $data
     *
     * @dataProvider serializedRepresentationDataProvider
     */
    public function testDeserializeSingleChoiceWithoutOther($data)
    {
        $customField = $this->generateCustomField([
            CustomFieldChoice::ALLOW_OTHER => false,
            CustomFieldChoice::MULTIPLE => false,
        ]);

        $deserialized = $this->cfChoice->deserialize($data, $customField);

        $this->assertSame('my-value', $deserialized);
    }

    public function testDeserializeSingleChoiceWithoutOtherDataIsNull()
    {
        $customField = $this->generateCustomField([
            CustomFieldChoice::ALLOW_OTHER => false,
            CustomFieldChoice::MULTIPLE => false,
        ]);

        $deserialized = $this->cfChoice->deserialize(null, $customField);
        $this->assertSame(null, $deserialized);

        $deserialized = $this->cfChoice->deserialize('', $customField);
        $this->assertSame('', $deserialized);

        $deserialized = $this->cfChoice->deserialize([null], $customField);
        $this->assertSame(null, $deserialized);

        $deserialized = $this->cfChoice->deserialize(['_other' => null, '_choices' => null], $customField);
        $this->assertSame(null, $deserialized);

        $deserialized = $this->cfChoice->deserialize(['_other' => null, '_choices' => ''], $customField);
        $this->assertSame('', $deserialized);

        $deserialized = $this->cfChoice->deserialize([null], $customField);
        $this->assertSame(null, $deserialized);
    }

    /**
     * @dataProvider emptyDataProvider
     */
    public function testIsEmptyValueEmpty(mixed $data)
    {
        $customField = $this->generateCustomField([
            CustomFieldChoice::ALLOW_OTHER => false,
            CustomFieldChoice::MULTIPLE => true,
        ]);

        $isEmpty = $this->cfChoice->isEmptyValue($data, $customField);

        $this->assertTrue($isEmpty);
    }

    /**
     * provide empty data in different possible representations.
     * Those data are supposed to be deserialized.
     *
     * @return array
     */
    public static function emptyDataProvider()
    {
        return [
            // 0
            [
                // signle
                '',
            ],
            // 1
            [
                // single
                null,
            ],
            // 2
            [
                // signle with allow other
                ['_other' => 'something', '_choices' => ''],
            ],
            // 3
            [
                // multiple
                [],
            ],
            // 4
            [
                // multiple with allow other
                ['_other' => 'something', '_choices' => []],
            ],
            // 5
            [
                // multiple with allow other
                ['_other' => '', '_choices' => []],
            ],
            // 6
            [
                // empty
                ['_other' => null, '_choices' => null],
            ],
            // 7
            [
                // empty
                [null],
            ],
        ];
    }

    // ///////////////////////////////////////
    //
    // test function isEmptyValue
    //
    // //////////////////////////////////////
    /**
     * test the not empty with the not-empty data provider.
     *
     * @dataProvider serializedRepresentationDataProvider
     */
    public function testIsEmptyValueNotEmpty(mixed $data)
    {
        $customField = $this->generateCustomField([
            CustomFieldChoice::ALLOW_OTHER => false,
            CustomFieldChoice::MULTIPLE => true,
        ]);

        $deserialized = $this->cfChoice->deserialize($data, $customField);
        $isEmpty = $this->cfChoice->isEmptyValue($deserialized, $customField);

        $this->assertFalse($isEmpty);
    }

    public static function serializedRepresentationDataProvider()
    {
        return [
            [
                // multiple => false, allow_other => false
                'my-value',
            ],
            [
                // multiple => true, allow_ther => false
                ['my-value'],
            ],
            [
                // multiple => false, allow_other => true, current value not in other
                ['_other' => '', '_choices' => 'my-value'],
            ],
            [
                // multiple => true, allow_other => true, current value not in other
                ['_other' => '', '_choices' => ['my-value']],
            ],
        ];
    }

    /**
     * @param array $options
     *
     * @return CustomField
     */
    private function generateCustomField($options)
    {
        return (new CustomField())
            ->setActive(true)
            ->setSlug('slug')
            ->setOptions($options)
            ->setType('choice');
    }
}
