<?php
/**
 * @author Daniel J. Summers <daniel@bitbadger.solutions>
 * @license MIT
 */

declare(strict_types=1);

use BitBadger\PDODocument\{Configuration, Field, Mode, Op};

pest()->group('unit');

describe('->appendParameter()', function () {
    afterEach(function () { Configuration::overrideMode(null); });
    test('appends no parameter for exists', function () {
        expect(Field::exists('exists')->appendParameter([]))->toBeEmpty();
    });
    test('appends no parameter for notExists', function () {
        expect(Field::notExists('absent')->appendParameter([]))->toBeEmpty();
    });
    test('appends two parameters for between', function () {
        expect(Field::between('exists', 5, 9, '@num')->appendParameter([]))
            ->toHaveLength(2)
            ->toEqual(['@nummin' => 5, '@nummax' => 9]);
    });
    test('appends a parameter for each value for in', function () {
        expect(Field::in('it', ['test', 'unit', 'great'], ':val')->appendParameter([]))
            ->toHaveLength(3)
            ->toEqual([':val_0' => 'test', ':val_1' => 'unit', ':val_2' => 'great']);
    });
    test('appends a parameter for each value for inArray [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::inArray('it', 'table', [2, 8, 64], ':bit')->appendParameter([]))
            ->toHaveLength(3)
            ->toEqual([':bit_0' => '2', ':bit_1' => '8', ':bit_2' => '64']);
    })->group('postgresql');
    test('appends a parameter for each value for inArray [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::inArray('it', 'table', [2, 8, 64], ':bit')->appendParameter([]))
            ->toHaveLength(3)
            ->toEqual([':bit_0' => 2, ':bit_1' => 8, ':bit_2' => 64]);
    })->group('sqlite');
    test('appends a parameter for other operators', function () {
        expect(Field::equal('the_field', 33, ':test')->appendParameter([]))
            ->toHaveLength(1)
            ->toEqual([':test' => 33]);
    });
});

describe('->path()', function () {
    afterEach(function () { Configuration::overrideMode(null); });
    test('returns simple SQL path [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::equal('it', 'that'))->path()->toBe("data->>'it'");
    })->group('postgresql');
    test('returns simple SQL path [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::equal('top', 'that'))->path()->toBe("data->>'top'");
    })->group('sqlite');
    test('returns nested SQL path [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::equal('parts.to.the.path', ''))->path()->toBe("data#>>'{parts,to,the,path}'");
    })->group('postgresql');
    test('returns nested SQL path [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::equal('one.two.three', ''))->path()->toBe("data->'one'->'two'->>'three'");
    })->group('sqlite');
    test('returns simple JSON path [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::equal('it', 'that'))->path(true)->toBe("data->'it'");
    })->group('postgresql');
    test('returns simple JSON path [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::equal('top', 'that'))->path(true)->toBe("data->'top'");
    })->group('sqlite');
    test('returns nested JSON path [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::equal('parts.to.the.path', ''))->path(true)->toBe("data#>'{parts,to,the,path}'");
    })->group('postgresql');
    test('returns nested JSON path [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::equal('one.two.three', ''))->path(true)->toBe("data->'one'->'two'->'three'");
    })->group('sqlite');
});

