<?php

declare(strict_types=1);

namespace PhpMyAdmin\Tests\Database;

use PhpMyAdmin\ConfigStorage\RelationParameters;
use PhpMyAdmin\Database\CentralColumns;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Tests\AbstractTestCase;
use PhpMyAdmin\Types;

use function array_slice;

/**
 * @covers \PhpMyAdmin\Database\CentralColumns
 */
class CentralColumnsTest extends AbstractTestCase
{
    /** @var CentralColumns */
    private $centralColumns;

    /** @var array<int, array<string, string|int>> */
    private $columnData = [
        [
            'col_name' => 'id',
            'col_type' => 'integer',
            'col_length' => 0,
            'col_isNull' => 0,
            'col_extra' => 'UNSIGNED,auto_increment',
            'col_default' => 1,
            'col_collation' => '',
        ],
        [
            'col_name' => 'col1',
            'col_type' => 'varchar',
            'col_length' => 100,
            'col_isNull' => 1,
            'col_extra' => 'BINARY',
            'col_default' => 1,
            'col_collation' => '',
        ],
        [
            'col_name' => 'col2',
            'col_type' => 'DATETIME',
            'col_length' => 0,
            'col_isNull' => 1,
            'col_extra' => 'on update CURRENT_TIMESTAMP',
            'col_default' => 'CURRENT_TIMESTAMP',
            'col_collation' => '',
        ],
    ];

    /** @var array<int, array<string, string|int>> */
    private $modifiedColumnData = [
        [
            'col_name' => 'id',
            'col_type' => 'integer',
            'col_length' => 0,
            'col_isNull' => 0,
            'col_extra' => 'auto_increment',
            'col_default' => 1,
            'col_collation' => '',
            'col_attribute' => 'UNSIGNED',
        ],
        [
            'col_name' => 'col1',
            'col_type' => 'varchar',
            'col_length' => 100,
            'col_isNull' => 1,
            'col_extra' => '',
            'col_default' => 1,
            'col_collation' => '',
            'col_attribute' => 'BINARY',
        ],
        [
            'col_name' => 'col2',
            'col_type' => 'DATETIME',
            'col_length' => 0,
            'col_isNull' => 1,
            'col_extra' => '',
            'col_default' => 'CURRENT_TIMESTAMP',
            'col_collation' => '',
            'col_attribute' => 'on update CURRENT_TIMESTAMP',
        ],
    ];

    /**
     * prepares environment for tests
     */
    protected function setUp(): void
    {
        parent::setUp();
        parent::setGlobalConfig();
        $GLOBALS['cfg']['Server']['user'] = 'pma_user';
        $GLOBALS['cfg']['Server']['DisableIS'] = true;
        $GLOBALS['cfg']['MaxRows'] = 10;
        $GLOBALS['cfg']['ServerDefault'] = 'PMA_server';
        $GLOBALS['cfg']['ActionLinksMode'] = 'icons';
        $GLOBALS['cfg']['CharEditing'] = '';
        $GLOBALS['cfg']['LimitChars'] = 50;
        $GLOBALS['db'] = 'PMA_db';
        $GLOBALS['table'] = 'PMA_table';

        $GLOBALS['server'] = 1;
        $_SESSION['relation'] = [];
        $_SESSION['relation'][$GLOBALS['server']] = RelationParameters::fromArray([
            'centralcolumnswork' => true,
            'relwork' => true,
            'db' => 'phpmyadmin',
            'relation' => 'relation',
            'central_columns' => 'pma_central_columns',
        ])->toArray();

        // mock DBI
        $dbi = $this->getMockBuilder(DatabaseInterface::class)
            ->disableOriginalConstructor()
            ->getMock();
        $dbi->types = new Types($dbi);
        $GLOBALS['dbi'] = $dbi;

        // set some common expectations
        $dbi->expects($this->any())
            ->method('selectDb')
            ->will($this->returnValue(true));
        $dbi->expects($this->any())
            ->method('getColumns')
            ->will(
                $this->returnValue(
                    [
                        'id' => [
                            'Type' => 'integer',
                            'Null' => 'NO',
                        ],
                        'col1' => [
                            'Type' => 'varchar(100)',
                            'Null' => 'YES',
                        ],
                        'col2' => [
                            'Type' => 'DATETIME',
                            'Null' => 'NO',
                        ],
                    ]
                )
            );
        $dbi->expects($this->any())
            ->method('getColumnNames')
            ->will($this->returnValue(['id', 'col1', 'col2']));
        $dbi->expects($this->any())
            ->method('tryQuery')
            ->will($this->returnValue(true));
        $dbi->expects($this->any())
            ->method('getTables')
            ->will(
                $this->returnValue(['PMA_table', 'PMA_table1', 'PMA_table2'])
            );
        $dbi->expects($this->any())->method('escapeString')
            ->will($this->returnArgument(0));

        $this->centralColumns = new CentralColumns($dbi);
    }

