<?php
/*
 * PSX is an open source PHP framework to develop RESTful APIs.
 * For the current version and information visit <https://phpsx.org>
 *
 * Copyright 2010-2022 Christoph Kappestein <christoph.kappestein@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace PSX\Sql\Tests;

use PHPUnit\Framework\TestCase;
use PSX\Sql\Condition;

/**
 * ConditionTest
 *
 * @author  Christoph Kappestein <christoph.kappestein@gmail.com>
 * @license http://www.apache.org/licenses/LICENSE-2.0
 * @link    https://phpsx.org
 */
class ConditionTest extends TestCase
{
    public function testCondition()
    {
        $con = new Condition(array('id', '=', '1'));

        $this->assertEquals('WHERE (id = ?)', $con->getStatement());
        $this->assertEquals(array('1'), $con->getValues());

        $con = new Condition();
        $con->add('id', '=', '1');

        $this->assertEquals('WHERE (id = ?)', $con->getStatement());
        $this->assertEquals(array('1'), $con->getValues());
    }

    public function testConditionConstructor()
    {
        $con = new Condition(['id', '=', '1', 'OR'], ['id', '=', '2']);

        $this->assertEquals('WHERE (id = ? OR id = ?)', $con->getStatement());
        $this->assertEquals(array('1', '2'), $con->getValues());


        $con = new Condition(['id', '=', '1', 'OR'], new Condition\Raw('(1 = 1 OR 2 = 2)'));

        $this->assertEquals('WHERE (id = ? OR (1 = 1 OR 2 = 2))', $con->getStatement());
        $this->assertEquals(array('1'), $con->getValues());
    }

    public function testConditionConstructorException()
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('Condition argument must be either an array or PSX\Sql\Condition\ExpressionInterface');

