<?php

namespace Tests\Unit\Framework;

use InvalidArgumentException;
use Openguru\OpenCartFramework\Support\Arr;
use PHPUnit\Framework\TestCase;

class ArrTest extends TestCase
{
    public function testKeyByField(): void
    {
        $data = [
            ['id' => 1, 'name' => 'Item 1'],
            ['id' => 2, 'name' => 'Item 2'],
        ];

        $result = Arr::keyByField($data, 'id');

        $this->assertArrayHasKey(1, $result);
        $this->assertArrayHasKey(2, $result);
        $this->assertEquals('Item 1', $result[1]['name']);
        $this->assertEquals('Item 2', $result[2]['name']);

        $this->expectException(InvalidArgumentException::class);
        Arr::keyByField($data, 'nonexistent');
    }

    public function testGroupByKey(): void
    {
        $data = [
            ['category' => 'A', 'value' => 10],
            ['category' => 'A', 'value' => 20],
            ['category' => 'B', 'value' => 30],
        ];

        $result = Arr::groupByKey($data, 'category', 'value');

        $this->assertEquals(['A' => [10, 20], 'B' => [30]], $result);
    }

    public function testGet(): void
    {
        $data = ['key' => 'value', 'nested' => ['key' => 'nestedValue']];

        $this->assertEquals('value', Arr::get($data, 'key'));
        $this->assertEquals('nestedValue', Arr::get($data, 'nested.key'));
        $this->assertNull(Arr::get($data, 'nonexistent'));
        $this->assertEquals('default', Arr::get($data, 'nonexistent', 'default'));
    }

    public function testHas(): void
    {
        $data = [
            'key' => 'value',
            'nested' => [
                'key' => 'nestedValue',
                'deep' => [
                    'key' => 'deepValue',
                ],
            ],
        ];

        // Простые ключи
        $this->assertTrue(Arr::has($data, 'key'));
        $this->assertFalse(Arr::has($data, 'nonexistent'));

        // Dot notation - один уровень
        $this->assertTrue(Arr::has($data, 'nested.key'));
        $this->assertFalse(Arr::has($data, 'nested.nonexistent'));

        // Dot notation - несколько уровней
        $this->assertTrue(Arr::has($data, 'nested.deep.key'));
        $this->assertFalse(Arr::has($data, 'nested.deep.nonexistent'));
        $this->assertFalse(Arr::has($data, 'nested.nonexistent.key'));

        // Пустой массив
        $this->assertFalse(Arr::has([], 'key'));

        // Ключ с точкой в корневом массиве
        $dataWithDotKey = ['key.with.dots' => 'value'];
        $this->assertTrue(Arr::has($dataWithDotKey, 'key.with.dots'));
    }

    public function testSet(): void
    {
        $data = [];

        Arr::set($data, 'key', 'value');
        $this->assertEquals('value', $data['key']);

        Arr::set($data, 'nested.key', 'nestedValue');
        $this->assertEquals('nestedValue', $data['nested']['key']);
    }

    public function testUnset(): void
    {
        $data = ['key' => 'value', 'nested' => ['key' => 'nestedValue']];

        Arr::unset($data, 'key');
        $this->assertArrayNotHasKey('key', $data);

        Arr::unset($data, 'nested.key');
        $this->assertArrayNotHasKey('key', $data['nested']);
    }

    public function testFind(): void
    {
        $data = [
            ['id' => 1, 'name' => 'Item 1'],
            ['id' => 2, 'name' => 'Item 2'],
        ];

        $result = Arr::find($data, function ($item) {
            return $item['id'] === 2;
        });
        $this->assertEquals(['id' => 2, 'name' => 'Item 2'], $result);

        $result = Arr::find($data, function ($item) {
            return $item['id'] === 3;
        });
        $this->assertNull($result);
    }

    public function testMergeArraysFlat(): void
    {
        $base = ['key1' => 'value1', 'key2' => 'value2'];
        $override = ['key2' => 'new_value2', 'key3' => 'value3'];

        $expected = [
            'key1' => 'value1',
            'key2' => 'new_value2',
            'key3' => 'value3',
        ];

        $this->assertSame($expected, Arr::mergeArraysRecursively($base, $override));
    }