    /**
     * Test for getParams
     */
    public function testGetParams(): void
    {
        self::assertSame([
            'user' => 'pma_user',
            'db' => 'phpmyadmin',
            'table' => 'pma_central_columns',
        ], $this->centralColumns->getParams());
    }

    /**
     * Test for getColumnsList
     */
    public function testGetColumnsList(): void
    {
        $GLOBALS['dbi']->expects($this->exactly(2))
            ->method('fetchResult')
            ->willReturnOnConsecutiveCalls(
                $this->columnData,
                array_slice($this->columnData, 1, 2)
            );

        self::assertSame($this->modifiedColumnData, $this->centralColumns->getColumnsList('phpmyadmin'));
        self::assertSame(
            array_slice($this->modifiedColumnData, 1, 2),
            $this->centralColumns->getColumnsList('phpmyadmin', 1, 2)
        );
    }

    /**
     * Test for getCount
     */
    public function testGetCount(): void
    {
        $GLOBALS['dbi']->expects($this->once())
            ->method('fetchResult')
            ->with(
                'SELECT count(db_name) FROM `pma_central_columns` WHERE db_name = \'phpmyadmin\';',
                null,
                null,
                DatabaseInterface::CONNECT_CONTROL
            )
            ->will(
                $this->returnValue([3])
            );

        self::assertSame(3, $this->centralColumns->getCount('phpmyadmin'));
    }

    /**
     * Test for syncUniqueColumns
     */
    public function testSyncUniqueColumns(): void
    {
        $_POST['db'] = 'PMA_db';
        $_POST['table'] = 'PMA_table';

        self::assertTrue($this->centralColumns->syncUniqueColumns(
            ['PMA_table']
        ));
    }

    /**
     * Test for makeConsistentWithList
     */
    public function testMakeConsistentWithList(): void
    {
        $GLOBALS['dbi']->expects($this->any())
            ->method('fetchResult')
            ->will(
                $this->returnValue($this->columnData)
            );
        $GLOBALS['dbi']->expects($this->any())
            ->method('fetchValue')
            ->will(
                $this->returnValue('PMA_table=CREATE table `PMA_table` (id integer)')
            );
        self::assertTrue($this->centralColumns->makeConsistentWithList(
            'phpmyadmin',
            ['PMA_table']
        ));
    }

    /**
     * Test for getFromTable
     */
    public function testGetFromTable(): void
    {
        $db = 'PMA_db';
        $table = 'PMA_table';

        $GLOBALS['dbi']->expects($this->once())
            ->method('fetchResult')
            ->with(
                'SELECT col_name FROM `pma_central_columns` '
                . "WHERE db_name = 'PMA_db' AND col_name IN ('id','col1','col2');",
                null,
                null,
                DatabaseInterface::CONNECT_CONTROL
            )
            ->will(
                $this->returnValue(['id', 'col1'])
            );
        self::assertSame([
            'id',
            'col1',
        ], $this->centralColumns->getFromTable(
            $db,
            $table
        ));
    }

    /**
     * Test for getFromTable with $allFields = true
     */
    public function testGetFromTableWithAllFields(): void
    {
        $db = 'PMA_db';
        $table = 'PMA_table';

        $GLOBALS['dbi']->expects($this->once())
            ->method('fetchResult')
            ->with(
                'SELECT * FROM `pma_central_columns` '
                . "WHERE db_name = 'PMA_db' AND col_name IN ('id','col1','col2');",
                null,
                null,
                DatabaseInterface::CONNECT_CONTROL
            )
            ->will(
                $this->returnValue(array_slice($this->columnData, 0, 2))
            );
        self::assertSame(array_slice($this->modifiedColumnData, 0, 2), $this->centralColumns->getFromTable(
            $db,
            $table,
            true
        ));
    }

