<?php

namespace MotorORM\Tests;

use MotorORM\Collection;
use MotorORM\CollectionPaginate;
use MotorOrm\Tests\Models\Test;
use MotorOrm\Tests\Models\Test2;
use MotorOrm\Tests\Models\Test3;

/**
 * @coversDefaultClass \MotorORM\Builder
 */
final class BuilderTest extends TestCase
{
    /**
     * Find by primary key
     * @covers ::find()
     */
    public function testFind(): void
    {
        $find = Test::query()->find(17);

        $this->assertIsObject($find);
        $this->assertEquals('17', $find->id);
    }

    /**
     * Find by primary key empty
     * @covers ::find()
     */
    public function testFindEmpty(): void
    {
        $find = Test::query()->find(777);

        $this->assertNull($find);
    }

    /**
     * Find by name limit 1
     * @covers ::where()
     */
    public function testWhereLimit(): void
    {
        $find = Test::query()->where('name', 'Миша')->limit(1)->get();

        $this->assertInstanceOf(Collection::class, $find);
        $this->assertIsObject($find[0]);
        $this->assertObjectHasAttribute('attr', $find[0]);
        $this->assertEquals('Миша', $find[0]->name);
        $this->assertEquals('Заголовок10', $find[0]->title);
    }

    /**
     * Find by name and last 1
     *
     * @covers ::where()
     */
    public function testWhereLimitLast(): void
    {
        $find = Test::query()->where('name', 'Миша')->orderByDesc('id')->first();

        $this->assertIsObject($find);
        $this->assertObjectHasAttribute('attr', $find);
        $this->assertEquals('Миша', $find->name);
        $this->assertEquals('Заголовок18', $find->title);
    }


    /**
     * Find by name and title
     *
     * @covers ::where()
     */
    public function testWhereWhereGet(): void
    {
        $find = Test::query()->where('name', 'Миша')->where('title', 'Заголовок10')->get();

        $this->assertInstanceOf(Collection::class, $find);
        $this->assertIsObject($find[0]);
        $this->assertObjectHasAttribute('attr', $find[0]);
        $this->assertEquals('Миша', $find[0]->name);
        $this->assertEquals('Заголовок10', $find[0]->title);
    }

    /**
     * Find by condition
     *
     * @covers ::where()
     */
    public function testWhere(): void
    {
        $find = Test::query()->where('time', '>=', 1231231235)->get();

        $this->assertInstanceOf(Collection::class, $find);
        $this->assertClassHasAttribute('elements', Collection::class);
        $this->assertClassNotHasAttribute('paginator', Collection::class);
        $this->assertIsObject($find[0]);
        $this->assertCount(3, $find);
        $this->assertObjectHasAttribute('attr', $find[0]);
        $this->assertGreaterThanOrEqual('1231231235', $find[0]->time);
        $this->assertGreaterThanOrEqual('1231231235', $find[1]->time);
        $this->assertGreaterThanOrEqual('1231231235', $find[2]->time);
    }

    /**
     * Find by condition
     *
     * @covers ::where()
     */
    public function testWherePaginate(): void
    {
        $find = Test::query()->where('time', '>=', 1231231235)->paginate(2);

        $this->assertInstanceOf(CollectionPaginate::class, $find);
        $this->assertClassHasAttribute('elements', CollectionPaginate::class);
        $this->assertClassHasAttribute('paginator', CollectionPaginate::class);
        $this->assertIsObject($find[0]);
        $this->assertCount(2, $find);
        $this->assertObjectHasAttribute('attr', $find[0]);
    }

    /**
     * Find by condition in
     *
     * @covers ::whereIn()
     */
    public function testWhereIn(): void
    {
        $find = Test::query()->whereIn('id', [1, 3, 5, 7])->get();

        $this->assertInstanceOf(Collection::class, $find);
        $this->assertCount(4, $find);
        $this->assertEquals('1', $find[0]->id);
        $this->assertEquals('3', $find[1]->id);
        $this->assertEquals('5', $find[2]->id);
        $this->assertEquals('7', $find[3]->id);
    }