    public function testMergeArraysNested(): void
    {
        $base = [
            'key1' => [
                'subkey1' => 'value1',
                'subkey2' => 'value2',
            ],
            'key2' => 'value3',
        ];
        $override = [
            'key1' => [
                'subkey2' => 'new_value2',
                'subkey3' => 'value4',
            ],
            'key2' => 'new_value3',
        ];

        $expected = [
            'key1' => [
                'subkey1' => 'value1',
                'subkey2' => 'new_value2',
                'subkey3' => 'value4',
            ],
            'key2' => 'new_value3',
        ];

        $this->assertSame($expected, Arr::mergeArraysRecursively($base, $override));
    }

    public function testMergeArraysOverrideWithNonArray(): void
    {
        $base = ['key1' => ['subkey1' => 'value1']];
        $override = ['key1' => 'new_value1'];

        $expected = ['key1' => 'new_value1'];

        $this->assertSame($expected, Arr::mergeArraysRecursively($base, $override));
    }

    public function testMergeArraysEmptyBase(): void
    {
        $base = [];
        $override = ['key1' => 'value1'];

        $expected = ['key1' => 'value1'];

        $this->assertSame($expected, Arr::mergeArraysRecursively($base, $override));
    }

    public function testMergeArraysEmptyOverride(): void
    {
        $base = ['key1' => 'value1'];
        $override = [];

        $expected = ['key1' => 'value1'];

        $this->assertSame($expected, Arr::mergeArraysRecursively($base, $override));
    }

    public function testMergeArraysWithDeepNesting(): void
    {
        $base = [
            'key1' => [
                'subkey1' => [
                    'subsubkey1' => 'value1',
                ],
            ],
        ];
        $override = [
            'key1' => [
                'subkey1' => [
                    'subsubkey1' => 'new_value1',
                    'subsubkey2' => 'value2',
                ],
            ],
        ];

        $expected = [
            'key1' => [
                'subkey1' => [
                    'subsubkey1' => 'new_value1',
                    'subsubkey2' => 'value2',
                ],
            ],
        ];

        $this->assertSame($expected, Arr::mergeArraysRecursively($base, $override));
    }

    public function testMergeArraysRecursivelyWithDotNotationSimple(): void
    {
        $base = ['app' => ['name' => 'MyApp']];
        $override = ['app.logs.path' => '/var/log'];

        $expected = [
            'app' => [
                'name' => 'MyApp',
                'logs' => [
                    'path' => '/var/log',
                ],
            ],
        ];

        $result = Arr::mergeArraysRecursivelyWithDotNotation($base, $override);
        $this->assertSame($expected, $result);
    }

    public function testMergeArraysRecursivelyWithDotNotationOverrideExisting(): void
    {
        $base = [
            'app' => [
                'name' => 'MyApp',
                'logs' => [
                    'path' => '/tmp/log',
                    'level' => 'info',
                ],
            ],
        ];
        $override = ['app.logs.path' => '/var/log'];

        $expected = [
            'app' => [
                'name' => 'MyApp',
                'logs' => [
                    'path' => '/var/log',
                    'level' => 'info',
                ],
            ],
        ];

        $result = Arr::mergeArraysRecursivelyWithDotNotation($base, $override);
        $this->assertSame($expected, $result);
    }

    public function testMergeArraysRecursivelyWithDotNotationMultipleKeys(): void
    {
        $base = ['app' => ['name' => 'MyApp']];
        $override = [
            'app.logs.path' => '/var/log',
            'app.logs.level' => 'debug',
            'app.debug' => true,
        ];

        $expected = [
            'app' => [
                'name' => 'MyApp',
                'logs' => [
                    'path' => '/var/log',
                    'level' => 'debug',
                ],
                'debug' => true,
            ],
        ];

        $result = Arr::mergeArraysRecursivelyWithDotNotation($base, $override);
        $this->assertSame($expected, $result);
    }

    public function testMergeArraysRecursivelyWithDotNotationDeepNesting(): void
    {
        $base = [
            'app' => [
                'name' => 'MyApp',
            ],
        ];
        $override = [
            'app.logs.path' => '/var/log',
            'app.config.database.host' => 'localhost',
            'app.config.database.port' => 3306,
        ];

        $expected = [
            'app' => [
                'name' => 'MyApp',
                'logs' => [
                    'path' => '/var/log',
                ],
                'config' => [
                    'database' => [
                        'host' => 'localhost',
                        'port' => 3306,
                    ],
                ],
            ],
        ];

        $result = Arr::mergeArraysRecursivelyWithDotNotation($base, $override);
        $this->assertSame($expected, $result);
    }

