<?php

namespace Tests\Unit\Framework;

use Mockery as m;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\Grammars\MySqlGrammar;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
use Tests\TestCase;

class MySqlGrammarTest extends TestCase
{
    private $grammar;

    public function setUp(): void
    {
        parent::setUp();

        $this->grammar = new MySqlGrammar();
    }

    public function tearDown(): void
    {
        parent::tearDown();

        m::close();
    }

    public function testCompileFrom(): void
    {
        $this->assertEquals(
            'FROM foobar AS alias',
            $this->grammar->compileFrom(m::mock(Builder::class), ['table' => 'foobar', 'as' => 'alias'])
        );

        $this->assertEquals(
            'FROM foobar',
            $this->grammar->compileFrom(m::mock(Builder::class), ['table' => 'foobar', 'as' => null])
        );
    }

    public function testCompileLimit(): void
    {
        $this->assertEquals('LIMIT 10', $this->grammar->compileLimit(m::mock(Builder::class), 10));
    }

    public function testCompileOffset(): void
    {
        $this->assertEquals('OFFSET 10', $this->grammar->compileOffset(m::mock(Builder::class), 10));
    }

    public function testCompileColumns(): void
    {
        $this->assertEquals('SELECT *', $this->grammar->compileColumns(m::mock(Builder::class), []));
        $this->assertEquals('SELECT foo, bar', $this->grammar->compileColumns(m::mock(Builder::class), [
            ['column' => 'foo', 'as' => null],
            ['column' => 'bar', 'as' => null],
        ]));
        $this->assertEquals('SELECT foo AS bar, xxx AS yyy', $this->grammar->compileColumns(
            m::mock(Builder::class), [
            ['column' => 'foo', 'as' => 'bar'],
            ['column' => 'xxx', 'as' => 'yyy'],
        ]));
    }

    public function testCompileWheres(): void
    {
        $this->assertEquals('', $this->grammar->compileWheres(m::mock(Builder::class), []));
        $this->assertEquals('WHERE foo IS NOT NULL', $this->grammar->compileWheres(m::mock(Builder::class), [
            [
                'type' => 'NotNull',
                'column' => 'foo',
                'boolean' => 'and',
            ]
        ]));
        $this->assertEquals('WHERE foo IS NULL', $this->grammar->compileWheres(m::mock(Builder::class), [
            [
                'type' => 'Null',
                'column' => 'foo',
                'boolean' => 'and',
            ]
        ]));

        $this->assertEquals('WHERE foo = ? OR bar = ? AND xxx IS NULL', $this->grammar->compileWheres(
            m::mock(Builder::class), [
            [
                'type' => 'Basic',
                'column' => 'foo',
                'operator' => '=',
                'value' => 'val',
                'boolean' => 'and',
            ],
            [
                'type' => 'Basic',
                'column' => 'bar',
                'operator' => '=',
                'value' => 'val',
                'boolean' => 'or',
            ],
            [
                'type' => 'Null',
                'column' => 'xxx',
                'boolean' => 'and',
            ]
        ]));
    }

    public function testCompileOrders(): void
    {
        $this->assertEquals('ORDER BY foo DESC, bar ASC', $this->grammar->compileOrders(
            m::mock(Builder::class), [
            ['column' => 'foo', 'direction' => 'DESC'],
            ['column' => 'bar', 'direction' => 'ASC'],
        ]));
    }

    public function testWhereNotNull(): void
    {
        $this->assertEquals('foo IS NOT NULL', $this->grammar->whereNotNull([
            'type' => 'NotNull',
            'column' => 'foo',
            'boolean' => 'and',
        ]));
    }

    public function testWhereNull(): void
    {
        $this->assertEquals('foo IS NULL', $this->grammar->whereNull([
            'type' => 'Null',
            'column' => 'foo',
            'boolean' => 'and',
        ]));
    }

    public function testWhereBasic(): void
    {
        $this->assertEquals('foo = ?', $this->grammar->whereBasic([
            'type' => 'Basic',
            'column' => 'foo',
            'operator' => '=',
            'value' => 'val',
            'boolean' => 'and',
        ]));
    }

