<?php
declare(strict_types=1);

namespace Tests\Unit\Core;

use App\Core\QueryBuilder;
use Tests\TestCase;

class QueryBuilderTest extends TestCase
{
    private QueryBuilder $qb;

    protected function setUp(): void
    {
        parent::setUp();
        $this->qb = new QueryBuilder($this->createMockDb());
    }

    private function createMockDb(): \App\Core\Database
    {
        $mock = $this->createMock(\App\Core\Database::class);
        return $mock;
    }

    public function testBasicSelect(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()->toSql();
        $this->assertSame('SELECT * FROM `members`', $sql);
    }

    public function testSelectSpecificColumns(): void
    {
        $sql = $this->qb->table('members')->select(['id', 'name'])->noSoftDelete()->toSql();
        $this->assertSame('SELECT id, name FROM `members`', $sql);
    }

    public function testWhereClause(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()->where('status', '=', 'active')->toSql();
        $this->assertSame('SELECT * FROM `members` WHERE `status` = ?', $sql);
    }

    public function testMultipleWheres(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->where('status', '=', 'active')
            ->where('branch_id', '=', 1)
            ->toSql();
        $this->assertSame('SELECT * FROM `members` WHERE `status` = ? AND `branch_id` = ?', $sql);
    }

    public function testOrWhere(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->where('status', '=', 'active')
            ->orWhere('status', '=', 'pending')
            ->toSql();
        $this->assertSame('SELECT * FROM `members` WHERE `status` = ? OR `status` = ?', $sql);
    }

    public function testWhereIn(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->whereIn('id', [1, 2, 3])
            ->toSql();
        $this->assertSame('SELECT * FROM `members` WHERE `id` IN (?, ?, ?)', $sql);
    }

    public function testWhereInEmpty(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->whereIn('id', [])
            ->toSql();
        $this->assertSame('SELECT * FROM `members` WHERE 1 = 0', $sql);
    }

    public function testWhereNull(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->whereNull('deleted_at')
            ->toSql();
        $this->assertSame('SELECT * FROM `members` WHERE `deleted_at` IS NULL', $sql);
    }

    public function testWhereNotNull(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->whereNotNull('email')
            ->toSql();
        $this->assertSame('SELECT * FROM `members` WHERE `email` IS NOT NULL', $sql);
    }

    public function testWhereBetween(): void
    {
        $sql = $this->qb->table('payments')->noSoftDelete()
            ->whereBetween('amount', 100, 500)
            ->toSql();
        $this->assertSame('SELECT * FROM `payments` WHERE `amount` BETWEEN ? AND ?', $sql);
    }

    public function testJoin(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->join('branches', 'members.branch_id = branches.id')
            ->toSql();
        $this->assertSame('SELECT * FROM `members` INNER JOIN `branches` ON members.branch_id = branches.id', $sql);
    }

    public function testLeftJoin(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->leftJoin('photos', 'members.id = photos.member_id')
            ->toSql();
        $this->assertSame('SELECT * FROM `members` LEFT JOIN `photos` ON members.id = photos.member_id', $sql);
    }

    public function testOrderBy(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->orderBy('name', 'ASC')
            ->toSql();
        $this->assertSame('SELECT * FROM `members` ORDER BY `name` ASC', $sql);
    }

    public function testOrderByDesc(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->orderBy('created_at', 'DESC')
            ->toSql();
        $this->assertSame('SELECT * FROM `members` ORDER BY `created_at` DESC', $sql);
    }

    public function testGroupBy(): void
    {
        $sql = $this->qb->table('payments')->noSoftDelete()
            ->select('branch_id, SUM(amount) as total')
            ->groupBy('branch_id')
            ->toSql();
        $this->assertSame('SELECT branch_id, SUM(amount) as total FROM `payments` GROUP BY `branch_id`', $sql);
    }

    public function testHaving(): void
    {
        $sql = $this->qb->table('payments')->noSoftDelete()
            ->select('branch_id, SUM(amount) as total')
            ->groupBy('branch_id')
            ->having('total > 1000')
            ->toSql();
        $this->assertSame('SELECT branch_id, SUM(amount) as total FROM `payments` GROUP BY `branch_id` HAVING total > 1000', $sql);
    }

    public function testLimitOffset(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->limit(10)
            ->offset(20)
            ->toSql();
        $this->assertSame('SELECT * FROM `members` LIMIT 10 OFFSET 20', $sql);
    }

    public function testSoftDeleteFilterApplied(): void
    {
        $sql = $this->qb->table('members')->toSql();
        $this->assertStringContainsString('is_archived', $sql);
    }

    public function testWithArchivedRemovesFilter(): void
    {
        $sql = $this->qb->table('members')->withArchived()->toSql();
        $this->assertStringNotContainsString('is_archived', $sql);
    }

    public function testOnlyArchived(): void
    {
        $sql = $this->qb->table('members')->onlyArchived()->toSql();
        $this->assertStringContainsString('`is_archived` = 1', $sql);
    }

    public function testWhenTrue(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->when(true, fn($q) => $q->where('status', '=', 'active'))
            ->toSql();
        $this->assertStringContainsString('`status` = ?', $sql);
    }

    public function testWhenFalse(): void
    {
        $sql = $this->qb->table('members')->noSoftDelete()
            ->when(false, fn($q) => $q->where('status', '=', 'active'))
            ->toSql();
        $this->assertStringNotContainsString('status', $sql);
    }

    public function testImmutability(): void
    {
        $base = $this->qb->table('members')->noSoftDelete();
        $withWhere = $base->where('status', '=', 'active');

        $this->assertSame('SELECT * FROM `members`', $base->toSql());
        $this->assertSame('SELECT * FROM `members` WHERE `status` = ?', $withWhere->toSql());
    }
}