    public function testMergeArraysRecursivelyWithDotNotationMixedKeys(): void
    {
        $base = ['app' => ['name' => 'MyApp']];
        $override = [
            'app.logs.path' => '/var/log',
            'telegram' => ['bot_token' => '123456'],
        ];

        $expected = [
            'app' => [
                'name' => 'MyApp',
                'logs' => [
                    'path' => '/var/log',
                ],
            ],
            'telegram' => [
                'bot_token' => '123456',
            ],
        ];

        $result = Arr::mergeArraysRecursivelyWithDotNotation($base, $override);
        $this->assertSame($expected, $result);
    }

    public function testMergeArraysRecursivelyWithDotNotationEmptyBase(): void
    {
        $base = [];
        $override = ['app.logs.path' => '/var/log'];

        $expected = [
            'app' => [
                'logs' => [
                    'path' => '/var/log',
                ],
            ],
        ];

        $result = Arr::mergeArraysRecursivelyWithDotNotation($base, $override);
        $this->assertSame($expected, $result);
    }

    public function testMergeArraysRecursivelyWithDotNotationEmptyOverride(): void
    {
        $base = ['app' => ['name' => 'MyApp']];
        $override = [];

        $expected = ['app' => ['name' => 'MyApp']];

        $result = Arr::mergeArraysRecursivelyWithDotNotation($base, $override);
        $this->assertSame($expected, $result);
    }

    public function testMergeArraysRecursivelyWithDotNotationOverrideArrayWithValue(): void
    {
        $base = [
            'app' => [
                'logs' => [
                    'path' => '/tmp/log',
                    'level' => 'info',
                ],
            ],
        ];
        $override = ['app.logs' => '/var/log'];

        $expected = [
            'app' => [
                'logs' => '/var/log',
            ],
        ];

        $result = Arr::mergeArraysRecursivelyWithDotNotation($base, $override);
        $this->assertSame($expected, $result);
    }

    public function testMergeArraysRecursivelyWithDotNotationRealWorldExample(): void
    {
        $json = [
            'module_telecart_settings' => [
                'app' => [
                    'app_enabled' => true,
                    'app_name' => 'Telecart',
                ],
                'telegram' => [
                    'bot_token' => 'old_token',
                ],
            ],
        ];

        $envOverrides = [
            'app.logs.path' => '/var/log/telecart',
            'app.app_debug' => true,
            'telegram.bot_token' => 'new_token_from_env',
        ];

        $result = Arr::mergeArraysRecursivelyWithDotNotation(
            $json['module_telecart_settings'],
            $envOverrides
        );

        $expected = [
            'app' => [
                'app_enabled' => true,
                'app_name' => 'Telecart',
                'logs' => [
                    'path' => '/var/log/telecart',
                ],
                'app_debug' => true,
            ],
            'telegram' => [
                'bot_token' => 'new_token_from_env',
            ],
        ];

        $this->assertSame($expected, $result);
    }

    public function testGetWithKeysSimpleKeys(): void
    {
        $array = [
            'app_name' => 'MyApp',
            'debug_mode' => true,
            'default_language' => 'en',
        ];

        $keys = ['app_name', 'debug_mode'];
        $result = Arr::getWithKeys($array, $keys);

        $expected = [
            'app_name' => 'MyApp',
            'debug_mode' => true,
        ];

        $this->assertSame($expected, $result);
    }

    public function testGetWithKeysDotNotation(): void
    {
        $array = [
            'app' => [
                'name' => 'MyApp',
                'logs' => [
                    'path' => '/var/log',
                    'level' => 'debug',
                ],
            ],
            'telegram' => [
                'bot_token' => 'token123',
                'chat_id' => 'chat456',
            ],
        ];

        $keys = ['app.name', 'app.logs.path', 'telegram.bot_token'];
        $result = Arr::getWithKeys($array, $keys);

        $expected = [
            'app' => [
                'name' => 'MyApp',
                'logs' => [
                    'path' => '/var/log',
                ],
            ],
            'telegram' => [
                'bot_token' => 'token123',
            ],
        ];

        $this->assertSame($expected, $result);
    }