    public function testConcatCompiled(): void
    {
        $this->assertEquals(
        /** @lang text */ 'SELECT * FROM table WHERE foo = ? LIMIT 10',
            $this->grammar->concatCompiled([
                'wheres' => 'WHERE foo = ?',
                'limit' => 'LIMIT 10',
                'columns' => 'SELECT *',
                'from' => 'FROM table',
            ])
        );
    }

    public function testCompileJoins(): void
    {
        $joinClause = m::mock(JoinClause::class);
        $joinClause->table = 'table AS t';
        $joinClause->type = 'inner';
        $joinClause->first = 't.id';
        $joinClause->operator = '=';
        $joinClause->second = 'parent.id';
        $joinClause->wheres = [
            [
                'type' => 'Basic',
                'column' => 't.foo',
                'operator' => '=',
                'value' => 'bar',
                'boolean' => 'and',
            ]
        ];

        $builder = m::mock(Builder::class);

        $this->assertEquals(
            'INNER JOIN table AS t ON t.id = parent.id AND t.foo = ?',
            $this->grammar->compileJoins(
                $builder,
                [
                    $joinClause,
                ]
            )
        );
    }

    public function testCompileWhereIn(): void
    {
        $this->assertEquals('foo IN (?, ?, ?, ?)', $this->grammar->whereIn([
            'type' => 'In',
            'column' => 'foo',
            'operator' => 'IN',
            'value' => [1, 2, 3, 4],
            'boolean' => 'and',
        ]));
    }

    public function testCompileWhereNotIn(): void
    {
        $this->assertEquals('foo NOT IN (?, ?, ?, ?)', $this->grammar->whereIn([
            'type' => 'In',
            'column' => 'foo',
            'operator' => 'NOT IN',
            'value' => [1, 2, 3, 4],
            'boolean' => 'and',
        ]));
    }

    public function testCompileDistinctColumns(): void
    {
        $mock = m::mock(Builder::class);
        $mock->distinct = true;

        $this->assertEquals('SELECT DISTINCT foo, bar', $this->grammar->compileColumns($mock, [
            ['column' => 'foo', 'as' => null],
            ['column' => 'bar', 'as' => null],
        ]));
    }

    public function testCompileGroupBy(): void
    {
        $mock = m::mock(Builder::class);
        $mock->groupBy = ['foo', 'bar'];

        $this->assertEquals(
            'GROUP BY foo, bar',
            $this->grammar->compileGroupBy($mock, ['foo', 'bar'])
        );
    }

    public function testCompileUpdate(): void
    {
        $builder = m::mock(Builder::class);
        $builder->joins = [];
        $builder->wheres = [
            [
                'type' => 'Basic',
                'column' => 'id',
                'operator' => '=',
                'value' => 1,
                'boolean' => 'and',
            ]
        ];

        $this->assertEquals(
            "UPDATE `table` SET `foo` = ?, `bar` = ? WHERE id = ?",
            $this->grammar->compileUpdate($builder, 'table', ['foo' => 'bar', 'bar' => 'baz'])
        );
    }

    public function testCompileUpdateWithJoins(): void
    {
        $joinClause = m::mock(JoinClause::class);
        $joinClause->table = 'other_table';
        $joinClause->type = 'inner';
        $joinClause->first = 'table.id';
        $joinClause->operator = '=';
        $joinClause->second = 'other_table.id';
        $joinClause->wheres = [];

        $builder = m::mock(Builder::class);
        $builder->joins = [$joinClause];
        $builder->wheres = [
            [
                'type' => 'Basic',
                'column' => 'table.id',
                'operator' => '=',
                'value' => 1,
                'boolean' => 'and',
            ]
        ];

        $this->assertEquals(
            "UPDATE `table` INNER JOIN other_table ON table.id = other_table.id SET `foo` = ?, `bar` = ? WHERE table.id = ?",
            $this->grammar->compileUpdate($builder, 'table', ['foo' => 'bar', 'bar' => 'baz'])
        );
    }
}