        new Condition('foo');
    }

    public function testConditionMultiple()
    {
        $con = new Condition(array('id', '=', '1', 'OR'));
        $con->add('id', '=', '2');

        $this->assertEquals('WHERE (id = ? OR id = ?)', $con->getStatement());
        $this->assertEquals(array('1', '2'), $con->getValues());

        $con2 = new Condition();
        $con2->add('id', '=', '1');
        $con2->add('id', '=', '2');
        $con2->addExpression($con);

        $this->assertEquals('WHERE (id = ? AND id = ? AND (id = ? OR id = ?))', $con2->getStatement());
        $this->assertEquals(array('1', '2', '1', '2'), $con2->getValues());
    }

    public function testAdd()
    {
        $con = new Condition();
        $con->add('id', '=', '1');
        $con->add('foo', 'IN', array(1, 2));

        $this->assertEquals('WHERE (id = ? AND foo IN (?,?))', $con->getStatement());
        $this->assertEquals(array('1', 1, 2), $con->getValues());
    }

    public function testAddInvalidOperator()
    {
        $this->expectException(\InvalidArgumentException::class);

        $con = new Condition();
        $con->add('id', 'foo', '1');
    }

    public function testAddInvalidConjunction()
    {
        $this->expectException(\InvalidArgumentException::class);

        $con = new Condition();
        $con->add('id', '=', '1', 'foo');
    }

    public function testEquals()
    {
        $con = new Condition();
        $con->equals('foo', 2);

        $this->assertEquals('WHERE (foo = ?)', $con->getStatement());
        $this->assertEquals([2], $con->getValues());
    }

    public function testNotEquals()
    {
        $con = new Condition();
        $con->notEquals('foo', 2);

        $this->assertEquals('WHERE (foo != ?)', $con->getStatement());
        $this->assertEquals([2], $con->getValues());
    }

    public function testGreater()
    {
        $con = new Condition();
        $con->greater('foo', 2);

        $this->assertEquals('WHERE (foo > ?)', $con->getStatement());
        $this->assertEquals([2], $con->getValues());
    }

    public function testGreaterThen()
    {
        $con = new Condition();
        $con->greaterThen('foo', 2);

        $this->assertEquals('WHERE (foo >= ?)', $con->getStatement());
        $this->assertEquals([2], $con->getValues());
    }

    public function testLower()
    {
        $con = new Condition();
        $con->lower('foo', 2);

        $this->assertEquals('WHERE (foo < ?)', $con->getStatement());
        $this->assertEquals([2], $con->getValues());
    }

    public function testLowerThen()
    {
        $con = new Condition();
        $con->lowerThen('foo', 2);

        $this->assertEquals('WHERE (foo <= ?)', $con->getStatement());
        $this->assertEquals([2], $con->getValues());
    }

    public function testLike()
    {
        $con = new Condition();
        $con->like('foo', 'bar');

        $this->assertEquals('WHERE (foo LIKE ?)', $con->getStatement());
        $this->assertEquals(['bar'], $con->getValues());
    }

    public function testNotLike()
    {
        $con = new Condition();
        $con->notLike('foo', 'bar');

        $this->assertEquals('WHERE (foo NOT LIKE ?)', $con->getStatement());
        $this->assertEquals(['bar'], $con->getValues());
    }

    public function testBetween()
    {
        $con = new Condition();
        $con->between('id', 8, 16);

        $this->assertEquals('WHERE (id BETWEEN ? AND ?)', $con->getStatement());
        $this->assertEquals([8, 16], $con->getValues());
    }

    public function testIn()
    {
        $con = new Condition();
        $con->in('id', [8, 16]);

        $this->assertEquals('WHERE (id IN (?,?))', $con->getStatement());
        $this->assertEquals([8, 16], $con->getValues());
    }

    public function testNil()
    {
        $con = new Condition();
        $con->nil('foo');

        $this->assertEquals('WHERE (foo IS NULL)', $con->getStatement());
        $this->assertEquals([], $con->getValues());
    }

    public function testNotNil()
    {
        $con = new Condition();
        $con->notNil('foo');

        $this->assertEquals('WHERE (foo IS NOT NULL)', $con->getStatement());
        $this->assertEquals([], $con->getValues());
    }

    public function testRaw()
    {
        $con = new Condition();
        $con->raw('foo IN (SELECT id FROM foo WHERE id = ?)', [1]);

        $this->assertEquals('WHERE (foo IN (SELECT id FROM foo WHERE id = ?))', $con->getStatement());
        $this->assertEquals([1], $con->getValues());
    }

    public function testRegexp()
    {
        $con = new Condition();
        $con->regexp('foo', '[A-z]+');

        $this->assertEquals('WHERE (foo RLIKE ?)', $con->getStatement());
        $this->assertEquals(['[A-z]+'], $con->getValues());
    }

    public function testCount()
    {
        $con = new Condition();
        $con->add('id', '=', '1');
        $con->add('id', '=', '2');
        $con->add('id', '=', '3');

        $this->assertEquals(3, count($con));
    }

    public function testMerge()
    {
        $con_1 = new Condition(array('id', '=', '1'));
        $con_2 = new Condition(array('id', '=', '2'));

        $this->assertEquals(true, $con_1->hasCondition());

        $con_1->merge($con_2);

        $this->assertEquals('WHERE (id = ? AND id = ?)', $con_1->getStatement());
        $this->assertEquals(array('1', '2'), $con_1->getValues());
        $this->assertEquals(true, $con_1->hasCondition());
    }

    public function testGet()
    {
        $con = new Condition();
        $con->add('title', '=', '2');

        $titleExpression = $con->get('title');

        $this->assertInstanceOf('PSX\Sql\Condition\ExpressionInterface', $titleExpression);
        $this->assertEquals(['2'], $titleExpression->getValues());

        $this->assertNull($con->get('foo'));
    }

    public function testRemove()
    {
        $con = new Condition();
        $con->add('id', '=', '1');
        $con->add('title', '=', '2');
        $con->add('date', '=', '3');

        $this->assertEquals(3, count($con));

        $con->remove('title');

        $this->assertEquals(2, count($con));
    }

    public function testRemoveInvalidColumn()
    {
        $con = new Condition();
        $con->add('id', '=', '1');

        $this->assertEquals(1, count($con));

        $con->remove('title');

        $this->assertEquals(1, count($con));
    }

    public function testRemoveAll()
    {
        $con = new Condition(array('id', '=', '1'));

        $this->assertEquals('WHERE (id = ?)', $con->getStatement());
        $this->assertEquals(array('1'), $con->getValues());
        $this->assertEquals(true, $con->hasCondition());

        $con->removeAll();

        $this->assertEquals('WHERE 1 = 1', $con->getStatement());
        $this->assertEquals(array(), $con->getValues());
        $this->assertEquals(false, $con->hasCondition());
    }

    public function testGetStatment()
    {
        $con = Condition::fromCriteria(array(
            'foo' => 'bar',
            'bar' => array(1, 2),
            'baz' => null,
        ));

        $this->assertEquals('WHERE (foo = ? AND bar IN (?,?) AND baz IS NULL)', $con->getStatement());
        $this->assertEquals('WHERE (foo = ? AND bar IN (?,?) AND baz IS NULL)', $con->getStatement());
    }

    public function testGetValues()
    {
        $con = Condition::fromCriteria(array(
            'foo' => 'bar',
            'bar' => array(1, 2),
            'baz' => null,
        ));

        $this->assertEquals(array('bar', 1, 2), $con->getValues());
    }

    public function testGetArray()
    {
        $criteria = array(
            'foo' => 'bar',
            'bar' => array(1, 2),
            'baz' => null,
        );

        $con = Condition::fromCriteria($criteria);

        $this->assertEquals($criteria, $con->getArray());
    }

    public function testFromCriteria()
    {
        $con = Condition::fromCriteria(array(
            'foo' => 'bar',
            'bar' => array(1, 2),
            'baz' => null,
        ));

        $result = $con->toArray();

        $this->assertContainsOnlyInstancesOf('PSX\Sql\Condition\ExpressionInterface', $result);
        $this->assertEquals('WHERE (foo = ? AND bar IN (?,?) AND baz IS NULL)', $con->getStatement());
    }
}