    public function testGetWithKeysNonExistentKeys(): void
    {
        $array = [
            'app' => [
                'name' => 'MyApp',
            ],
            'telegram' => [
                'bot_token' => 'token123',
            ],
        ];

        $keys = ['app.name', 'app.nonexistent', 'telegram.bot_token', 'nonexistent.key'];
        $result = Arr::getWithKeys($array, $keys);

        $expected = [
            'app' => [
                'name' => 'MyApp',
            ],
            'telegram' => [
                'bot_token' => 'token123',
            ],
        ];

        $this->assertSame($expected, $result);
    }

    public function testGetWithKeysWithNullValues(): void
    {
        $array = [
            'app' => [
                'name' => 'MyApp',
                'icon' => null,
                'logs' => [
                    'path' => '/var/log',
                ],
            ],
        ];

        $keys = ['app.name', 'app.icon', 'app.logs.path'];
        $result = Arr::getWithKeys($array, $keys);

        $expected = [
            'app' => [
                'name' => 'MyApp',
                'icon' => null,
                'logs' => [
                    'path' => '/var/log',
                ],
            ],
        ];

        $this->assertSame($expected, $result);
    }

    public function testGetWithKeysEmptyKeysArray(): void
    {
        $array = [
            'app' => [
                'name' => 'MyApp',
            ],
        ];

        $keys = [];
        $result = Arr::getWithKeys($array, $keys);

        $this->assertSame([], $result);
    }

    public function testGetWithKeysPreservesStructure(): void
    {
        $array = [
            'app' => [
                'name' => 'MyApp',
                'config' => [
                    'database' => [
                        'host' => 'localhost',
                        'port' => 3306,
                        'username' => 'root',
                    ],
                ],
            ],
            'telegram' => [
                'bot_token' => 'token',
            ],
        ];

        $keys = ['app.config.database.host', 'app.config.database.port'];
        $result = Arr::getWithKeys($array, $keys);

        $expected = [
            'app' => [
                'config' => [
                    'database' => [
                        'host' => 'localhost',
                        'port' => 3306,
                    ],
                ],
            ],
        ];

        $this->assertSame($expected, $result);
    }

    public function testGetWithKeysMultipleKeysFromSameBranch(): void
    {
        $array = [
            'app' => [
                'name' => 'MyApp',
                'logs' => [
                    'path' => '/var/log',
                    'level' => 'debug',
                    'max_files' => 10,
                ],
            ],
        ];

        $keys = ['app.logs.path', 'app.logs.level', 'app.logs.max_files'];
        $result = Arr::getWithKeys($array, $keys);

        $expected = [
            'app' => [
                'logs' => [
                    'path' => '/var/log',
                    'level' => 'debug',
                    'max_files' => 10,
                ],
            ],
        ];

        $this->assertSame($expected, $result);
    }

    public function testGetWithKeysMixedTypes(): void
    {
        $array = [
            'app' => [
                'name' => 'MyApp',
                'enabled' => true,
                'count' => 42,
                'price' => 99.99,
                'tags' => ['tag1', 'tag2'],
            ],
        ];

        $keys = ['app.name', 'app.enabled', 'app.count', 'app.price', 'app.tags'];
        $result = Arr::getWithKeys($array, $keys);

        $expected = [
            'app' => [
                'name' => 'MyApp',
                'enabled' => true,
                'count' => 42,
                'price' => 99.99,
                'tags' => ['tag1', 'tag2'],
            ],
        ];

        $this->assertSame($expected, $result);
    }

    public function testGetWithKeysEmptyArray(): void
    {
        $array = [];
        $keys = ['app.name', 'telegram.bot_token'];
        $result = Arr::getWithKeys($array, $keys);

        $this->assertSame([], $result);
    }

    public function testExceptRemovesSpecifiedKeys(): void
    {
        $array = [
            'app' => 'telecart',
            'debug' => true,
            'version' => '1.0.0',
        ];

        $result = Arr::except($array, ['debug', 'nonexistent']);

        $expected = [
            'app' => 'telecart',
            'version' => '1.0.0',
        ];

        $this->assertSame($expected, $result);
    }

    public function testExceptReturnsOriginalArrayWhenNoKeysProvided(): void
    {
        $array = [
            'app' => 'telecart',
            'debug' => true,
        ];

        $result = Arr::except($array, []);

        $this->assertSame($array, $result);
    }
}