    /**
     * Test for updateOneColumn
     */
    public function testUpdateOneColumn(): void
    {
        self::assertTrue($this->centralColumns->updateOneColumn(
            'phpmyadmin',
            '',
            '',
            '',
            '',
            '',
            0,
            '',
            '',
            ''
        ));
        self::assertTrue($this->centralColumns->updateOneColumn(
            'phpmyadmin',
            'col1',
            '',
            '',
            '',
            '',
            0,
            '',
            '',
            ''
        ));
    }

    /**
     * Test for updateMultipleColumn
     */
    public function testUpdateMultipleColumn(): void
    {
        $params = [];
        $params['db'] = 'phpmyadmin';
        $params['orig_col_name'] = [
            'col1',
            'col2',
        ];
        $params['field_name'] = [
            'col1',
            'col2',
        ];
        $params['field_default_type'] = [
            '',
            '',
        ];
        $params['col_extra'] = [
            '',
            '',
        ];
        $params['field_length'] = [
            '',
            '',
        ];
        $params['field_attribute'] = [
            '',
            '',
        ];
        $params['field_type'] = [
            '',
            '',
        ];
        $params['field_collation'] = [
            '',
            '',
        ];
        self::assertTrue($this->centralColumns->updateMultipleColumn($params));
    }

    /**
     * Test for getHtmlForEditingPage
     */
    public function testGetHtmlForEditingPage(): void
    {
        $GLOBALS['dbi']->expects($this->any())
            ->method('fetchResult')
            ->with(
                'SELECT * FROM `pma_central_columns` '
                . "WHERE db_name = 'phpmyadmin' AND col_name IN ('col1','col2');",
                null,
                null,
                DatabaseInterface::CONNECT_CONTROL
            )
            ->will(
                $this->returnValue($this->columnData)
            );
        $result = $this->centralColumns->getHtmlForEditingPage(
            [
                'col1',
                'col2',
            ],
            'phpmyadmin'
        );
        $list_detail_cols = $this->callFunction(
            $this->centralColumns,
            CentralColumns::class,
            'findExistingColNames',
            [
                'phpmyadmin',
                "'col1','col2'",
                true,
            ]
        );
        self::assertStringContainsString($this->callFunction(
            $this->centralColumns,
            CentralColumns::class,
            'getHtmlForEditTableRow',
            [
                $list_detail_cols[0],
                0,
            ]
        ), $result);
    }

    /**
     * Test for getListRaw
     */
    public function testGetListRaw(): void
    {
        $GLOBALS['dbi']->expects($this->once())
            ->method('fetchResult')
            ->with(
                'SELECT * FROM `pma_central_columns` WHERE db_name = \'phpmyadmin\';',
                null,
                null,
                DatabaseInterface::CONNECT_CONTROL
            )
            ->will(
                $this->returnValue($this->columnData)
            );
        self::assertSame($this->modifiedColumnData, $this->centralColumns->getListRaw(
            'phpmyadmin',
            ''
        ));
    }

    /**
     * Test for getListRaw with a table name
     */
    public function testGetListRawWithTable(): void
    {
        $GLOBALS['dbi']->expects($this->once())
            ->method('fetchResult')
            ->with(
                'SELECT * FROM `pma_central_columns` '
                . "WHERE db_name = 'phpmyadmin' AND col_name "
                . "NOT IN ('id','col1','col2');",
                null,
                null,
                DatabaseInterface::CONNECT_CONTROL
            )
            ->will(
                $this->returnValue($this->columnData)
            );
        self::assertSame($this->modifiedColumnData, $this->centralColumns->getListRaw(
            'phpmyadmin',
            'table1'
        ));
    }

    /**
     * Test for findExistingColNames
     */
    public function testFindExistingColNames(): void
    {
        $GLOBALS['dbi']->expects($this->once())
            ->method('fetchResult')
            ->with(
                'SELECT * FROM `pma_central_columns` WHERE db_name = \'phpmyadmin\' AND col_name IN (\'col1\');',
                null,
                null,
                DatabaseInterface::CONNECT_CONTROL
            )
            ->will(
                $this->returnValue(array_slice($this->columnData, 1, 1))
            );
        self::assertSame(array_slice($this->modifiedColumnData, 1, 1), $this->callFunction(
            $this->centralColumns,
            CentralColumns::class,
            'findExistingColNames',
            [
                'phpmyadmin',
                "'col1'",
                true,
            ]
        ));
    }

    public function testGetColumnsNotInCentralList(): void
    {
        $columns = $this->centralColumns->getColumnsNotInCentralList('PMA_db', 'PMA_table');
        self::assertIsArray($columns);
        self::assertSame(['id', 'col1', 'col2'], $columns);
    }
}