    /**
     * Find by condition not in
     *
     * @covers ::whereNotIn()
     */
    public function testWhereNotIn(): void
    {
        $find = Test::query()->whereNotIn('id', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])->get();

        $this->assertInstanceOf(Collection::class, $find);
        $this->assertCount(10, $find);
        $this->assertEquals('11', $find[0]->id);
        $this->assertEquals('12', $find[1]->id);
        $this->assertEquals('13', $find[2]->id);
        $this->assertEquals('14', $find[3]->id);
        $this->assertEquals('15', $find[4]->id);
        $this->assertEquals('16', $find[5]->id);
        $this->assertEquals('17', $find[6]->id);
        $this->assertEquals('18', $find[7]->id);
        $this->assertEquals('19', $find[8]->id);
        $this->assertEquals('20', $find[9]->id);
    }

    /**
     * Get count
     *
     * @covers ::count()
     */
    public function testCount(): void
    {
        $find = Test::query()->count();

        $this->assertEquals(20, $find);
    }

    /**
     * Get where count
     *
     * @covers ::count()
     */
    public function testWhereCount(): void
    {
        $find = Test::query()->where('time', '>', 1231231234)->count();

        $this->assertEquals(3, $find);
    }

    /**
     * Get lines 1 - 10
     *
     * @covers ::get()
     */
    public function testOffsetLimitGet(): void
    {
        $find = Test::query()->offset(0)->limit(10)->get();

        $this->assertCount(10, $find);
        $this->assertEquals('Заголовок1', $find[0]->title);
        $this->assertEquals('Заголовок7', $find[6]->title);
        $this->assertEquals('Заголовок10', $find[9]->title);
    }

    /**
     * Get headers
     *
     * @covers ::headers()
     */
    public function testHeaders(): void
    {
        $find = Test::query()->headers();

        $this->assertCount(5, $find);
        $this->assertEquals('id', $find[0]);
        $this->assertEquals('name', $find[1]);
        $this->assertEquals('title', $find[2]);
        $this->assertEquals('text', $find[3]);
        $this->assertEquals('time', $find[4]);
    }

    /**
     * Get first line
     *
     * @covers ::first()
     */
    public function testFirst(): void
    {
        $find = Test::query()->first();

        $this->assertIsObject($find);
        $this->assertObjectHasAttribute('attr', $find);
        $this->assertEquals('Петя', $find->name);
        $this->assertEquals('Заголовок1', $find->title);
    }

    /**
     * Get first line empty
     *
     * @covers ::first()
     */
    public function testFirstEmpty(): void
    {
        $find = Test3::query()->first();

        $this->assertNull($find);
    }

    /**
     * Get where first line empty
     *
     * @covers ::first()
     */
    public function testWhereFirstEmpty(): void
    {
        $find = Test::query()->where('name', 'something')->first();

        $this->assertNull($find);
    }

    /**
     * Get first 3 lines
     *
     * @covers ::first()
     */
    public function testFirst3(): void
    {
        $find = Test::query()->limit(3)->get();

        $this->assertCount(3, $find);
        $this->assertObjectHasAttribute('attr', $find[0]);
        $this->assertEquals('Петя', $find[0]->name);
        $this->assertEquals('Заголовок1', $find[0]->title);
    }

    /**
     * Get last 3 lines
     *
     * @covers ::first()
     */
    public function testLast3(): void
    {
        $find = Test::query()->orderByDesc('id')->limit(3)->get();

        $this->assertCount(3, $find);
        $this->assertObjectHasAttribute('attr', $find[0]);
        $this->assertEquals('Петя', $find[0]->name);
        $this->assertEquals('Заголовок20', $find[0]->title);
    }

    /**
     * Find by string primary key
     * @covers ::find()
     */
    public function testFindStringKey(): void
    {
        $find = Test2::query()->find('key1');

        $this->assertIsObject($find);
        $this->assertEquals('key1', $find->key);
        $this->assertEquals('500', $find->value);
    }

    /**
     * Find by empty string primary key
     * @covers ::find()
     */
    public function testFindEmptyStringKey(): void
    {
        $find = Test2::query()->find('key3');

        $this->assertIsObject($find);
        $this->assertEquals('key3', $find->key);
        $this->assertEquals('', $find->value);
    }

    /**
     * Get all
     * @covers ::get()
     */
    public function testAllGet(): void
    {
        $find = Test2::query()->get();

        $this->assertCount(5, $find);
        $this->assertObjectHasAttribute('attr', $find[0]);
        $this->assertEquals('key1', $find[0]->key);
        $this->assertEquals('500', $find[0]->value);
    }

    /**
     * Find by name and sort (time asc)
     * @covers ::orderBy()
     */
    public function testSort(): void
    {
        $find = Test::query()->where('name', 'Миша')->orderBy('time')->limit(3)->get();

        $this->assertCount(3, $find);
        $this->assertEquals(10, $find[0]->id);
        $this->assertEquals(18, $find[2]->id);
    }

    /**
     * Find by name and double sort (time desc, id desc)
     * @covers ::orderByDesc()
     */
    public function testDoubleSort(): void
    {
        $find = Test::query()->where('name', 'Миша')->orderBy('time', 'desc')->orderByDesc('id')->limit(3)->get();

        $this->assertCount(3, $find);
        $this->assertEquals(18, $find[0]->id);
        $this->assertEquals(10, $find[2]->id);
    }

    /**
     * Create field
     * @covers ::create()
     */
    public function testCreate(): void
    {
        $data = Test3::query()->create([
           'name' => 'name1',
           'value' => 555,
        ]);

        $find = Test3::query()->orderByDesc('id')->first();

        $this->assertEquals($find->id, $data->id);
        $this->assertObjectHasAttribute('attr', $find);
        $this->assertEquals('name1', $find->name);
        $this->assertEquals('555', $find->value);

        Test3::query()->truncate();
    }

    /**
     * Create multiple fields
     * @covers ::create()
     */
    public function testMultipleCreate(): void
    {
        foreach ($this->data() as $val) {
            Test3::query()->create($val);
        }

        $find = Test3::query()->get();

        $this->assertCount(6, $find);
        $this->assertEquals('name3', $find[2]->name);
        $this->assertEquals('value3', $find[2]->value);

        Test3::query()->truncate();
    }

    /**
     * Update fields
     * @covers ::update()
     */
    public function testFindUpdate(): void
    {
        foreach ($this->data() as $val) {
            Test3::query()->create($val);
        }

        $updatedLines = Test3::query()->find(1)->update(['name' => 'yyy', 'value' => 999]);

        $find = Test3::query()->find(1);

        $this->assertEquals(1, $updatedLines);
        $this->assertEquals('yyy', $find->name);
        $this->assertEquals('999', $find->value);

        Test3::query()->truncate();
    }

    /**
     * Update fields
     * @covers ::update()
     */
    public function testUpdate(): void
    {
        foreach ($this->data() as $val) {
            Test3::query()->create($val);
        }

        Test3::query()->where('id', 3)->update(['name' => 'xxx', 'value' => 888]);

        $find = Test3::query()->find(3);

        $this->assertEquals('xxx', $find->name);
        $this->assertEquals('888', $find->value);

        Test3::query()->truncate();
    }

    /**
     * Delete fields
     * @covers ::delete()
     */
    public function testDelete(): void
    {
        foreach ($this->data() as $val) {
            Test3::query()->create($val);
        }
        Test3::query()->where('id', 3)->delete();

        $find = Test3::query()->find(3);

        $this->assertNull($find);

        Test3::query()->truncate();
    }

    /**
     * Truncate fields
     * @covers ::truncate()
     */
    public function testTruncate(): void
    {
        foreach ($this->data() as $val) {
            Test3::query()->create($val);
        }

        Test3::query()->truncate();

        $find = Test3::query()->get();
        $this->assertCount(0, $find);
    }

    /**
     * @return array
     */
    private function data(): array
    {
        return [
            [
                'name' => 'name1',
                'value' => 555,
            ],
            [
                'name' => 'name2',
                'value' => 777,
            ],
            [
                'name' => 'name3',
                'value' => 'value3',
            ],
            [
                'name' => 'name4',
                'value' => null,
            ],
            [
                'name' => 'name5',
            ],
            [
                'name' => 'name6',
                'value' => ',',
            ],
        ];
    }
}