describe('->toWhere()', function () {
    afterEach(function () { Configuration::overrideMode(null); });
    test('generates IS NOT NULL for exists w/o qualifier [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::exists('that_field'))->toWhere()->toBe("data->>'that_field' IS NOT NULL");
    })->group('postgresql');
    test('generates IS NOT NULL for exists w/o qualifier [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::exists('that_field'))->toWhere()->toBe("data->>'that_field' IS NOT NULL");
    })->group('sqlite');
    test('generates IS NULL for notExists w/o qualifier [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::notExists('a_field'))->toWhere()->toBe("data->>'a_field' IS NULL");
    })->group('postgresql');
    test('generates IS NULL for notExists w/o qualifier [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::notExists('a_field'))->toWhere()->toBe("data->>'a_field' IS NULL");
    })->group('sqlite');
    test('generates BETWEEN for between w/o qualifier [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::between('age', 13, 17, '@age'))->toWhere()->toBe("data->>'age' BETWEEN @agemin AND @agemax");
    })->group('sqlite');
    test('generates BETWEEN for between w/o qualifier, numeric range [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::between('age', 13, 17, '@age'))->toWhere()
            ->toBe("(data->>'age')::numeric BETWEEN @agemin AND @agemax");
    })->group('postgresql');
    test('generates BETWEEN for between w/o qualifier, non-numeric range [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::between('city', 'Atlanta', 'Chicago', ':city'))->toWhere()
            ->toBe("data->>'city' BETWEEN :citymin AND :citymax");
    })->group('postgresql');
    test('generates BETWEEN for between w/ qualifier [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        $field = Field::between('age', 13, 17, '@age');
        $field->qualifier = 'me';
        expect($field)->toWhere()->toBe("me.data->>'age' BETWEEN @agemin AND @agemax");
    })->group('sqlite');
    test('generates BETWEEN for between w/ qualifier, numeric range [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        $field = Field::between('age', 13, 17, '@age');
        $field->qualifier = 'me';
        expect($field)->toWhere()->toBe("(me.data->>'age')::numeric BETWEEN @agemin AND @agemax");
    })->group('postgresql');
    test('generates BETWEEN for between w/ qualifier, non-numeric range [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        $field = Field::between('city', 'Atlanta', 'Chicago', ':city');
        $field->qualifier = 'me';
        expect($field)->toWhere()->toBe("me.data->>'city' BETWEEN :citymin AND :citymax");
    })->group('postgresql');
    test('generates IN for in, non-numeric values [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::in('test', ['Atlanta', 'Chicago'], ':city'))->toWhere()
            ->toBe("data->>'test' IN (:city_0, :city_1)");
    })->group('postgresql');
    test('generates IN for in, numeric values [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::in('even', [2, 4, 6], ':nbr'))->toWhere()
            ->toBe("(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)");
    })->group('postgresql');
    test('generates IN for in [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::in('test', ['Atlanta', 'Chicago'], ':city'))->toWhere()
            ->toBe("data->>'test' IN (:city_0, :city_1)");
    })->group('sqlite');
    test('generates clause for inArray [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::inArray('even', 'tbl', [2, 4, 6, 8], ':it'))->toWhere()
            ->toBe("data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]");
    })->group('postgresql');
    test('generates clause for inArray [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::inArray('test', 'tbl', ['Atlanta', 'Chicago'], ':city'))->toWhere()
            ->toBe("EXISTS (SELECT 1 FROM json_each(tbl.data, '\$.test') WHERE value IN (:city_0, :city_1))");
    })->group('sqlite');
    test('generates clause for other operators w/o qualifier [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        expect(Field::equal('some_field', '', ':value'))->toWhere()->toBe("data->>'some_field' = :value");
    })->group('postgresql');
    test('generates clause for other operators w/o qualifier [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        expect(Field::equal('some_field', '', ':value'))->toWhere()->toBe("data->>'some_field' = :value");
    })->group('sqlite');
    test('generates no-parameter clause w/ qualifier [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        $field = Field::exists('no_field');
        $field->qualifier = 'test';
        expect($field)->toWhere()->toBe("test.data->>'no_field' IS NOT NULL");
    })->group('postgresql');
    test('generates no-parameter clause w/ qualifier [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        $field = Field::exists('no_field');
        $field->qualifier = 'test';
        expect($field)->toWhere()->toBe("test.data->>'no_field' IS NOT NULL");
    })->group('sqlite');
    test('generates parameter clause w/ qualifier [PostgreSQL]', function () {
        Configuration::overrideMode(Mode::PgSQL);
        $field = Field::lessOrEqual('le_field', 18, ':it');
        $field->qualifier = 'q';
        expect($field)->toWhere()->toBe("(q.data->>'le_field')::numeric <= :it");
    })->group('postgresql');
    test('generates parameter clause w/ qualifier [SQLite]', function () {
        Configuration::overrideMode(Mode::SQLite);
        $field = Field::lessOrEqual('le_field', 18, ':it');
        $field->qualifier = 'q';
        expect($field)->toWhere()->toBe("q.data->>'le_field' <= :it");
    })->group('sqlite');
});

describe('::equal()', function () {
    test('creates Field w/o parameter', function () {
        $field = Field::equal('my_test', 9);
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('my_test')
            ->op->toBe(Op::Equal)
            ->paramName->toBeEmpty()
            ->and($field->value)->toBe(9);
    });
    test('creates Field w/ parameter', function () {
        $field = Field::equal('another_test', 'turkey', ':test');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('another_test')
            ->op->toBe(Op::Equal)
            ->paramName->toBe(':test')
            ->and($field->value)->toBe('turkey');
    });
});

describe('::greater()', function () {
    test('creates Field w/o parameter', function () {
        $field = Field::greater('your_test', 4);
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('your_test')
            ->op->toBe(Op::Greater)
            ->paramName->toBeEmpty()
            ->and($field->value)->toBe(4);
    });
    test('creates Field w/ parameter', function () {
        $field = Field::greater('more_test', 'chicken', ':value');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('more_test')
            ->op->toBe(Op::Greater)
            ->paramName->toBe(':value')
            ->and($field->value)->toBe('chicken');
    });
});

describe('::greaterOrEqual()', function () {
    test('creates Field w/o parameter', function () {
        $field = Field::greaterOrEqual('their_test', 6);
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('their_test')
            ->op->toBe(Op::GreaterOrEqual)
            ->paramName->toBeEmpty()
            ->and($field->value)->toBe(6);
    });
    test('creates Field w/ parameter', function () {
        $field = Field::greaterOrEqual('greater_test', 'poultry', ':cluck');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('greater_test')
            ->op->toBe(Op::GreaterOrEqual)
            ->paramName->toBe(':cluck')
            ->and($field->value)->toBe('poultry');
    });
});

describe('::less()', function () {
    test('creates Field w/o parameter', function () {
        $field = Field::less('z', 32);
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('z')
            ->op->toBe(Op::Less)
            ->paramName->toBeEmpty()
            ->and($field->value)->toBe(32);
    });
    test('creates Field w/ parameter', function () {
        $field = Field::less('additional_test', 'fowl', ':boo');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('additional_test')
            ->op->toBe(Op::Less)
            ->paramName->toBe(':boo')
            ->and($field->value)->toBe('fowl');
    });
});

describe('::lessOrEqual()', function () {
    test('creates Field w/o parameter', function () {
        $field = Field::lessOrEqual('g', 87);
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('g')
            ->op->toBe(Op::LessOrEqual)
            ->paramName->toBeEmpty()
            ->and($field->value)->toBe(87);
    });
    test('creates Field w/ parameter', function () {
        $field = Field::lessOrEqual('lesser_test', 'hen', ':woo');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('lesser_test')
            ->op->toBe(Op::LessOrEqual)
            ->paramName->toBe(':woo')
            ->and($field->value)->toBe('hen');
    });
});

describe('::notEqual()', function () {
    test('creates Field w/o parameter', function () {
        $field = Field::notEqual('j', 65);
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('j')
            ->op->toBe(Op::NotEqual)
            ->paramName->toBeEmpty()
            ->and($field->value)->toBe(65);
    });
    test('creates Field w/ parameter', function () {
        $field = Field::notEqual('unequal_test', 'egg', ':zoo');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('unequal_test')
            ->op->toBe(Op::NotEqual)
            ->paramName->toBe(':zoo')
            ->and($field->value)->toBe('egg');
    });
});

describe('::between()', function () {
    test('creates Field w/o parameter', function () {
        $field = Field::between('k', 'alpha', 'zed');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('k')
            ->op->toBe(Op::Between)
            ->paramName->toBeEmpty()
            ->and($field->value)->toEqual(['alpha', 'zed']);
    });
    test('creates Field w/ parameter', function () {
        $field = Field::between('between_test', 18, 49, ':count');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('between_test')
            ->op->toBe(Op::Between)
            ->paramName->toBe(':count')
            ->and($field->value)->toEqual([18, 49]);
    });
});

describe('::in()', function () {
    test('creates Field w/o parameter', function () {
        $field = Field::in('test', [1, 2, 3]);
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('test')
            ->op->toBe(Op::In)
            ->paramName->toBeEmpty()
            ->and($field->value)->toEqual([1, 2, 3]);
    });
    test('creates Field w/ parameter', function () {
        $field = Field::in('unit', ['a', 'b'], ':inParam');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('unit')
            ->op->toBe(Op::In)
            ->paramName->toBe(':inParam')
            ->and($field->value)->toEqual(['a', 'b']);
    });
});

describe('::inArray()', function () {
    test('creates Field w/o parameter', function () {
        $field = Field::inArray('test', 'tbl', [1, 2, 3]);
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('test')
            ->op->toBe(Op::InArray)
            ->paramName->toBeEmpty()
            ->and($field->value)->toEqual(['table' => 'tbl', 'values' => [1, 2, 3]]);
    });
    test('creates Field w/ parameter', function () {
        $field = Field::inArray('unit', 'tab', ['a', 'b'], ':inAParam');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('unit')
            ->op->toBe(Op::InArray)
            ->paramName->toBe(':inAParam')
            ->and($field->value)->toEqual(['table' => 'tab', 'values' => ['a', 'b']]);
    });
});

describe('::exists()', function () {
    test('creates Field', function () {
        $field = Field::exists('be_there');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('be_there')
            ->op->toBe(Op::Exists)
            ->paramName->toBeEmpty()
            ->and($field->value)->toBeEmpty();
    });
});

describe('::notExists()', function () {
    test('creates Field', function () {
        $field = Field::notExists('be_absent');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('be_absent')
            ->op->toBe(Op::NotExists)
            ->paramName->toBeEmpty()
            ->and($field->value)->toBeEmpty();
    });
});

describe('::named()', function () {
    test('creates Field', function () {
        $field = Field::named('the_field');
        expect($field)
            ->not->toBeNull()
            ->fieldName->toBe('the_field')
            ->op->toBe(Op::Equal)
            ->value->toBeEmpty()
            ->and($field->value)->toBeEmpty();
    });
});